Merge branch 'frontend-core' of github.com:Budibase/budibase into experimental-hbs-caching
This commit is contained in:
commit
14eca1b461
|
@ -108,6 +108,8 @@ spec:
|
||||||
value: {{ .Values.globals.accountPortalApiKey | quote }}
|
value: {{ .Values.globals.accountPortalApiKey | quote }}
|
||||||
- name: COOKIE_DOMAIN
|
- name: COOKIE_DOMAIN
|
||||||
value: {{ .Values.globals.cookieDomain | quote }}
|
value: {{ .Values.globals.cookieDomain | quote }}
|
||||||
|
- name: HTTP_MIGRATIONS
|
||||||
|
value: {{ .Values.globals.httpMigrations | quote }}
|
||||||
image: budibase/apps:{{ .Values.globals.appVersion }}
|
image: budibase/apps:{{ .Values.globals.appVersion }}
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
name: bbapps
|
name: bbapps
|
||||||
|
|
|
@ -99,6 +99,7 @@ globals:
|
||||||
accountPortalApiKey: ""
|
accountPortalApiKey: ""
|
||||||
cookieDomain: ""
|
cookieDomain: ""
|
||||||
platformUrl: ""
|
platformUrl: ""
|
||||||
|
httpMigrations: "0"
|
||||||
|
|
||||||
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
createSecrets: true # creates an internal API key, JWT secrets and redis password for you
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "1.0.46-alpha.8",
|
"version": "1.0.49-alpha.5",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -36,10 +36,10 @@
|
||||||
"dev:server": "lerna run --parallel dev:builder --concurrency 1 --scope @budibase/worker --scope @budibase/server",
|
"dev:server": "lerna run --parallel dev:builder --concurrency 1 --scope @budibase/worker --scope @budibase/server",
|
||||||
"test": "lerna run test",
|
"test": "lerna run test",
|
||||||
"lint:eslint": "eslint packages",
|
"lint:eslint": "eslint packages",
|
||||||
"lint:prettier": "prettier --check \"packages/**/*.{js,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",
|
||||||
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,svelte}\"",
|
"lint:fix:prettier": "prettier --write \"packages/**/*.{js,ts,svelte}\"",
|
||||||
"lint:fix:ts": "lerna run lint:fix",
|
"lint:fix:ts": "lerna run lint:fix",
|
||||||
"lint:fix": "yarn run lint:fix:ts && yarn run lint:fix:prettier && yarn run lint:fix:eslint",
|
"lint:fix": "yarn run lint:fix:ts && yarn run lint:fix:prettier && yarn run lint:fix:eslint",
|
||||||
"test:e2e": "lerna run cy:test",
|
"test:e2e": "lerna run cy:test",
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
const {
|
||||||
|
getAppDB,
|
||||||
|
getDevAppDB,
|
||||||
|
getProdAppDB,
|
||||||
|
getAppId,
|
||||||
|
updateAppId,
|
||||||
|
doInAppContext,
|
||||||
|
} = require("./src/context")
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getAppDB,
|
||||||
|
getDevAppDB,
|
||||||
|
getProdAppDB,
|
||||||
|
getAppId,
|
||||||
|
updateAppId,
|
||||||
|
doInAppContext,
|
||||||
|
}
|
|
@ -1,4 +1,6 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...require("./src/db/utils"),
|
...require("./src/db/utils"),
|
||||||
...require("./src/db/constants"),
|
...require("./src/db/constants"),
|
||||||
|
...require("./src/db"),
|
||||||
|
...require("./src/db/views"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
module.exports = require("./src/tenancy/deprovision")
|
module.exports = require("./src/context/deprovision")
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "1.0.46-alpha.8",
|
"version": "1.0.49-alpha.5",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
|
|
|
@ -13,6 +13,7 @@ const {
|
||||||
appTenancy,
|
appTenancy,
|
||||||
authError,
|
authError,
|
||||||
csrf,
|
csrf,
|
||||||
|
internalApi,
|
||||||
} = require("./middleware")
|
} = require("./middleware")
|
||||||
|
|
||||||
// Strategies
|
// Strategies
|
||||||
|
@ -44,4 +45,5 @@ module.exports = {
|
||||||
auditLog,
|
auditLog,
|
||||||
authError,
|
authError,
|
||||||
buildCsrfMiddleware: csrf,
|
buildCsrfMiddleware: csrf,
|
||||||
|
internalApi,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ const { newid } = require("../hashing")
|
||||||
const REQUEST_ID_KEY = "requestId"
|
const REQUEST_ID_KEY = "requestId"
|
||||||
|
|
||||||
class FunctionContext {
|
class FunctionContext {
|
||||||
static getMiddleware(updateCtxFn = null) {
|
static getMiddleware(updateCtxFn = null, contextName = "session") {
|
||||||
const namespace = this.createNamespace()
|
const namespace = this.createNamespace(contextName)
|
||||||
|
|
||||||
return async function (ctx, next) {
|
return async function (ctx, next) {
|
||||||
await new Promise(
|
await new Promise(
|
||||||
|
@ -24,14 +24,14 @@ class FunctionContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static run(callback) {
|
static run(callback, contextName = "session") {
|
||||||
const namespace = this.createNamespace()
|
const namespace = this.createNamespace(contextName)
|
||||||
|
|
||||||
return namespace.runAndReturn(callback)
|
return namespace.runAndReturn(callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
static setOnContext(key, value) {
|
static setOnContext(key, value, contextName = "session") {
|
||||||
const namespace = this.createNamespace()
|
const namespace = this.createNamespace(contextName)
|
||||||
namespace.set(key, value)
|
namespace.set(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,16 +55,16 @@ class FunctionContext {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static destroyNamespace() {
|
static destroyNamespace(name = "session") {
|
||||||
if (this._namespace) {
|
if (this._namespace) {
|
||||||
cls.destroyNamespace("session")
|
cls.destroyNamespace(name)
|
||||||
this._namespace = null
|
this._namespace = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static createNamespace() {
|
static createNamespace(name = "session") {
|
||||||
if (!this._namespace) {
|
if (!this._namespace) {
|
||||||
this._namespace = cls.createNamespace("session")
|
this._namespace = cls.createNamespace(name)
|
||||||
}
|
}
|
||||||
return this._namespace
|
return this._namespace
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
const { getGlobalUserParams, getAllApps } = require("../db/utils")
|
const { getGlobalUserParams, getAllApps } = require("../db/utils")
|
||||||
const { getDB, getCouch } = require("../db")
|
const { getDB, getCouch } = require("../db")
|
||||||
const { getGlobalDB } = require("./tenancy")
|
const { getGlobalDB } = require("../tenancy")
|
||||||
const { StaticDatabases } = require("../db/constants")
|
const { StaticDatabases } = require("../db/constants")
|
||||||
|
|
||||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
|
@ -0,0 +1,195 @@
|
||||||
|
const env = require("../environment")
|
||||||
|
const { Headers } = require("../../constants")
|
||||||
|
const cls = require("./FunctionContext")
|
||||||
|
const { getCouch } = require("../db")
|
||||||
|
const { getProdAppID, getDevelopmentAppID } = require("../db/conversions")
|
||||||
|
const { isEqual } = require("lodash")
|
||||||
|
|
||||||
|
// some test cases call functions directly, need to
|
||||||
|
// store an app ID to pretend there is a context
|
||||||
|
let TEST_APP_ID = null
|
||||||
|
|
||||||
|
const ContextKeys = {
|
||||||
|
TENANT_ID: "tenantId",
|
||||||
|
APP_ID: "appId",
|
||||||
|
// whatever the request app DB was
|
||||||
|
CURRENT_DB: "currentDb",
|
||||||
|
// get the prod app DB from the request
|
||||||
|
PROD_DB: "prodDb",
|
||||||
|
// get the dev app DB from the request
|
||||||
|
DEV_DB: "devDb",
|
||||||
|
DB_OPTS: "dbOpts",
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.DEFAULT_TENANT_ID = "default"
|
||||||
|
|
||||||
|
exports.isDefaultTenant = () => {
|
||||||
|
return exports.getTenantId() === exports.DEFAULT_TENANT_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.isMultiTenant = () => {
|
||||||
|
return env.MULTI_TENANCY
|
||||||
|
}
|
||||||
|
|
||||||
|
// used for automations, API endpoints should always be in context already
|
||||||
|
exports.doInTenant = (tenantId, task) => {
|
||||||
|
return cls.run(() => {
|
||||||
|
// set the tenant id
|
||||||
|
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
||||||
|
|
||||||
|
// invoke the task
|
||||||
|
return task()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.doInAppContext = (appId, task) => {
|
||||||
|
return cls.run(() => {
|
||||||
|
// set the app ID
|
||||||
|
cls.setOnContext(ContextKeys.APP_ID, appId)
|
||||||
|
|
||||||
|
// invoke the task
|
||||||
|
return task()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.updateTenantId = tenantId => {
|
||||||
|
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.updateAppId = appId => {
|
||||||
|
try {
|
||||||
|
cls.setOnContext(ContextKeys.APP_ID, appId)
|
||||||
|
cls.setOnContext(ContextKeys.PROD_DB, null)
|
||||||
|
cls.setOnContext(ContextKeys.DEV_DB, null)
|
||||||
|
cls.setOnContext(ContextKeys.CURRENT_DB, null)
|
||||||
|
cls.setOnContext(ContextKeys.DB_OPTS, null)
|
||||||
|
} catch (err) {
|
||||||
|
if (env.isTest()) {
|
||||||
|
TEST_APP_ID = appId
|
||||||
|
} else {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.setTenantId = (
|
||||||
|
ctx,
|
||||||
|
opts = { allowQs: false, allowNoTenant: false }
|
||||||
|
) => {
|
||||||
|
let tenantId
|
||||||
|
// exit early if not multi-tenant
|
||||||
|
if (!exports.isMultiTenant()) {
|
||||||
|
cls.setOnContext(ContextKeys.TENANT_ID, this.DEFAULT_TENANT_ID)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowQs = opts && opts.allowQs
|
||||||
|
const allowNoTenant = opts && opts.allowNoTenant
|
||||||
|
const header = ctx.request.headers[Headers.TENANT_ID]
|
||||||
|
const user = ctx.user || {}
|
||||||
|
if (allowQs) {
|
||||||
|
const query = ctx.request.query || {}
|
||||||
|
tenantId = query.tenantId
|
||||||
|
}
|
||||||
|
// override query string (if allowed) by user, or header
|
||||||
|
// URL params cannot be used in a middleware, as they are
|
||||||
|
// processed later in the chain
|
||||||
|
tenantId = user.tenantId || header || tenantId
|
||||||
|
|
||||||
|
// Set the tenantId from the subdomain
|
||||||
|
if (!tenantId) {
|
||||||
|
tenantId = ctx.subdomains && ctx.subdomains[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tenantId && !allowNoTenant) {
|
||||||
|
ctx.throw(403, "Tenant id not set")
|
||||||
|
}
|
||||||
|
// check tenant ID just incase no tenant was allowed
|
||||||
|
if (tenantId) {
|
||||||
|
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.isTenantIdSet = () => {
|
||||||
|
const tenantId = cls.getFromContext(ContextKeys.TENANT_ID)
|
||||||
|
return !!tenantId
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getTenantId = () => {
|
||||||
|
if (!exports.isMultiTenant()) {
|
||||||
|
return exports.DEFAULT_TENANT_ID
|
||||||
|
}
|
||||||
|
const tenantId = cls.getFromContext(ContextKeys.TENANT_ID)
|
||||||
|
if (!tenantId) {
|
||||||
|
throw Error("Tenant id not found")
|
||||||
|
}
|
||||||
|
return tenantId
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getAppId = () => {
|
||||||
|
const foundId = cls.getFromContext(ContextKeys.APP_ID)
|
||||||
|
if (!foundId && env.isTest() && TEST_APP_ID) {
|
||||||
|
return TEST_APP_ID
|
||||||
|
} else {
|
||||||
|
return foundId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDB(key, opts) {
|
||||||
|
const dbOptsKey = `${key}${ContextKeys.DB_OPTS}`
|
||||||
|
let storedOpts = cls.getFromContext(dbOptsKey)
|
||||||
|
let db = cls.getFromContext(key)
|
||||||
|
if (db && isEqual(opts, storedOpts)) {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
const appId = exports.getAppId()
|
||||||
|
const CouchDB = getCouch()
|
||||||
|
let toUseAppId
|
||||||
|
switch (key) {
|
||||||
|
case ContextKeys.CURRENT_DB:
|
||||||
|
toUseAppId = appId
|
||||||
|
break
|
||||||
|
case ContextKeys.PROD_DB:
|
||||||
|
toUseAppId = getProdAppID(appId)
|
||||||
|
break
|
||||||
|
case ContextKeys.DEV_DB:
|
||||||
|
toUseAppId = getDevelopmentAppID(appId)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
db = new CouchDB(toUseAppId, opts)
|
||||||
|
try {
|
||||||
|
cls.setOnContext(key, db)
|
||||||
|
if (opts) {
|
||||||
|
cls.setOnContext(dbOptsKey, opts)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (!env.isTest()) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the app database based on whatever the request
|
||||||
|
* contained, dev or prod.
|
||||||
|
*/
|
||||||
|
exports.getAppDB = opts => {
|
||||||
|
return getDB(ContextKeys.CURRENT_DB, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This specifically gets the prod app ID, if the request
|
||||||
|
* contained a development app ID, this will open the prod one.
|
||||||
|
*/
|
||||||
|
exports.getProdAppDB = opts => {
|
||||||
|
return getDB(ContextKeys.PROD_DB, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This specifically gets the dev app ID, if the request
|
||||||
|
* contained a prod app ID, this will open the dev one.
|
||||||
|
*/
|
||||||
|
exports.getDevAppDB = opts => {
|
||||||
|
return getDB(ContextKeys.DEV_DB, opts)
|
||||||
|
}
|
|
@ -32,3 +32,7 @@ exports.StaticDatabases = {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.APP_PREFIX = exports.DocumentTypes.APP + exports.SEPARATOR
|
||||||
|
exports.APP_DEV = exports.APP_DEV_PREFIX =
|
||||||
|
exports.DocumentTypes.APP_DEV + exports.SEPARATOR
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
const NO_APP_ERROR = "No app provided"
|
||||||
|
const { APP_DEV_PREFIX, APP_PREFIX } = require("./constants")
|
||||||
|
|
||||||
|
exports.isDevAppID = appId => {
|
||||||
|
if (!appId) {
|
||||||
|
throw NO_APP_ERROR
|
||||||
|
}
|
||||||
|
return appId.startsWith(APP_DEV_PREFIX)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.isProdAppID = appId => {
|
||||||
|
if (!appId) {
|
||||||
|
throw NO_APP_ERROR
|
||||||
|
}
|
||||||
|
return appId.startsWith(APP_PREFIX) && !exports.isDevAppID(appId)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.isDevApp = app => {
|
||||||
|
if (!app) {
|
||||||
|
throw NO_APP_ERROR
|
||||||
|
}
|
||||||
|
return exports.isDevAppID(app.appId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a development app ID to a deployed app ID.
|
||||||
|
*/
|
||||||
|
exports.getProdAppID = appId => {
|
||||||
|
// if dev, convert it
|
||||||
|
if (appId.startsWith(APP_DEV_PREFIX)) {
|
||||||
|
const id = appId.split(APP_DEV_PREFIX)[1]
|
||||||
|
return `${APP_PREFIX}${id}`
|
||||||
|
}
|
||||||
|
return appId
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a deployed app ID to a development app ID.
|
||||||
|
*/
|
||||||
|
exports.getDevelopmentAppID = appId => {
|
||||||
|
if (!appId.startsWith(APP_DEV_PREFIX)) {
|
||||||
|
const id = appId.split(APP_PREFIX)[1]
|
||||||
|
return `${APP_DEV_PREFIX}${id}`
|
||||||
|
}
|
||||||
|
return appId
|
||||||
|
}
|
|
@ -2,7 +2,13 @@ const { newid } = require("../hashing")
|
||||||
const Replication = require("./Replication")
|
const Replication = require("./Replication")
|
||||||
const { DEFAULT_TENANT_ID, Configs } = require("../constants")
|
const { DEFAULT_TENANT_ID, Configs } = require("../constants")
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
const { StaticDatabases, SEPARATOR, DocumentTypes } = require("./constants")
|
const {
|
||||||
|
StaticDatabases,
|
||||||
|
SEPARATOR,
|
||||||
|
DocumentTypes,
|
||||||
|
APP_PREFIX,
|
||||||
|
APP_DEV,
|
||||||
|
} = require("./constants")
|
||||||
const {
|
const {
|
||||||
getTenantId,
|
getTenantId,
|
||||||
getTenantIDFromAppID,
|
getTenantIDFromAppID,
|
||||||
|
@ -12,8 +18,13 @@ const fetch = require("node-fetch")
|
||||||
const { getCouch } = require("./index")
|
const { getCouch } = require("./index")
|
||||||
const { getAppMetadata } = require("../cache/appMetadata")
|
const { getAppMetadata } = require("../cache/appMetadata")
|
||||||
const { checkSlashesInUrl } = require("../helpers")
|
const { checkSlashesInUrl } = require("../helpers")
|
||||||
|
const {
|
||||||
const NO_APP_ERROR = "No app provided"
|
isDevApp,
|
||||||
|
isProdAppID,
|
||||||
|
isDevAppID,
|
||||||
|
getDevelopmentAppID,
|
||||||
|
getProdAppID,
|
||||||
|
} = require("./conversions")
|
||||||
|
|
||||||
const UNICODE_MAX = "\ufff0"
|
const UNICODE_MAX = "\ufff0"
|
||||||
|
|
||||||
|
@ -24,10 +35,15 @@ exports.ViewNames = {
|
||||||
exports.StaticDatabases = StaticDatabases
|
exports.StaticDatabases = StaticDatabases
|
||||||
|
|
||||||
exports.DocumentTypes = DocumentTypes
|
exports.DocumentTypes = DocumentTypes
|
||||||
exports.APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
exports.APP_PREFIX = APP_PREFIX
|
||||||
exports.APP_DEV = exports.APP_DEV_PREFIX = DocumentTypes.APP_DEV + SEPARATOR
|
exports.APP_DEV = exports.APP_DEV_PREFIX = APP_DEV
|
||||||
exports.SEPARATOR = SEPARATOR
|
exports.SEPARATOR = SEPARATOR
|
||||||
exports.getTenantIDFromAppID = getTenantIDFromAppID
|
exports.getTenantIDFromAppID = getTenantIDFromAppID
|
||||||
|
exports.isDevApp = isDevApp
|
||||||
|
exports.isProdAppID = isProdAppID
|
||||||
|
exports.isDevAppID = isDevAppID
|
||||||
|
exports.getDevelopmentAppID = getDevelopmentAppID
|
||||||
|
exports.getProdAppID = getProdAppID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If creating DB allDocs/query params with only a single top level ID this can be used, this
|
* If creating DB allDocs/query params with only a single top level ID this can be used, this
|
||||||
|
@ -52,27 +68,6 @@ function getDocParams(docType, docId = null, otherProps = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.isDevAppID = appId => {
|
|
||||||
if (!appId) {
|
|
||||||
throw NO_APP_ERROR
|
|
||||||
}
|
|
||||||
return appId.startsWith(exports.APP_DEV_PREFIX)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.isProdAppID = appId => {
|
|
||||||
if (!appId) {
|
|
||||||
throw NO_APP_ERROR
|
|
||||||
}
|
|
||||||
return appId.startsWith(exports.APP_PREFIX) && !exports.isDevAppID(appId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDevApp(app) {
|
|
||||||
if (!app) {
|
|
||||||
throw NO_APP_ERROR
|
|
||||||
}
|
|
||||||
return exports.isDevAppID(app.appId)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a new workspace ID.
|
* Generates a new workspace ID.
|
||||||
* @returns {string} The new workspace ID which the workspace doc can be stored under.
|
* @returns {string} The new workspace ID which the workspace doc can be stored under.
|
||||||
|
@ -157,29 +152,6 @@ exports.getRoleParams = (roleId = null, otherProps = {}) => {
|
||||||
return getDocParams(DocumentTypes.ROLE, roleId, otherProps)
|
return getDocParams(DocumentTypes.ROLE, roleId, otherProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a development app ID to a deployed app ID.
|
|
||||||
*/
|
|
||||||
exports.getDeployedAppID = appId => {
|
|
||||||
// if dev, convert it
|
|
||||||
if (appId.startsWith(exports.APP_DEV_PREFIX)) {
|
|
||||||
const id = appId.split(exports.APP_DEV_PREFIX)[1]
|
|
||||||
return `${exports.APP_PREFIX}${id}`
|
|
||||||
}
|
|
||||||
return appId
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a deployed app ID to a development app ID.
|
|
||||||
*/
|
|
||||||
exports.getDevelopmentAppID = appId => {
|
|
||||||
if (!appId.startsWith(exports.APP_DEV_PREFIX)) {
|
|
||||||
const id = appId.split(exports.APP_PREFIX)[1]
|
|
||||||
return `${exports.APP_DEV_PREFIX}${id}`
|
|
||||||
}
|
|
||||||
return appId
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.getCouchUrl = () => {
|
exports.getCouchUrl = () => {
|
||||||
if (!env.COUCH_DB_URL) return
|
if (!env.COUCH_DB_URL) return
|
||||||
|
|
||||||
|
@ -225,7 +197,7 @@ exports.getAllDbs = async () => {
|
||||||
}
|
}
|
||||||
let couchUrl = `${exports.getCouchUrl()}/_all_dbs`
|
let couchUrl = `${exports.getCouchUrl()}/_all_dbs`
|
||||||
let tenantId = getTenantId()
|
let tenantId = getTenantId()
|
||||||
if (!env.MULTI_TENANCY || tenantId == DEFAULT_TENANT_ID) {
|
if (!env.MULTI_TENANCY || tenantId === DEFAULT_TENANT_ID) {
|
||||||
// just get all DBs when:
|
// just get all DBs when:
|
||||||
// - single tenancy
|
// - single tenancy
|
||||||
// - default tenant
|
// - default tenant
|
||||||
|
@ -250,11 +222,10 @@ exports.getAllDbs = async () => {
|
||||||
/**
|
/**
|
||||||
* Lots of different points in the system need to find the full list of apps, this will
|
* Lots of different points in the system need to find the full list of apps, this will
|
||||||
* enumerate the entire CouchDB cluster and get the list of databases (every app).
|
* enumerate the entire CouchDB cluster and get the list of databases (every app).
|
||||||
* NOTE: this operation is fine in self hosting, but cannot be used when hosting many
|
|
||||||
* different users/companies apps as there is no security around it - all apps are returned.
|
|
||||||
* @return {Promise<object[]>} returns the app information document stored in each app database.
|
* @return {Promise<object[]>} returns the app information document stored in each app database.
|
||||||
*/
|
*/
|
||||||
exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => {
|
exports.getAllApps = async ({ dev, all, idsOnly } = {}) => {
|
||||||
|
const CouchDB = getCouch()
|
||||||
let tenantId = getTenantId()
|
let tenantId = getTenantId()
|
||||||
if (!env.MULTI_TENANCY && !tenantId) {
|
if (!env.MULTI_TENANCY && !tenantId) {
|
||||||
tenantId = DEFAULT_TENANT_ID
|
tenantId = DEFAULT_TENANT_ID
|
||||||
|
@ -310,8 +281,8 @@ exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => {
|
||||||
/**
|
/**
|
||||||
* Utility function for getAllApps but filters to production apps only.
|
* Utility function for getAllApps but filters to production apps only.
|
||||||
*/
|
*/
|
||||||
exports.getDeployedAppIDs = async CouchDB => {
|
exports.getProdAppIDs = async () => {
|
||||||
return (await exports.getAllApps(CouchDB, { idsOnly: true })).filter(
|
return (await exports.getAllApps({ idsOnly: true })).filter(
|
||||||
id => !exports.isDevAppID(id)
|
id => !exports.isDevAppID(id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -319,13 +290,14 @@ exports.getDeployedAppIDs = async CouchDB => {
|
||||||
/**
|
/**
|
||||||
* Utility function for the inverse of above.
|
* Utility function for the inverse of above.
|
||||||
*/
|
*/
|
||||||
exports.getDevAppIDs = async CouchDB => {
|
exports.getDevAppIDs = async () => {
|
||||||
return (await exports.getAllApps(CouchDB, { idsOnly: true })).filter(id =>
|
return (await exports.getAllApps({ idsOnly: true })).filter(id =>
|
||||||
exports.isDevAppID(id)
|
exports.isDevAppID(id)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.dbExists = async (CouchDB, dbName) => {
|
exports.dbExists = async dbName => {
|
||||||
|
const CouchDB = getCouch()
|
||||||
let exists = false
|
let exists = false
|
||||||
try {
|
try {
|
||||||
const db = CouchDB(dbName, { skip_setup: true })
|
const db = CouchDB(dbName, { skip_setup: true })
|
||||||
|
|
|
@ -3,8 +3,9 @@ const {
|
||||||
updateTenantId,
|
updateTenantId,
|
||||||
isTenantIdSet,
|
isTenantIdSet,
|
||||||
DEFAULT_TENANT_ID,
|
DEFAULT_TENANT_ID,
|
||||||
|
updateAppId,
|
||||||
} = require("../tenancy")
|
} = require("../tenancy")
|
||||||
const ContextFactory = require("../tenancy/FunctionContext")
|
const ContextFactory = require("../context/FunctionContext")
|
||||||
const { getTenantIDFromAppID } = require("../db/utils")
|
const { getTenantIDFromAppID } = require("../db/utils")
|
||||||
|
|
||||||
module.exports = () => {
|
module.exports = () => {
|
||||||
|
@ -21,5 +22,6 @@ module.exports = () => {
|
||||||
const appId = ctx.appId ? ctx.appId : ctx.user ? ctx.user.appId : null
|
const appId = ctx.appId ? ctx.appId : ctx.user ? ctx.user.appId : null
|
||||||
const tenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID
|
const tenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID
|
||||||
updateTenantId(tenantId)
|
updateTenantId(tenantId)
|
||||||
|
updateAppId(appId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ const authenticated = require("./authenticated")
|
||||||
const auditLog = require("./auditLog")
|
const auditLog = require("./auditLog")
|
||||||
const tenancy = require("./tenancy")
|
const tenancy = require("./tenancy")
|
||||||
const appTenancy = require("./appTenancy")
|
const appTenancy = require("./appTenancy")
|
||||||
|
const internalApi = require("./internalApi")
|
||||||
const datasourceGoogle = require("./passport/datasource/google")
|
const datasourceGoogle = require("./passport/datasource/google")
|
||||||
const csrf = require("./csrf")
|
const csrf = require("./csrf")
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@ module.exports = {
|
||||||
tenancy,
|
tenancy,
|
||||||
appTenancy,
|
appTenancy,
|
||||||
authError,
|
authError,
|
||||||
|
internalApi,
|
||||||
datasource: {
|
datasource: {
|
||||||
google: datasourceGoogle,
|
google: datasourceGoogle,
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
const env = require("../environment")
|
||||||
|
const { Headers } = require("../constants")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Key only endpoint.
|
||||||
|
*/
|
||||||
|
module.exports = async (ctx, next) => {
|
||||||
|
const apiKey = ctx.request.headers[Headers.API_KEY]
|
||||||
|
if (apiKey !== env.INTERNAL_API_KEY) {
|
||||||
|
ctx.throw(403, "Unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
const { setTenantId } = require("../tenancy")
|
const { setTenantId } = require("../tenancy")
|
||||||
const ContextFactory = require("../tenancy/FunctionContext")
|
const ContextFactory = require("../context/FunctionContext")
|
||||||
const { buildMatcherRegex, matches } = require("./matchers")
|
const { buildMatcherRegex, matches } = require("./matchers")
|
||||||
|
|
||||||
module.exports = (
|
module.exports = (
|
||||||
|
|
|
@ -1,20 +1,17 @@
|
||||||
|
const { DEFAULT_TENANT_ID } = require("../constants")
|
||||||
const { DocumentTypes } = require("../db/constants")
|
const { DocumentTypes } = require("../db/constants")
|
||||||
const { getGlobalDB, getTenantId } = require("../tenancy")
|
const { getAllApps } = require("../db/utils")
|
||||||
|
const environment = require("../environment")
|
||||||
|
const {
|
||||||
|
doInTenant,
|
||||||
|
getTenantIds,
|
||||||
|
getGlobalDBName,
|
||||||
|
getTenantId,
|
||||||
|
} = require("../tenancy")
|
||||||
|
|
||||||
exports.MIGRATION_DBS = {
|
exports.MIGRATION_TYPES = {
|
||||||
GLOBAL_DB: "GLOBAL_DB",
|
GLOBAL: "global", // run once, recorded in global db, global db is provided as an argument
|
||||||
}
|
APP: "app", // run per app, recorded in each app db, app db is provided as an argument
|
||||||
|
|
||||||
exports.MIGRATIONS = {
|
|
||||||
USER_EMAIL_VIEW_CASING: "user_email_view_casing",
|
|
||||||
QUOTAS_1: "quotas_1",
|
|
||||||
}
|
|
||||||
|
|
||||||
const DB_LOOKUP = {
|
|
||||||
[exports.MIGRATION_DBS.GLOBAL_DB]: [
|
|
||||||
exports.MIGRATIONS.USER_EMAIL_VIEW_CASING,
|
|
||||||
exports.MIGRATIONS.QUOTAS_1,
|
|
||||||
],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getMigrationsDoc = async db => {
|
exports.getMigrationsDoc = async db => {
|
||||||
|
@ -28,40 +25,90 @@ exports.getMigrationsDoc = async db => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.migrateIfRequired = async (migrationDb, migrationName, migrateFn) => {
|
const runMigration = async (CouchDB, migration, options = {}) => {
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
try {
|
const migrationType = migration.type
|
||||||
let db
|
const migrationName = migration.name
|
||||||
if (migrationDb === exports.MIGRATION_DBS.GLOBAL_DB) {
|
|
||||||
db = getGlobalDB()
|
|
||||||
} else {
|
|
||||||
throw new Error(`Unrecognised migration db [${migrationDb}]`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!DB_LOOKUP[migrationDb].includes(migrationName)) {
|
// get the db to store the migration in
|
||||||
throw new Error(
|
let dbNames
|
||||||
`Unrecognised migration name [${migrationName}] for db [${migrationDb}]`
|
if (migrationType === exports.MIGRATION_TYPES.GLOBAL) {
|
||||||
)
|
dbNames = [getGlobalDBName()]
|
||||||
}
|
} else if (migrationType === exports.MIGRATION_TYPES.APP) {
|
||||||
|
const apps = await getAllApps(CouchDB, migration.opts)
|
||||||
const doc = await exports.getMigrationsDoc(db)
|
dbNames = apps.map(app => app.appId)
|
||||||
// exit if the migration has been performed
|
} else {
|
||||||
if (doc[migrationName]) {
|
throw new Error(
|
||||||
return
|
`[Tenant: ${tenantId}] Unrecognised migration type [${migrationType}]`
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[Tenant: ${tenantId}] Performing migration: ${migrationName}`)
|
|
||||||
await migrateFn()
|
|
||||||
console.log(`[Tenant: ${tenantId}] Migration complete: ${migrationName}`)
|
|
||||||
|
|
||||||
// mark as complete
|
|
||||||
doc[migrationName] = Date.now()
|
|
||||||
await db.put(doc)
|
|
||||||
} catch (err) {
|
|
||||||
console.error(
|
|
||||||
`[Tenant: ${tenantId}] Error performing migration: ${migrationName}: `,
|
|
||||||
err
|
|
||||||
)
|
)
|
||||||
throw err
|
}
|
||||||
|
|
||||||
|
// run the migration against each db
|
||||||
|
for (const dbName of dbNames) {
|
||||||
|
const db = new CouchDB(dbName)
|
||||||
|
try {
|
||||||
|
const doc = await exports.getMigrationsDoc(db)
|
||||||
|
|
||||||
|
// exit if the migration has been performed already
|
||||||
|
if (doc[migrationName]) {
|
||||||
|
if (
|
||||||
|
options.force &&
|
||||||
|
options.force[migrationType] &&
|
||||||
|
options.force[migrationType].includes(migrationName)
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Forcing`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// the migration has already been performed
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Running`
|
||||||
|
)
|
||||||
|
// run the migration with tenant context
|
||||||
|
await migration.fn(db)
|
||||||
|
console.log(
|
||||||
|
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Complete`
|
||||||
|
)
|
||||||
|
|
||||||
|
// mark as complete
|
||||||
|
doc[migrationName] = Date.now()
|
||||||
|
await db.put(doc)
|
||||||
|
} catch (err) {
|
||||||
|
console.error(
|
||||||
|
`[Tenant: ${tenantId}] [Migration: ${migrationName}] [DB: ${dbName}] Error: `,
|
||||||
|
err
|
||||||
|
)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.runMigrations = async (CouchDB, migrations, options = {}) => {
|
||||||
|
console.log("Running migrations")
|
||||||
|
let tenantIds
|
||||||
|
if (environment.MULTI_TENANCY) {
|
||||||
|
if (!options.tenantIds || !options.tenantIds.length) {
|
||||||
|
// run for all tenants
|
||||||
|
tenantIds = await getTenantIds()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// single tenancy
|
||||||
|
tenantIds = [DEFAULT_TENANT_ID]
|
||||||
|
}
|
||||||
|
|
||||||
|
// for all tenants
|
||||||
|
for (const tenantId of tenantIds) {
|
||||||
|
// for all migrations
|
||||||
|
for (const migration of migrations) {
|
||||||
|
// run the migration
|
||||||
|
await doInTenant(tenantId, () =>
|
||||||
|
runMigration(CouchDB, migration, options)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("Migrations complete")
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
exports[`migrations should match snapshot 1`] = `
|
exports[`migrations should match snapshot 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"_id": "migrations",
|
"_id": "migrations",
|
||||||
"_rev": "1-af6c272fe081efafecd2ea49a8fcbb40",
|
"_rev": "1-6277abc4e3db950221768e5a2618a059",
|
||||||
"user_email_view_casing": 1487076708000,
|
"test": 1487076708000,
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
require("../../tests/utilities/dbConfig")
|
require("../../tests/utilities/dbConfig")
|
||||||
|
|
||||||
const { migrateIfRequired, MIGRATION_DBS, MIGRATIONS, getMigrationsDoc } = require("../index")
|
const { runMigrations, getMigrationsDoc } = require("../index")
|
||||||
const database = require("../../db")
|
const CouchDB = require("../../db").getCouch()
|
||||||
const {
|
const {
|
||||||
StaticDatabases,
|
StaticDatabases,
|
||||||
} = require("../../db/utils")
|
} = require("../../db/utils")
|
||||||
|
@ -13,8 +13,14 @@ describe("migrations", () => {
|
||||||
|
|
||||||
const migrationFunction = jest.fn()
|
const migrationFunction = jest.fn()
|
||||||
|
|
||||||
|
const MIGRATIONS = [{
|
||||||
|
type: "global",
|
||||||
|
name: "test",
|
||||||
|
fn: migrationFunction
|
||||||
|
}]
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
db = database.getDB(StaticDatabases.GLOBAL.name)
|
db = new CouchDB(StaticDatabases.GLOBAL.name)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
@ -22,39 +28,29 @@ describe("migrations", () => {
|
||||||
await db.destroy()
|
await db.destroy()
|
||||||
})
|
})
|
||||||
|
|
||||||
const validMigration = () => {
|
const migrate = () => {
|
||||||
return migrateIfRequired(MIGRATION_DBS.GLOBAL_DB, MIGRATIONS.USER_EMAIL_VIEW_CASING, migrationFunction)
|
return runMigrations(CouchDB, MIGRATIONS)
|
||||||
}
|
}
|
||||||
|
|
||||||
it("should run a new migration", async () => {
|
it("should run a new migration", async () => {
|
||||||
await validMigration()
|
await migrate()
|
||||||
expect(migrationFunction).toHaveBeenCalled()
|
expect(migrationFunction).toHaveBeenCalled()
|
||||||
|
const doc = await getMigrationsDoc(db)
|
||||||
|
expect(doc.test).toBeDefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should match snapshot", async () => {
|
it("should match snapshot", async () => {
|
||||||
await validMigration()
|
await migrate()
|
||||||
const doc = await getMigrationsDoc(db)
|
const doc = await getMigrationsDoc(db)
|
||||||
expect(doc).toMatchSnapshot()
|
expect(doc).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should skip a previously run migration", async () => {
|
it("should skip a previously run migration", async () => {
|
||||||
await validMigration()
|
await migrate()
|
||||||
await validMigration()
|
const previousMigrationTime = await getMigrationsDoc(db).test
|
||||||
|
await migrate()
|
||||||
|
const currentMigrationTime = await getMigrationsDoc(db).test
|
||||||
expect(migrationFunction).toHaveBeenCalledTimes(1)
|
expect(migrationFunction).toHaveBeenCalledTimes(1)
|
||||||
|
expect(currentMigrationTime).toBe(previousMigrationTime)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should reject an unknown migration name", async () => {
|
|
||||||
expect(async () => {
|
|
||||||
await migrateIfRequired(MIGRATION_DBS.GLOBAL_DB, "bogus_name", migrationFunction)
|
|
||||||
}).rejects.toThrow()
|
|
||||||
expect(migrationFunction).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should reject an unknown database name", async () => {
|
|
||||||
expect(async () => {
|
|
||||||
await migrateIfRequired("bogus_db", MIGRATIONS.USER_EMAIL_VIEW_CASING, migrationFunction)
|
|
||||||
}).rejects.toThrow()
|
|
||||||
expect(migrationFunction).not.toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
})
|
})
|
|
@ -1,4 +1,3 @@
|
||||||
const { getDB } = require("../db")
|
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
const { BUILTIN_PERMISSION_IDS } = require("./permissions")
|
const { BUILTIN_PERMISSION_IDS } = require("./permissions")
|
||||||
const {
|
const {
|
||||||
|
@ -7,6 +6,8 @@ const {
|
||||||
DocumentTypes,
|
DocumentTypes,
|
||||||
SEPARATOR,
|
SEPARATOR,
|
||||||
} = require("../db/utils")
|
} = require("../db/utils")
|
||||||
|
const { getAppDB } = require("../context")
|
||||||
|
const { getDB } = require("../db")
|
||||||
|
|
||||||
const BUILTIN_IDS = {
|
const BUILTIN_IDS = {
|
||||||
ADMIN: "ADMIN",
|
ADMIN: "ADMIN",
|
||||||
|
@ -111,11 +112,10 @@ exports.lowerBuiltinRoleID = (roleId1, roleId2) => {
|
||||||
/**
|
/**
|
||||||
* Gets the role object, this is mainly useful for two purposes, to check if the level exists and
|
* Gets the role object, this is mainly useful for two purposes, to check if the level exists and
|
||||||
* to check if the role inherits any others.
|
* to check if the role inherits any others.
|
||||||
* @param {string} appId The app in which to look for the role.
|
|
||||||
* @param {string|null} roleId The level ID to lookup.
|
* @param {string|null} roleId The level ID to lookup.
|
||||||
* @returns {Promise<Role|object|null>} The role object, which may contain an "inherits" property.
|
* @returns {Promise<Role|object|null>} The role object, which may contain an "inherits" property.
|
||||||
*/
|
*/
|
||||||
exports.getRole = async (appId, roleId) => {
|
exports.getRole = async roleId => {
|
||||||
if (!roleId) {
|
if (!roleId) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ exports.getRole = async (appId, roleId) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const db = getDB(appId)
|
const db = getAppDB()
|
||||||
const dbRole = await db.get(exports.getDBRoleID(roleId))
|
const dbRole = await db.get(exports.getDBRoleID(roleId))
|
||||||
role = Object.assign(role, dbRole)
|
role = Object.assign(role, dbRole)
|
||||||
// finalise the ID
|
// finalise the ID
|
||||||
|
@ -145,11 +145,11 @@ exports.getRole = async (appId, roleId) => {
|
||||||
/**
|
/**
|
||||||
* Simple function to get all the roles based on the top level user role ID.
|
* Simple function to get all the roles based on the top level user role ID.
|
||||||
*/
|
*/
|
||||||
async function getAllUserRoles(appId, userRoleId) {
|
async function getAllUserRoles(userRoleId) {
|
||||||
if (!userRoleId) {
|
if (!userRoleId) {
|
||||||
return [BUILTIN_IDS.BASIC]
|
return [BUILTIN_IDS.BASIC]
|
||||||
}
|
}
|
||||||
let currentRole = await exports.getRole(appId, userRoleId)
|
let currentRole = await exports.getRole(userRoleId)
|
||||||
let roles = currentRole ? [currentRole] : []
|
let roles = currentRole ? [currentRole] : []
|
||||||
let roleIds = [userRoleId]
|
let roleIds = [userRoleId]
|
||||||
// get all the inherited roles
|
// get all the inherited roles
|
||||||
|
@ -159,7 +159,7 @@ async function getAllUserRoles(appId, userRoleId) {
|
||||||
roleIds.indexOf(currentRole.inherits) === -1
|
roleIds.indexOf(currentRole.inherits) === -1
|
||||||
) {
|
) {
|
||||||
roleIds.push(currentRole.inherits)
|
roleIds.push(currentRole.inherits)
|
||||||
currentRole = await exports.getRole(appId, currentRole.inherits)
|
currentRole = await exports.getRole(currentRole.inherits)
|
||||||
roles.push(currentRole)
|
roles.push(currentRole)
|
||||||
}
|
}
|
||||||
return roles
|
return roles
|
||||||
|
@ -168,29 +168,23 @@ async function getAllUserRoles(appId, userRoleId) {
|
||||||
/**
|
/**
|
||||||
* Returns an ordered array of the user's inherited role IDs, this can be used
|
* Returns an ordered array of the user's inherited role IDs, this can be used
|
||||||
* to determine if a user can access something that requires a specific role.
|
* to determine if a user can access something that requires a specific role.
|
||||||
* @param {string} appId The ID of the application from which roles should be obtained.
|
|
||||||
* @param {string} userRoleId The user's role ID, this can be found in their access token.
|
* @param {string} userRoleId The user's role ID, this can be found in their access token.
|
||||||
* @param {object} opts Various options, such as whether to only retrieve the IDs (default true).
|
* @param {object} opts Various options, such as whether to only retrieve the IDs (default true).
|
||||||
* @returns {Promise<string[]>} returns an ordered array of the roles, with the first being their
|
* @returns {Promise<string[]>} returns an ordered array of the roles, with the first being their
|
||||||
* highest level of access and the last being the lowest level.
|
* highest level of access and the last being the lowest level.
|
||||||
*/
|
*/
|
||||||
exports.getUserRoleHierarchy = async (
|
exports.getUserRoleHierarchy = async (userRoleId, opts = { idOnly: true }) => {
|
||||||
appId,
|
|
||||||
userRoleId,
|
|
||||||
opts = { idOnly: true }
|
|
||||||
) => {
|
|
||||||
// special case, if they don't have a role then they are a public user
|
// special case, if they don't have a role then they are a public user
|
||||||
const roles = await getAllUserRoles(appId, userRoleId)
|
const roles = await getAllUserRoles(userRoleId)
|
||||||
return opts.idOnly ? roles.map(role => role._id) : roles
|
return opts.idOnly ? roles.map(role => role._id) : roles
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given an app ID this will retrieve all of the roles that are currently within that app.
|
* Given an app ID this will retrieve all of the roles that are currently within that app.
|
||||||
* @param {string} appId The ID of the app to retrieve the roles from.
|
|
||||||
* @return {Promise<object[]>} An array of the role objects that were found.
|
* @return {Promise<object[]>} An array of the role objects that were found.
|
||||||
*/
|
*/
|
||||||
exports.getAllRoles = async appId => {
|
exports.getAllRoles = async appId => {
|
||||||
const db = getDB(appId)
|
const db = appId ? getDB(appId) : getAppDB()
|
||||||
const body = await db.allDocs(
|
const body = await db.allDocs(
|
||||||
getRoleParams(null, {
|
getRoleParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
|
@ -218,19 +212,17 @@ exports.getAllRoles = async appId => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This retrieves the required role/
|
* This retrieves the required role
|
||||||
* @param appId
|
|
||||||
* @param permLevel
|
* @param permLevel
|
||||||
* @param resourceId
|
* @param resourceId
|
||||||
* @param subResourceId
|
* @param subResourceId
|
||||||
* @return {Promise<{permissions}|Object>}
|
* @return {Promise<{permissions}|Object>}
|
||||||
*/
|
*/
|
||||||
exports.getRequiredResourceRole = async (
|
exports.getRequiredResourceRole = async (
|
||||||
appId,
|
|
||||||
permLevel,
|
permLevel,
|
||||||
{ resourceId, subResourceId }
|
{ resourceId, subResourceId }
|
||||||
) => {
|
) => {
|
||||||
const roles = await exports.getAllRoles(appId)
|
const roles = await exports.getAllRoles()
|
||||||
let main = [],
|
let main = [],
|
||||||
sub = []
|
sub = []
|
||||||
for (let role of roles) {
|
for (let role of roles) {
|
||||||
|
@ -251,8 +243,7 @@ exports.getRequiredResourceRole = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccessController {
|
class AccessController {
|
||||||
constructor(appId) {
|
constructor() {
|
||||||
this.appId = appId
|
|
||||||
this.userHierarchies = {}
|
this.userHierarchies = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +261,7 @@ class AccessController {
|
||||||
}
|
}
|
||||||
let roleIds = this.userHierarchies[userRoleId]
|
let roleIds = this.userHierarchies[userRoleId]
|
||||||
if (!roleIds) {
|
if (!roleIds) {
|
||||||
roleIds = await exports.getUserRoleHierarchy(this.appId, userRoleId)
|
roleIds = await exports.getUserRoleHierarchy(userRoleId)
|
||||||
this.userHierarchies[userRoleId] = roleIds
|
this.userHierarchies[userRoleId] = roleIds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
const env = require("../environment")
|
|
||||||
const { Headers } = require("../../constants")
|
|
||||||
const cls = require("./FunctionContext")
|
|
||||||
|
|
||||||
exports.DEFAULT_TENANT_ID = "default"
|
|
||||||
|
|
||||||
exports.isDefaultTenant = () => {
|
|
||||||
return exports.getTenantId() === exports.DEFAULT_TENANT_ID
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.isMultiTenant = () => {
|
|
||||||
return env.MULTI_TENANCY
|
|
||||||
}
|
|
||||||
|
|
||||||
const TENANT_ID = "tenantId"
|
|
||||||
|
|
||||||
// used for automations, API endpoints should always be in context already
|
|
||||||
exports.doInTenant = (tenantId, task) => {
|
|
||||||
return cls.run(() => {
|
|
||||||
// set the tenant id
|
|
||||||
cls.setOnContext(TENANT_ID, tenantId)
|
|
||||||
|
|
||||||
// invoke the task
|
|
||||||
return task()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.updateTenantId = tenantId => {
|
|
||||||
cls.setOnContext(TENANT_ID, tenantId)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.setTenantId = (
|
|
||||||
ctx,
|
|
||||||
opts = { allowQs: false, allowNoTenant: false }
|
|
||||||
) => {
|
|
||||||
let tenantId
|
|
||||||
// exit early if not multi-tenant
|
|
||||||
if (!exports.isMultiTenant()) {
|
|
||||||
cls.setOnContext(TENANT_ID, this.DEFAULT_TENANT_ID)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const allowQs = opts && opts.allowQs
|
|
||||||
const allowNoTenant = opts && opts.allowNoTenant
|
|
||||||
const header = ctx.request.headers[Headers.TENANT_ID]
|
|
||||||
const user = ctx.user || {}
|
|
||||||
if (allowQs) {
|
|
||||||
const query = ctx.request.query || {}
|
|
||||||
tenantId = query.tenantId
|
|
||||||
}
|
|
||||||
// override query string (if allowed) by user, or header
|
|
||||||
// URL params cannot be used in a middleware, as they are
|
|
||||||
// processed later in the chain
|
|
||||||
tenantId = user.tenantId || header || tenantId
|
|
||||||
|
|
||||||
// Set the tenantId from the subdomain
|
|
||||||
if (!tenantId) {
|
|
||||||
tenantId = ctx.subdomains && ctx.subdomains[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tenantId && !allowNoTenant) {
|
|
||||||
ctx.throw(403, "Tenant id not set")
|
|
||||||
}
|
|
||||||
// check tenant ID just incase no tenant was allowed
|
|
||||||
if (tenantId) {
|
|
||||||
cls.setOnContext(TENANT_ID, tenantId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.isTenantIdSet = () => {
|
|
||||||
const tenantId = cls.getFromContext(TENANT_ID)
|
|
||||||
return !!tenantId
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.getTenantId = () => {
|
|
||||||
if (!exports.isMultiTenant()) {
|
|
||||||
return exports.DEFAULT_TENANT_ID
|
|
||||||
}
|
|
||||||
const tenantId = cls.getFromContext(TENANT_ID)
|
|
||||||
if (!tenantId) {
|
|
||||||
throw Error("Tenant id not found")
|
|
||||||
}
|
|
||||||
return tenantId
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
...require("./context"),
|
...require("../context"),
|
||||||
...require("./tenancy"),
|
...require("./tenancy"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const { getDB } = require("../db")
|
const { getDB } = require("../db")
|
||||||
const { SEPARATOR, StaticDatabases, DocumentTypes } = require("../db/constants")
|
const { SEPARATOR, StaticDatabases, DocumentTypes } = require("../db/constants")
|
||||||
const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("./context")
|
const { getTenantId, DEFAULT_TENANT_ID, isMultiTenant } = require("../context")
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
|
|
||||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||||
|
@ -148,3 +148,15 @@ exports.isUserInAppTenant = (appId, user = null) => {
|
||||||
const tenantId = exports.getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID
|
const tenantId = exports.getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID
|
||||||
return tenantId === userTenantId
|
return tenantId === userTenantId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.getTenantIds = async () => {
|
||||||
|
const db = getDB(PLATFORM_INFO_DB)
|
||||||
|
let tenants
|
||||||
|
try {
|
||||||
|
tenants = await db.get(TENANT_DOC)
|
||||||
|
} catch (err) {
|
||||||
|
// if theres an error the doc doesn't exist, no tenants exist
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
return (tenants && tenants.tenantIds) || []
|
||||||
|
}
|
||||||
|
|
|
@ -20,9 +20,6 @@ const { hash } = require("./hashing")
|
||||||
const userCache = require("./cache/user")
|
const userCache = require("./cache/user")
|
||||||
const env = require("./environment")
|
const env = require("./environment")
|
||||||
const { getUserSessions, invalidateSessions } = require("./security/sessions")
|
const { getUserSessions, invalidateSessions } = require("./security/sessions")
|
||||||
const { migrateIfRequired } = require("./migrations")
|
|
||||||
const { USER_EMAIL_VIEW_CASING } = require("./migrations").MIGRATIONS
|
|
||||||
const { GLOBAL_DB } = require("./migrations").MIGRATION_DBS
|
|
||||||
|
|
||||||
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
|
||||||
|
|
||||||
|
@ -144,11 +141,6 @@ exports.getGlobalUserByEmail = async email => {
|
||||||
}
|
}
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
|
|
||||||
await migrateIfRequired(GLOBAL_DB, USER_EMAIL_VIEW_CASING, async () => {
|
|
||||||
// re-create the view with latest changes
|
|
||||||
await createUserEmailView(db)
|
|
||||||
})
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let users = (
|
let users = (
|
||||||
await db.query(`database/${ViewNames.USER_BY_EMAIL}`, {
|
await db.query(`database/${ViewNames.USER_BY_EMAIL}`, {
|
||||||
|
|
|
@ -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.0.46-alpha.8",
|
"version": "1.0.49-alpha.5",
|
||||||
"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",
|
||||||
|
|
|
@ -6,11 +6,12 @@
|
||||||
export let label = null
|
export let label = null
|
||||||
export let labelPosition = "above"
|
export let labelPosition = "above"
|
||||||
export let error = null
|
export let error = null
|
||||||
|
export let tooltip = ""
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="spectrum-Form-item" class:above={labelPosition === "above"}>
|
<div class="spectrum-Form-item" class:above={labelPosition === "above"}>
|
||||||
{#if label}
|
{#if label}
|
||||||
<FieldLabel forId={id} {label} position={labelPosition} />
|
<FieldLabel forId={id} {label} position={labelPosition} {tooltip} />
|
||||||
{/if}
|
{/if}
|
||||||
<div class="spectrum-Form-itemField">
|
<div class="spectrum-Form-itemField">
|
||||||
<slot />
|
<slot />
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
<script>
|
<script>
|
||||||
|
import TooltipWrapper from "../Tooltip/TooltipWrapper.svelte"
|
||||||
|
|
||||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||||
|
|
||||||
export let forId
|
export let forId
|
||||||
export let label
|
export let label
|
||||||
export let position = "above"
|
export let position = "above"
|
||||||
|
export let tooltip = ""
|
||||||
|
|
||||||
$: className = position === "above" ? "" : `spectrum-FieldLabel--${position}`
|
$: className = position === "above" ? "" : `spectrum-FieldLabel--${position}`
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label
|
<TooltipWrapper {tooltip} size="S">
|
||||||
for={forId}
|
<label
|
||||||
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${className}`}
|
for={forId}
|
||||||
>
|
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${className}`}
|
||||||
{label || ""}
|
>
|
||||||
</label>
|
{label || ""}
|
||||||
|
</label>
|
||||||
|
</TooltipWrapper>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
label {
|
label {
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
export let autoWidth = false
|
export let autoWidth = false
|
||||||
export let sort = false
|
export let sort = false
|
||||||
|
export let tooltip = ""
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
const onChange = e => {
|
const onChange = e => {
|
||||||
|
@ -32,7 +33,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Field {label} {labelPosition} {error}>
|
<Field {label} {labelPosition} {error} {tooltip}>
|
||||||
<Select
|
<Select
|
||||||
{quiet}
|
{quiet}
|
||||||
{error}
|
{error}
|
||||||
|
|
|
@ -1,74 +1,20 @@
|
||||||
<script>
|
<script>
|
||||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||||
import Tooltip from "../Tooltip/Tooltip.svelte"
|
import TooltipWrapper from "../Tooltip/TooltipWrapper.svelte"
|
||||||
import Icon from "../Icon/Icon.svelte"
|
|
||||||
|
|
||||||
export let size = "M"
|
export let size = "M"
|
||||||
export let tooltip = ""
|
export let tooltip = ""
|
||||||
export let showTooltip = false
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if tooltip}
|
<TooltipWrapper {tooltip} {size}>
|
||||||
<div class="container">
|
|
||||||
<label
|
|
||||||
for=""
|
|
||||||
class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}
|
|
||||||
>
|
|
||||||
<slot />
|
|
||||||
</label>
|
|
||||||
<div class="icon-container">
|
|
||||||
<div
|
|
||||||
class="icon"
|
|
||||||
class:icon-small={size === "M" || size === "S"}
|
|
||||||
on:mouseover={() => (showTooltip = true)}
|
|
||||||
on:focus={() => (showTooltip = true)}
|
|
||||||
on:mouseleave={() => (showTooltip = false)}
|
|
||||||
>
|
|
||||||
<Icon name="InfoOutline" size="S" disabled={true} />
|
|
||||||
</div>
|
|
||||||
{#if showTooltip}
|
|
||||||
<div class="tooltip">
|
|
||||||
<Tooltip textWrapping={true} direction={"bottom"} text={tooltip} />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<label for="" class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}>
|
<label for="" class={`spectrum-FieldLabel spectrum-FieldLabel--size${size}`}>
|
||||||
<slot />
|
<slot />
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
</TooltipWrapper>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
label {
|
label {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.icon-container {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-top: 1px;
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
.tooltip {
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
top: 15px;
|
|
||||||
z-index: 1;
|
|
||||||
width: 160px;
|
|
||||||
}
|
|
||||||
.icon {
|
|
||||||
transform: scale(0.75);
|
|
||||||
}
|
|
||||||
.icon-small {
|
|
||||||
margin-top: -2px;
|
|
||||||
margin-bottom: -5px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script>
|
||||||
|
import Tooltip from "./Tooltip.svelte"
|
||||||
|
import Icon from "../Icon/Icon.svelte"
|
||||||
|
|
||||||
|
export let tooltip = ""
|
||||||
|
export let size = "M"
|
||||||
|
|
||||||
|
let showTooltip = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class:container={!!tooltip}>
|
||||||
|
<slot />
|
||||||
|
{#if tooltip}
|
||||||
|
<div class="icon-container">
|
||||||
|
<div
|
||||||
|
class="icon"
|
||||||
|
class:icon-small={size === "M" || size === "S"}
|
||||||
|
on:mouseover={() => (showTooltip = true)}
|
||||||
|
on:mouseleave={() => (showTooltip = false)}
|
||||||
|
>
|
||||||
|
<Icon name="InfoOutline" size="S" disabled={true} />
|
||||||
|
</div>
|
||||||
|
{#if showTooltip}
|
||||||
|
<div class="tooltip">
|
||||||
|
<Tooltip textWrapping={true} direction={"bottom"} text={tooltip} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.icon-container {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 1px;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.tooltip {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
top: 15px;
|
||||||
|
z-index: 1;
|
||||||
|
width: 160px;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
transform: scale(0.75);
|
||||||
|
}
|
||||||
|
.icon-small {
|
||||||
|
margin-top: -2px;
|
||||||
|
margin-bottom: -5px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -3,9 +3,6 @@ const path = require("path")
|
||||||
|
|
||||||
const tmpdir = path.join(require("os").tmpdir(), ".budibase")
|
const tmpdir = path.join(require("os").tmpdir(), ".budibase")
|
||||||
|
|
||||||
// these run on ports we don't normally use so that they can run alongside the
|
|
||||||
const fs = require("fs")
|
|
||||||
|
|
||||||
// normal development system
|
// normal development system
|
||||||
const WORKER_PORT = "10002"
|
const WORKER_PORT = "10002"
|
||||||
const MAIN_PORT = cypressConfig.env.PORT
|
const MAIN_PORT = cypressConfig.env.PORT
|
||||||
|
@ -29,22 +26,20 @@ process.env.ALLOW_DEV_AUTOMATIONS = 1
|
||||||
// Stop info logs polluting test outputs
|
// Stop info logs polluting test outputs
|
||||||
process.env.LOG_LEVEL = "error"
|
process.env.LOG_LEVEL = "error"
|
||||||
|
|
||||||
async function run() {
|
exports.run = (
|
||||||
|
serverLoc = "../../server/dist",
|
||||||
|
workerLoc = "../../worker/dist"
|
||||||
|
) => {
|
||||||
// require("dotenv").config({ path: resolve(dir, ".env") })
|
// require("dotenv").config({ path: resolve(dir, ".env") })
|
||||||
if (!fs.existsSync("../server/dist")) {
|
|
||||||
console.error("Unable to run cypress, need to build server first")
|
|
||||||
process.exit(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// don't make this a variable or top level require
|
// don't make this a variable or top level require
|
||||||
// it will cause environment module to be loaded prematurely
|
// it will cause environment module to be loaded prematurely
|
||||||
const server = require("../../server/dist/app")
|
require(serverLoc)
|
||||||
process.env.PORT = WORKER_PORT
|
process.env.PORT = WORKER_PORT
|
||||||
const worker = require("../../worker/src/index")
|
require(workerLoc)
|
||||||
// reload main port for rest of system
|
// reload main port for rest of system
|
||||||
process.env.PORT = MAIN_PORT
|
process.env.PORT = MAIN_PORT
|
||||||
server.on("close", () => console.log("Server Closed"))
|
|
||||||
worker.on("close", () => console.log("Worker Closed"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
run()
|
if (require.main === module) {
|
||||||
|
exports.run()
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
// @ts-ignore
|
||||||
|
import { run } from "../setup"
|
||||||
|
|
||||||
|
run("../../server/src/index", "../../worker/src/index")
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "1.0.46-alpha.8",
|
"version": "1.0.49-alpha.5",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -9,12 +9,13 @@
|
||||||
"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",
|
||||||
"cy:setup": "node ./cypress/setup.js",
|
"cy:setup": "ts-node ./cypress/ts/setup.ts",
|
||||||
|
"cy:setup:ci": "node ./cypress/setup.js",
|
||||||
"cy:run": "cypress run",
|
"cy:run": "cypress run",
|
||||||
"cy:open": "cypress open",
|
"cy:open": "cypress open",
|
||||||
"cy:run:ci": "cypress run --record",
|
"cy:run:ci": "cypress run --record",
|
||||||
"cy:test": "start-server-and-test cy:setup http://localhost:10001/builder cy:run",
|
"cy:test": "start-server-and-test cy:setup http://localhost:10001/builder cy:run",
|
||||||
"cy:ci": "start-server-and-test cy:setup http://localhost:10001/builder cy:run",
|
"cy:ci": "start-server-and-test cy:setup:ci http://localhost:10001/builder cy:run",
|
||||||
"cy:debug": "start-server-and-test cy:setup http://localhost:10001/builder cy:open"
|
"cy:debug": "start-server-and-test cy:setup http://localhost:10001/builder cy:open"
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
|
@ -63,11 +64,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.0.46-alpha.8",
|
"@budibase/bbui": "^1.0.49-alpha.5",
|
||||||
"@budibase/client": "^1.0.46-alpha.8",
|
"@budibase/client": "^1.0.49-alpha.5",
|
||||||
"@budibase/frontend-core": "^1.0.46-alpha.8",
|
"@budibase/frontend-core": "^1.0.49-alpha.5",
|
||||||
"@budibase/colorpicker": "1.1.2",
|
"@budibase/colorpicker": "1.1.2",
|
||||||
"@budibase/string-templates": "^1.0.46-alpha.8",
|
"@budibase/string-templates": "^1.0.49-alpha.5",
|
||||||
"@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",
|
||||||
|
@ -105,6 +106,8 @@
|
||||||
"start-server-and-test": "^1.12.1",
|
"start-server-and-test": "^1.12.1",
|
||||||
"svelte": "^3.38.2",
|
"svelte": "^3.38.2",
|
||||||
"svelte-jester": "^1.3.2",
|
"svelte-jester": "^1.3.2",
|
||||||
|
"ts-node": "^10.4.0",
|
||||||
|
"typescript": "^4.5.5",
|
||||||
"vite": "^2.1.5"
|
"vite": "^2.1.5"
|
||||||
},
|
},
|
||||||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
||||||
|
|
|
@ -22,8 +22,10 @@
|
||||||
RelationshipTypes,
|
RelationshipTypes,
|
||||||
ALLOWABLE_STRING_OPTIONS,
|
ALLOWABLE_STRING_OPTIONS,
|
||||||
ALLOWABLE_NUMBER_OPTIONS,
|
ALLOWABLE_NUMBER_OPTIONS,
|
||||||
|
ALLOWABLE_JSON_OPTIONS,
|
||||||
ALLOWABLE_STRING_TYPES,
|
ALLOWABLE_STRING_TYPES,
|
||||||
ALLOWABLE_NUMBER_TYPES,
|
ALLOWABLE_NUMBER_TYPES,
|
||||||
|
ALLOWABLE_JSON_TYPES,
|
||||||
SWITCHABLE_TYPES,
|
SWITCHABLE_TYPES,
|
||||||
} from "constants/backend"
|
} from "constants/backend"
|
||||||
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
|
||||||
|
@ -154,6 +156,7 @@
|
||||||
delete field.subtype
|
delete field.subtype
|
||||||
delete field.tableId
|
delete field.tableId
|
||||||
delete field.relationshipType
|
delete field.relationshipType
|
||||||
|
delete field.formulaType
|
||||||
|
|
||||||
// Add in defaults and initial definition
|
// Add in defaults and initial definition
|
||||||
const definition = fieldDefinitions[event.detail?.toUpperCase()]
|
const definition = fieldDefinitions[event.detail?.toUpperCase()]
|
||||||
|
@ -165,6 +168,9 @@
|
||||||
if (field.type === LINK_TYPE) {
|
if (field.type === LINK_TYPE) {
|
||||||
field.relationshipType = RelationshipTypes.MANY_TO_MANY
|
field.relationshipType = RelationshipTypes.MANY_TO_MANY
|
||||||
}
|
}
|
||||||
|
if (field.type === FORMULA_TYPE) {
|
||||||
|
field.formulaType = "dynamic"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onChangeRequired(e) {
|
function onChangeRequired(e) {
|
||||||
|
@ -245,6 +251,11 @@
|
||||||
ALLOWABLE_NUMBER_TYPES.indexOf(field.type) !== -1
|
ALLOWABLE_NUMBER_TYPES.indexOf(field.type) !== -1
|
||||||
) {
|
) {
|
||||||
return ALLOWABLE_NUMBER_OPTIONS
|
return ALLOWABLE_NUMBER_OPTIONS
|
||||||
|
} else if (
|
||||||
|
originalName &&
|
||||||
|
ALLOWABLE_JSON_TYPES.indexOf(field.type) !== -1
|
||||||
|
) {
|
||||||
|
return ALLOWABLE_JSON_OPTIONS
|
||||||
} else if (!external) {
|
} else if (!external) {
|
||||||
return [
|
return [
|
||||||
...Object.values(fieldDefinitions),
|
...Object.values(fieldDefinitions),
|
||||||
|
@ -435,8 +446,22 @@
|
||||||
error={errors.relatedName}
|
error={errors.relatedName}
|
||||||
/>
|
/>
|
||||||
{:else if field.type === FORMULA_TYPE}
|
{:else if field.type === FORMULA_TYPE}
|
||||||
|
{#if !table.sql}
|
||||||
|
<Select
|
||||||
|
label="Formula type"
|
||||||
|
bind:value={field.formulaType}
|
||||||
|
options={[
|
||||||
|
{ label: "Dynamic", value: "dynamic" },
|
||||||
|
{ label: "Static", value: "static" },
|
||||||
|
]}
|
||||||
|
getOptionLabel={option => option.label}
|
||||||
|
getOptionValue={option => option.value}
|
||||||
|
tooltip="Dynamic formula are calculated when retrieved, but cannot be filtered,
|
||||||
|
while static formula are calculated when the row is saved."
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
<ModalBindableInput
|
<ModalBindableInput
|
||||||
title="Handlebars Formula"
|
title="Formula"
|
||||||
label="Formula"
|
label="Formula"
|
||||||
value={field.formula}
|
value={field.formula}
|
||||||
on:change={e => (field.formula = e.detail)}
|
on:change={e => (field.formula = e.detail)}
|
||||||
|
@ -445,7 +470,7 @@
|
||||||
/>
|
/>
|
||||||
{:else if field.type === AUTO_TYPE}
|
{:else if field.type === AUTO_TYPE}
|
||||||
<Select
|
<Select
|
||||||
label="Auto Column Type"
|
label="Auto column type"
|
||||||
value={field.subtype}
|
value={field.subtype}
|
||||||
on:change={e => (field.subtype = e.detail)}
|
on:change={e => (field.subtype = e.detail)}
|
||||||
options={Object.entries(getAutoColumnInformation())}
|
options={Object.entries(getAutoColumnInformation())}
|
||||||
|
|
|
@ -132,7 +132,7 @@
|
||||||
{bindings}
|
{bindings}
|
||||||
on:change={event => (filter.value = event.detail)}
|
on:change={event => (filter.value = event.detail)}
|
||||||
/>
|
/>
|
||||||
{:else if ["string", "longform", "number"].includes(filter.type)}
|
{:else if ["string", "longform", "number", "formula"].includes(filter.type)}
|
||||||
<Input disabled={filter.noValue} bind:value={filter.value} />
|
<Input disabled={filter.noValue} bind:value={filter.value} />
|
||||||
{:else if ["options", "array"].includes(filter.type)}
|
{:else if ["options", "array"].includes(filter.type)}
|
||||||
<Combobox
|
<Combobox
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
import { Label, notifications, Select } from "@budibase/bbui"
|
import { Label, notifications, Select } from "@budibase/bbui"
|
||||||
import { permissions, roles } from "stores/backend"
|
import { permissions, roles } from "stores/backend"
|
||||||
import { onMount } from "svelte"
|
|
||||||
import { Roles } from "constants/backend"
|
import { Roles } from "constants/backend"
|
||||||
|
|
||||||
export let query
|
export let query
|
||||||
export let saveId
|
|
||||||
export let label
|
export let label
|
||||||
|
|
||||||
$: updateRole(roleId, saveId)
|
$: getPermissions(query)
|
||||||
|
|
||||||
let roleId, loaded
|
let roleId, loaded, fetched
|
||||||
|
|
||||||
async function updateRole(role, id) {
|
async function updateRole(role, id) {
|
||||||
try {
|
try {
|
||||||
|
@ -30,19 +28,23 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMount(async () => {
|
async function getPermissions(queryToFetch) {
|
||||||
if (!query || !query._id) {
|
if (fetched?._id === queryToFetch?._id) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fetched = queryToFetch
|
||||||
|
if (!queryToFetch || !queryToFetch._id) {
|
||||||
roleId = Roles.BASIC
|
roleId = Roles.BASIC
|
||||||
loaded = true
|
loaded = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
roleId = (await permissions.forResource(query._id))["read"]
|
roleId = (await permissions.forResource(queryToFetch._id))["read"]
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
roleId = Roles.BASIC
|
roleId = Roles.BASIC
|
||||||
}
|
}
|
||||||
loaded = true
|
loaded = true
|
||||||
})
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if loaded}
|
{#if loaded}
|
||||||
|
|
|
@ -148,20 +148,23 @@ export const RelationshipTypes = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ALLOWABLE_STRING_OPTIONS = [FIELDS.STRING, FIELDS.OPTIONS]
|
export const ALLOWABLE_STRING_OPTIONS = [FIELDS.STRING, FIELDS.OPTIONS]
|
||||||
|
|
||||||
export const ALLOWABLE_STRING_TYPES = ALLOWABLE_STRING_OPTIONS.map(
|
export const ALLOWABLE_STRING_TYPES = ALLOWABLE_STRING_OPTIONS.map(
|
||||||
opt => opt.type
|
opt => opt.type
|
||||||
)
|
)
|
||||||
|
|
||||||
export const ALLOWABLE_NUMBER_OPTIONS = [FIELDS.NUMBER, FIELDS.BOOLEAN]
|
export const ALLOWABLE_NUMBER_OPTIONS = [FIELDS.NUMBER, FIELDS.BOOLEAN]
|
||||||
|
|
||||||
export const ALLOWABLE_NUMBER_TYPES = ALLOWABLE_NUMBER_OPTIONS.map(
|
export const ALLOWABLE_NUMBER_TYPES = ALLOWABLE_NUMBER_OPTIONS.map(
|
||||||
opt => opt.type
|
opt => opt.type
|
||||||
)
|
)
|
||||||
|
|
||||||
export const SWITCHABLE_TYPES = ALLOWABLE_NUMBER_TYPES.concat(
|
export const ALLOWABLE_JSON_OPTIONS = [FIELDS.JSON, FIELDS.ARRAY]
|
||||||
ALLOWABLE_STRING_TYPES
|
export const ALLOWABLE_JSON_TYPES = ALLOWABLE_JSON_OPTIONS.map(opt => opt.type)
|
||||||
)
|
|
||||||
|
export const SWITCHABLE_TYPES = [
|
||||||
|
...ALLOWABLE_STRING_TYPES,
|
||||||
|
...ALLOWABLE_NUMBER_TYPES,
|
||||||
|
...ALLOWABLE_JSON_TYPES,
|
||||||
|
]
|
||||||
|
|
||||||
export const IntegrationTypes = {
|
export const IntegrationTypes = {
|
||||||
POSTGRES: "POSTGRES",
|
POSTGRES: "POSTGRES",
|
||||||
|
|
|
@ -27,5 +27,8 @@ export function getFields(fields, { allowLinks } = { allowLinks: true }) {
|
||||||
filteredFields = filteredFields.concat(getTableFields(linkField))
|
filteredFields = filteredFields.concat(getTableFields(linkField))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return filteredFields
|
const staticFormulaFields = fields.filter(
|
||||||
|
field => field.type === "formula" && field.formulaType === "static"
|
||||||
|
)
|
||||||
|
return filteredFields.concat(staticFormulaFields)
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
await auth.checkAuth()
|
await auth.getSelf()
|
||||||
await admin.init()
|
await admin.init()
|
||||||
|
|
||||||
// Set init info if present
|
// Set init info if present
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
await auth.checkAuth()
|
await auth.getSelf()
|
||||||
await organisation.init()
|
await organisation.init()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notifications.error("Error getting org config")
|
notifications.error("Error getting org config")
|
||||||
|
|
|
@ -315,6 +315,14 @@
|
||||||
}}
|
}}
|
||||||
class="template-card"
|
class="template-card"
|
||||||
>
|
>
|
||||||
|
<a
|
||||||
|
href={item.url}
|
||||||
|
target="_blank"
|
||||||
|
class="external-link"
|
||||||
|
on:click|stopPropagation
|
||||||
|
>
|
||||||
|
<Icon name="LinkOut" size="S" />
|
||||||
|
</a>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div style="color: {item.background}" class="iconAlign">
|
<div style="color: {item.background}" class="iconAlign">
|
||||||
<svg
|
<svg
|
||||||
|
@ -496,6 +504,7 @@
|
||||||
border: 1px solid var(--spectrum-global-color-gray-300);
|
border: 1px solid var(--spectrum-global-color-gray-300);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.template-card:hover {
|
.template-card:hover {
|
||||||
|
@ -506,6 +515,18 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.external-link {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
right: 5px;
|
||||||
|
color: var(--spectrum-global-color-gray-300);
|
||||||
|
z-index: 99;
|
||||||
|
}
|
||||||
|
.external-link:hover {
|
||||||
|
color: var(--spectrum-global-color-gray-500);
|
||||||
|
}
|
||||||
|
|
||||||
.iconAlign {
|
.iconAlign {
|
||||||
padding: 0 0 0 var(--spacing-m);
|
padding: 0 0 0 var(--spacing-m);
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
@ -114,11 +114,7 @@ export function createAuthStore() {
|
||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const actions = {
|
||||||
subscribe: store.subscribe,
|
|
||||||
setOrganisation,
|
|
||||||
getInitInfo,
|
|
||||||
setInitInfo,
|
|
||||||
checkQueryString: async () => {
|
checkQueryString: async () => {
|
||||||
const urlParams = new URLSearchParams(window.location.search)
|
const urlParams = new URLSearchParams(window.location.search)
|
||||||
if (urlParams.has("tenantId")) {
|
if (urlParams.has("tenantId")) {
|
||||||
|
@ -129,7 +125,7 @@ export function createAuthStore() {
|
||||||
setOrg: async tenantId => {
|
setOrg: async tenantId => {
|
||||||
await setOrganisation(tenantId)
|
await setOrganisation(tenantId)
|
||||||
},
|
},
|
||||||
checkAuth: async () => {
|
getSelf: async () => {
|
||||||
// We need to catch this locally as we never want this to fail, even
|
// We need to catch this locally as we never want this to fail, even
|
||||||
// though normally we never want to swallow API errors at the store level.
|
// though normally we never want to swallow API errors at the store level.
|
||||||
// We're either logged in or we aren't.
|
// We're either logged in or we aren't.
|
||||||
|
@ -143,12 +139,12 @@ export function createAuthStore() {
|
||||||
},
|
},
|
||||||
login: async creds => {
|
login: async creds => {
|
||||||
const tenantId = get(store).tenantId
|
const tenantId = get(store).tenantId
|
||||||
const response = await API.logIn({
|
await API.logIn({
|
||||||
username: creds.username,
|
username: creds.username,
|
||||||
password: creds.password,
|
password: creds.password,
|
||||||
tenantId,
|
tenantId,
|
||||||
})
|
})
|
||||||
setUser(response.user)
|
await actions.getSelf()
|
||||||
},
|
},
|
||||||
logout: async () => {
|
logout: async () => {
|
||||||
setUser(null)
|
setUser(null)
|
||||||
|
@ -177,6 +173,14 @@ export function createAuthStore() {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
subscribe: store.subscribe,
|
||||||
|
setOrganisation,
|
||||||
|
getInitInfo,
|
||||||
|
setInitInfo,
|
||||||
|
...actions,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const auth = createAuthStore()
|
export const auth = createAuthStore()
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"lib": ["es2019"],
|
||||||
|
"allowJs": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"incremental": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src/**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"**/*.json",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.spec.js"
|
||||||
|
]
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "1.0.46-alpha.8",
|
"version": "1.0.49-alpha.5",
|
||||||
"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": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "1.0.46-alpha.8",
|
"version": "1.0.49-alpha.5",
|
||||||
"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.0.46-alpha.8",
|
"@budibase/bbui": "^1.0.49-alpha.5",
|
||||||
"@budibase/frontend-core": "^1.0.46-alpha.8",
|
"@budibase/frontend-core": "^1.0.49-alpha.5",
|
||||||
"@budibase/string-templates": "^1.0.46-alpha.8",
|
"@budibase/string-templates": "^1.0.49-alpha.5",
|
||||||
"@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",
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
number: "numberfield",
|
number: "numberfield",
|
||||||
datetime: "datetimefield",
|
datetime: "datetimefield",
|
||||||
boolean: "booleanfield",
|
boolean: "booleanfield",
|
||||||
|
formula: "stringfield",
|
||||||
}
|
}
|
||||||
|
|
||||||
let formId
|
let formId
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
number: "numberfield",
|
number: "numberfield",
|
||||||
datetime: "datetimefield",
|
datetime: "datetimefield",
|
||||||
boolean: "booleanfield",
|
boolean: "booleanfield",
|
||||||
|
formula: "stringfield",
|
||||||
}
|
}
|
||||||
|
|
||||||
let formId
|
let formId
|
||||||
|
@ -60,10 +61,11 @@
|
||||||
let enrichedFilter = [...(filter || [])]
|
let enrichedFilter = [...(filter || [])]
|
||||||
columns?.forEach(column => {
|
columns?.forEach(column => {
|
||||||
const safePath = column.name.split(".").map(safe).join(".")
|
const safePath = column.name.split(".").map(safe).join(".")
|
||||||
|
const stringType = column.type === "string" || column.type === "formula"
|
||||||
enrichedFilter.push({
|
enrichedFilter.push({
|
||||||
field: column.name,
|
field: column.name,
|
||||||
operator: column.type === "string" ? "string" : "equal",
|
|
||||||
type: column.type,
|
type: column.type,
|
||||||
|
operator: stringType ? "string" : "equal",
|
||||||
valueType: "Binding",
|
valueType: "Binding",
|
||||||
value: `{{ ${safe(formId)}.${safePath} }}`,
|
value: `{{ ${safe(formId)}.${safePath} }}`,
|
||||||
})
|
})
|
||||||
|
|
|
@ -16,10 +16,14 @@
|
||||||
export let schemaFields
|
export let schemaFields
|
||||||
export let filters = []
|
export let filters = []
|
||||||
|
|
||||||
const BannedTypes = ["link", "attachment", "formula", "json"]
|
const BannedTypes = ["link", "attachment", "json"]
|
||||||
|
|
||||||
$: fieldOptions = (schemaFields ?? [])
|
$: fieldOptions = (schemaFields ?? [])
|
||||||
.filter(field => !BannedTypes.includes(field.type))
|
.filter(
|
||||||
|
field =>
|
||||||
|
!BannedTypes.includes(field.type) ||
|
||||||
|
(field.type === "formula" && field.formulaType === "static")
|
||||||
|
)
|
||||||
.map(field => field.name)
|
.map(field => field.name)
|
||||||
|
|
||||||
const addFilter = () => {
|
const addFilter = () => {
|
||||||
|
@ -112,7 +116,7 @@
|
||||||
on:change={e => onOperatorChange(filter, e.detail)}
|
on:change={e => onOperatorChange(filter, e.detail)}
|
||||||
placeholder={null}
|
placeholder={null}
|
||||||
/>
|
/>
|
||||||
{#if ["string", "longform", "number"].includes(filter.type)}
|
{#if ["string", "longform", "number", "formula"].includes(filter.type)}
|
||||||
<Input disabled={filter.noValue} bind:value={filter.value} />
|
<Input disabled={filter.noValue} bind:value={filter.value} />
|
||||||
{:else if ["options", "array"].includes(filter.type)}
|
{:else if ["options", "array"].includes(filter.type)}
|
||||||
<Combobox
|
<Combobox
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
validation,
|
validation,
|
||||||
formStep
|
formStep
|
||||||
)
|
)
|
||||||
|
$: schemaType = fieldSchema?.type !== "formula" ? fieldSchema?.type : "string"
|
||||||
|
|
||||||
// Focus label when editing
|
// Focus label when editing
|
||||||
let labelNode
|
let labelNode
|
||||||
|
@ -72,7 +73,7 @@
|
||||||
<Placeholder
|
<Placeholder
|
||||||
text="Add the Field setting to start using your component"
|
text="Add the Field setting to start using your component"
|
||||||
/>
|
/>
|
||||||
{:else if fieldSchema?.type && fieldSchema?.type !== type && type !== "options"}
|
{:else if schemaType && schemaType !== type && type !== "options"}
|
||||||
<Placeholder
|
<Placeholder
|
||||||
text="This Field setting is the wrong data type for this component"
|
text="This Field setting is the wrong data type for this component"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "1.0.46-alpha.8",
|
"version": "1.0.49-alpha.5",
|
||||||
"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.0.46-alpha.8",
|
"@budibase/bbui": "^1.0.49-alpha.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,14 @@ export default class QueryFetch extends DataFetch {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return await this.API.fetchQueryDefinition(datasource._id)
|
const definition = await this.API.fetchQueryDefinition(datasource._id)
|
||||||
|
// After getting the definition of query, it loses "fields" attribute
|
||||||
|
// because of security reason from the server. However, this attribute
|
||||||
|
// needs to be inside the definition for pagination.
|
||||||
|
if (!definition.fields) {
|
||||||
|
definition.fields = datasource.fields
|
||||||
|
}
|
||||||
|
return definition
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,24 +7,26 @@ import { OperatorOptions } from "../constants"
|
||||||
*/
|
*/
|
||||||
export const getValidOperatorsForType = type => {
|
export const getValidOperatorsForType = type => {
|
||||||
const Op = OperatorOptions
|
const Op = OperatorOptions
|
||||||
|
const stringOps = [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.StartsWith,
|
||||||
|
Op.Like,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
]
|
||||||
|
const numOps = [
|
||||||
|
Op.Equals,
|
||||||
|
Op.NotEquals,
|
||||||
|
Op.MoreThan,
|
||||||
|
Op.LessThan,
|
||||||
|
Op.Empty,
|
||||||
|
Op.NotEmpty,
|
||||||
|
]
|
||||||
if (type === "string") {
|
if (type === "string") {
|
||||||
return [
|
return stringOps
|
||||||
Op.Equals,
|
|
||||||
Op.NotEquals,
|
|
||||||
Op.StartsWith,
|
|
||||||
Op.Like,
|
|
||||||
Op.Empty,
|
|
||||||
Op.NotEmpty,
|
|
||||||
]
|
|
||||||
} else if (type === "number") {
|
} else if (type === "number") {
|
||||||
return [
|
return numOps
|
||||||
Op.Equals,
|
|
||||||
Op.NotEquals,
|
|
||||||
Op.MoreThan,
|
|
||||||
Op.LessThan,
|
|
||||||
Op.Empty,
|
|
||||||
Op.NotEmpty,
|
|
||||||
]
|
|
||||||
} else if (type === "options") {
|
} else if (type === "options") {
|
||||||
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
||||||
} else if (type === "array") {
|
} else if (type === "array") {
|
||||||
|
@ -32,23 +34,11 @@ export const getValidOperatorsForType = type => {
|
||||||
} else if (type === "boolean") {
|
} else if (type === "boolean") {
|
||||||
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty]
|
||||||
} else if (type === "longform") {
|
} else if (type === "longform") {
|
||||||
return [
|
return stringOps
|
||||||
Op.Equals,
|
|
||||||
Op.NotEquals,
|
|
||||||
Op.StartsWith,
|
|
||||||
Op.Like,
|
|
||||||
Op.Empty,
|
|
||||||
Op.NotEmpty,
|
|
||||||
]
|
|
||||||
} else if (type === "datetime") {
|
} else if (type === "datetime") {
|
||||||
return [
|
return numOps
|
||||||
Op.Equals,
|
} else if (type === "formula") {
|
||||||
Op.NotEquals,
|
return stringOps.concat([Op.MoreThan, Op.LessThan])
|
||||||
Op.MoreThan,
|
|
||||||
Op.LessThan,
|
|
||||||
Op.Empty,
|
|
||||||
Op.NotEmpty,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ module ArangoMock {
|
||||||
this.close = jest.fn()
|
this.close = jest.fn()
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
arangodb.aql = (strings, ...args) => {
|
arangodb.aql = (strings, ...args) => {
|
||||||
let str = strings.join("{}")
|
let str = strings.join("{}")
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
module AwsMock {
|
module AwsMock {
|
||||||
const aws: any = {}
|
const aws: any = {}
|
||||||
|
|
||||||
const response = (body: any) => () => ({promise: () => body})
|
const response = (body: any) => () => ({ promise: () => body })
|
||||||
|
|
||||||
function DocumentClient() {
|
function DocumentClient() {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -39,9 +39,9 @@ module AwsMock {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
aws.DynamoDB = {DocumentClient}
|
aws.DynamoDB = { DocumentClient }
|
||||||
aws.S3 = S3
|
aws.S3 = S3
|
||||||
aws.config = {update: jest.fn()}
|
aws.config = { update: jest.fn() }
|
||||||
|
|
||||||
module.exports = aws
|
module.exports = aws
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,15 +5,14 @@ module MongoMock {
|
||||||
this.connect = jest.fn()
|
this.connect = jest.fn()
|
||||||
this.close = jest.fn()
|
this.close = jest.fn()
|
||||||
this.insertOne = jest.fn()
|
this.insertOne = jest.fn()
|
||||||
this.insertMany = jest.fn(() => ({toArray: () => []}))
|
this.insertMany = jest.fn(() => ({ toArray: () => [] }))
|
||||||
this.find = jest.fn(() => ({toArray: () => []}))
|
this.find = jest.fn(() => ({ toArray: () => [] }))
|
||||||
this.findOne = jest.fn()
|
this.findOne = jest.fn()
|
||||||
this.count = jest.fn()
|
this.count = jest.fn()
|
||||||
this.deleteOne = jest.fn()
|
this.deleteOne = jest.fn()
|
||||||
this.deleteMany = jest.fn(() => ({toArray: () => []}))
|
this.deleteMany = jest.fn(() => ({ toArray: () => [] }))
|
||||||
this.updateOne = jest.fn()
|
this.updateOne = jest.fn()
|
||||||
this.updateMany = jest.fn(() => ({toArray: () => []}))
|
this.updateMany = jest.fn(() => ({ toArray: () => [] }))
|
||||||
|
|
||||||
|
|
||||||
this.collection = jest.fn(() => ({
|
this.collection = jest.fn(() => ({
|
||||||
insertOne: this.insertOne,
|
insertOne: this.insertOne,
|
||||||
|
|
|
@ -10,15 +10,15 @@ module MsSqlMock {
|
||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// mssql.connect = jest.fn(() => ({ recordset: [] }))
|
// mssql.connect = jest.fn(() => ({ recordset: [] }))
|
||||||
|
|
||||||
mssql.ConnectionPool = jest.fn(() => ({
|
mssql.ConnectionPool = jest.fn(() => ({
|
||||||
connect: jest.fn(() => ({
|
connect: jest.fn(() => ({
|
||||||
request: jest.fn(() => ({
|
request: jest.fn(() => ({
|
||||||
query: jest.fn(sql => ({ recordset: [ sql ] })),
|
query: jest.fn(sql => ({ recordset: [sql] })),
|
||||||
})),
|
})),
|
||||||
})),
|
})),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
module.exports = mssql
|
module.exports = mssql
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,8 @@ module FetchMock {
|
||||||
return json({
|
return json({
|
||||||
url,
|
url,
|
||||||
opts,
|
opts,
|
||||||
value: "<!doctype html><html itemscope=\"\" itemtype=\"http://schema.org/WebPage\" lang=\"en-GB\"></html>",
|
value:
|
||||||
|
'<!doctype html><html itemscope="" itemtype="http://schema.org/WebPage" lang="en-GB"></html>',
|
||||||
})
|
})
|
||||||
} else if (url.includes("failonce.com")) {
|
} else if (url.includes("failonce.com")) {
|
||||||
failCount++
|
failCount++
|
||||||
|
|
|
@ -10,16 +10,14 @@ module PgMock {
|
||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// constructor
|
// constructor
|
||||||
function Client() {
|
function Client() {}
|
||||||
}
|
|
||||||
|
|
||||||
Client.prototype.query = query
|
Client.prototype.query = query
|
||||||
Client.prototype.connect = jest.fn()
|
Client.prototype.connect = jest.fn()
|
||||||
Client.prototype.release = jest.fn()
|
Client.prototype.release = jest.fn()
|
||||||
|
|
||||||
function Pool() {
|
function Pool() {}
|
||||||
}
|
|
||||||
|
|
||||||
const on = jest.fn()
|
const on = jest.fn()
|
||||||
Pool.prototype.query = query
|
Pool.prototype.query = query
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "1.0.46-alpha.8",
|
"version": "1.0.49-alpha.5",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -70,9 +70,9 @@
|
||||||
"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.0.46-alpha.8",
|
"@budibase/backend-core": "^1.0.49-alpha.5",
|
||||||
"@budibase/client": "^1.0.46-alpha.8",
|
"@budibase/client": "^1.0.49-alpha.5",
|
||||||
"@budibase/string-templates": "^1.0.46-alpha.8",
|
"@budibase/string-templates": "^1.0.49-alpha.5",
|
||||||
"@bull-board/api": "^3.7.0",
|
"@bull-board/api": "^3.7.0",
|
||||||
"@bull-board/koa": "^3.7.0",
|
"@bull-board/koa": "^3.7.0",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -30,4 +30,4 @@ process.env.ALLOW_DEV_AUTOMATIONS = "1"
|
||||||
const server = require("../src/app")
|
const server = require("../src/app")
|
||||||
process.env.PORT = WORKER_PORT
|
process.env.PORT = WORKER_PORT
|
||||||
const worker = require("../../worker/src/index")
|
const worker = require("../../worker/src/index")
|
||||||
process.env.PORT = MAIN_PORT
|
process.env.PORT = MAIN_PORT
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const packageJson = require("../../../package.json")
|
const packageJson = require("../../../package.json")
|
||||||
const {
|
const {
|
||||||
|
@ -29,7 +28,7 @@ const { processObject } = require("@budibase/string-templates")
|
||||||
const {
|
const {
|
||||||
getAllApps,
|
getAllApps,
|
||||||
isDevAppID,
|
isDevAppID,
|
||||||
getDeployedAppID,
|
getProdAppID,
|
||||||
Replication,
|
Replication,
|
||||||
} = require("@budibase/backend-core/db")
|
} = require("@budibase/backend-core/db")
|
||||||
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
||||||
|
@ -45,11 +44,17 @@ const { getTenantId, isMultiTenant } = require("@budibase/backend-core/tenancy")
|
||||||
const { syncGlobalUsers } = require("./user")
|
const { syncGlobalUsers } = require("./user")
|
||||||
const { app: appCache } = require("@budibase/backend-core/cache")
|
const { app: appCache } = require("@budibase/backend-core/cache")
|
||||||
const { cleanupAutomations } = require("../../automations/utils")
|
const { cleanupAutomations } = require("../../automations/utils")
|
||||||
|
const {
|
||||||
|
getAppDB,
|
||||||
|
getProdAppDB,
|
||||||
|
updateAppId,
|
||||||
|
} = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
const URL_REGEX_SLASH = /\/|\\/g
|
const URL_REGEX_SLASH = /\/|\\/g
|
||||||
|
|
||||||
// utility function, need to do away with this
|
// utility function, need to do away with this
|
||||||
async function getLayouts(db) {
|
async function getLayouts() {
|
||||||
|
const db = getAppDB()
|
||||||
return (
|
return (
|
||||||
await db.allDocs(
|
await db.allDocs(
|
||||||
getLayoutParams(null, {
|
getLayoutParams(null, {
|
||||||
|
@ -59,7 +64,8 @@ async function getLayouts(db) {
|
||||||
).rows.map(row => row.doc)
|
).rows.map(row => row.doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getScreens(db) {
|
async function getScreens() {
|
||||||
|
const db = getAppDB()
|
||||||
return (
|
return (
|
||||||
await db.allDocs(
|
await db.allDocs(
|
||||||
getScreenParams(null, {
|
getScreenParams(null, {
|
||||||
|
@ -75,7 +81,7 @@ function getUserRoleId(ctx) {
|
||||||
: ctx.user.role._id
|
: ctx.user.role._id
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getAppUrl(ctx) {
|
exports.getAppUrl = ctx => {
|
||||||
// construct the url
|
// construct the url
|
||||||
let url
|
let url
|
||||||
if (ctx.request.body.url) {
|
if (ctx.request.body.url) {
|
||||||
|
@ -117,8 +123,9 @@ async function createInstance(template) {
|
||||||
const tenantId = isMultiTenant() ? getTenantId() : null
|
const tenantId = isMultiTenant() ? getTenantId() : null
|
||||||
const baseAppId = generateAppID(tenantId)
|
const baseAppId = generateAppID(tenantId)
|
||||||
const appId = generateDevAppID(baseAppId)
|
const appId = generateDevAppID(baseAppId)
|
||||||
|
updateAppId(appId)
|
||||||
|
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
await db.put({
|
await db.put({
|
||||||
_id: "_design/database",
|
_id: "_design/database",
|
||||||
// view collation information, read before writing any complex views:
|
// view collation information, read before writing any complex views:
|
||||||
|
@ -128,9 +135,9 @@ async function createInstance(template) {
|
||||||
|
|
||||||
// NOTE: indexes need to be created before any tables/templates
|
// NOTE: indexes need to be created before any tables/templates
|
||||||
// add view for linked rows
|
// add view for linked rows
|
||||||
await createLinkView(appId)
|
await createLinkView()
|
||||||
await createRoutingView(appId)
|
await createRoutingView()
|
||||||
await createAllSearchIndex(appId)
|
await createAllSearchIndex()
|
||||||
|
|
||||||
// replicate the template data to the instance DB
|
// replicate the template data to the instance DB
|
||||||
// this is currently very hard to test, downloading and importing template files
|
// this is currently very hard to test, downloading and importing template files
|
||||||
|
@ -156,7 +163,7 @@ async function createInstance(template) {
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
const dev = ctx.query && ctx.query.status === AppStatus.DEV
|
const dev = ctx.query && ctx.query.status === AppStatus.DEV
|
||||||
const all = ctx.query && ctx.query.status === AppStatus.ALL
|
const all = ctx.query && ctx.query.status === AppStatus.ALL
|
||||||
const apps = await getAllApps(CouchDB, { dev, all })
|
const apps = await getAllApps({ dev, all })
|
||||||
|
|
||||||
// get the locks for all the dev apps
|
// get the locks for all the dev apps
|
||||||
if (dev || all) {
|
if (dev || all) {
|
||||||
|
@ -179,12 +186,11 @@ exports.fetch = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchAppDefinition = async ctx => {
|
exports.fetchAppDefinition = async ctx => {
|
||||||
const db = new CouchDB(ctx.params.appId)
|
const layouts = await getLayouts()
|
||||||
const layouts = await getLayouts(db)
|
|
||||||
const userRoleId = getUserRoleId(ctx)
|
const userRoleId = getUserRoleId(ctx)
|
||||||
const accessController = new AccessController(ctx.params.appId)
|
const accessController = new AccessController()
|
||||||
const screens = await accessController.checkScreensAccess(
|
const screens = await accessController.checkScreensAccess(
|
||||||
await getScreens(db),
|
await getScreens(),
|
||||||
userRoleId
|
userRoleId
|
||||||
)
|
)
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
|
@ -195,15 +201,15 @@ exports.fetchAppDefinition = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchAppPackage = async ctx => {
|
exports.fetchAppPackage = async ctx => {
|
||||||
const db = new CouchDB(ctx.params.appId)
|
const db = getAppDB()
|
||||||
const application = await db.get(DocumentTypes.APP_METADATA)
|
const application = await db.get(DocumentTypes.APP_METADATA)
|
||||||
const layouts = await getLayouts(db)
|
const layouts = await getLayouts()
|
||||||
let screens = await getScreens(db)
|
let screens = await getScreens()
|
||||||
|
|
||||||
// Only filter screens if the user is not a builder
|
// Only filter screens if the user is not a builder
|
||||||
if (!(ctx.user.builder && ctx.user.builder.global)) {
|
if (!(ctx.user.builder && ctx.user.builder.global)) {
|
||||||
const userRoleId = getUserRoleId(ctx)
|
const userRoleId = getUserRoleId(ctx)
|
||||||
const accessController = new AccessController(ctx.params.appId)
|
const accessController = new AccessController()
|
||||||
screens = await accessController.checkScreensAccess(screens, userRoleId)
|
screens = await accessController.checkScreensAccess(screens, userRoleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,10 +222,10 @@ exports.fetchAppPackage = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.create = async ctx => {
|
exports.create = async ctx => {
|
||||||
const apps = await getAllApps(CouchDB, { dev: true })
|
const apps = await getAllApps({ dev: true })
|
||||||
const name = ctx.request.body.name
|
const name = ctx.request.body.name
|
||||||
checkAppName(ctx, apps, name)
|
checkAppName(ctx, apps, name)
|
||||||
const url = await getAppUrl(ctx)
|
const url = exports.getAppUrl(ctx)
|
||||||
checkAppUrl(ctx, apps, url)
|
checkAppUrl(ctx, apps, url)
|
||||||
|
|
||||||
const { useTemplate, templateKey, templateString } = ctx.request.body
|
const { useTemplate, templateKey, templateString } = ctx.request.body
|
||||||
|
@ -234,7 +240,7 @@ exports.create = async ctx => {
|
||||||
const instance = await createInstance(instanceConfig)
|
const instance = await createInstance(instanceConfig)
|
||||||
const appId = instance._id
|
const appId = instance._id
|
||||||
|
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
let _rev
|
let _rev
|
||||||
try {
|
try {
|
||||||
// if template there will be an existing doc
|
// if template there will be an existing doc
|
||||||
|
@ -280,13 +286,13 @@ exports.create = async ctx => {
|
||||||
// This endpoint currently operates as a PATCH rather than a PUT
|
// This endpoint currently operates as a PATCH rather than a PUT
|
||||||
// Thus name and url fields are handled only if present
|
// Thus name and url fields are handled only if present
|
||||||
exports.update = async ctx => {
|
exports.update = async ctx => {
|
||||||
const apps = await getAllApps(CouchDB, { dev: true })
|
const apps = await getAllApps({ dev: true })
|
||||||
// validation
|
// validation
|
||||||
const name = ctx.request.body.name
|
const name = ctx.request.body.name
|
||||||
if (name) {
|
if (name) {
|
||||||
checkAppName(ctx, apps, name, ctx.params.appId)
|
checkAppName(ctx, apps, name, ctx.params.appId)
|
||||||
}
|
}
|
||||||
const url = await getAppUrl(ctx)
|
const url = await exports.getAppUrl(ctx)
|
||||||
if (url) {
|
if (url) {
|
||||||
checkAppUrl(ctx, apps, url, ctx.params.appId)
|
checkAppUrl(ctx, apps, url, ctx.params.appId)
|
||||||
ctx.request.body.url = url
|
ctx.request.body.url = url
|
||||||
|
@ -299,7 +305,7 @@ exports.update = async ctx => {
|
||||||
|
|
||||||
exports.updateClient = async ctx => {
|
exports.updateClient = async ctx => {
|
||||||
// Get current app version
|
// Get current app version
|
||||||
const db = new CouchDB(ctx.params.appId)
|
const db = getAppDB()
|
||||||
const application = await db.get(DocumentTypes.APP_METADATA)
|
const application = await db.get(DocumentTypes.APP_METADATA)
|
||||||
const currentVersion = application.version
|
const currentVersion = application.version
|
||||||
|
|
||||||
|
@ -321,7 +327,7 @@ exports.updateClient = async ctx => {
|
||||||
|
|
||||||
exports.revertClient = async ctx => {
|
exports.revertClient = async ctx => {
|
||||||
// Check app can be reverted
|
// Check app can be reverted
|
||||||
const db = new CouchDB(ctx.params.appId)
|
const db = getAppDB()
|
||||||
const application = await db.get(DocumentTypes.APP_METADATA)
|
const application = await db.get(DocumentTypes.APP_METADATA)
|
||||||
if (!application.revertableVersion) {
|
if (!application.revertableVersion) {
|
||||||
ctx.throw(400, "There is no version to revert to")
|
ctx.throw(400, "There is no version to revert to")
|
||||||
|
@ -343,7 +349,7 @@ exports.revertClient = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.delete = async ctx => {
|
exports.delete = async ctx => {
|
||||||
const db = new CouchDB(ctx.params.appId)
|
const db = getAppDB()
|
||||||
|
|
||||||
const result = await db.destroy()
|
const result = await db.destroy()
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
|
@ -368,10 +374,11 @@ exports.sync = async (ctx, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// replicate prod to dev
|
// replicate prod to dev
|
||||||
const prodAppId = getDeployedAppID(appId)
|
const prodAppId = getProdAppID(appId)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const prodDb = new CouchDB(prodAppId, { skip_setup: true })
|
// specific case, want to make sure setup is skipped
|
||||||
|
const prodDb = getProdAppDB({ skip_setup: true })
|
||||||
const info = await prodDb.info()
|
const info = await prodDb.info()
|
||||||
if (info.error) throw info.error
|
if (info.error) throw info.error
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -399,7 +406,7 @@ exports.sync = async (ctx, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// sync the users
|
// sync the users
|
||||||
await syncGlobalUsers(appId)
|
await syncGlobalUsers()
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
ctx.throw(400, error)
|
ctx.throw(400, error)
|
||||||
|
@ -411,7 +418,7 @@ exports.sync = async (ctx, next) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateAppPackage = async (appPackage, appId) => {
|
const updateAppPackage = async (appPackage, appId) => {
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const application = await db.get(DocumentTypes.APP_METADATA)
|
const application = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
|
||||||
const newAppPackage = { ...application, ...appPackage }
|
const newAppPackage = { ...application, ...appPackage }
|
||||||
|
@ -430,7 +437,7 @@ const updateAppPackage = async (appPackage, appId) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEmptyAppPackage = async (ctx, app) => {
|
const createEmptyAppPackage = async (ctx, app) => {
|
||||||
const db = new CouchDB(app.appId)
|
const db = getAppDB()
|
||||||
|
|
||||||
let screensAndLayouts = []
|
let screensAndLayouts = []
|
||||||
for (let layout of BASE_LAYOUTS) {
|
for (let layout of BASE_LAYOUTS) {
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const { outputProcessing } = require("../../utilities/rowProcessor")
|
const { outputProcessing } = require("../../utilities/rowProcessor")
|
||||||
const { InternalTables } = require("../../db/utils")
|
const { InternalTables } = require("../../db/utils")
|
||||||
const { getFullUser } = require("../../utilities/users")
|
const { getFullUser } = require("../../utilities/users")
|
||||||
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
|
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
|
||||||
|
const { getAppDB, getAppId } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
exports.fetchSelf = async ctx => {
|
exports.fetchSelf = async ctx => {
|
||||||
const appId = ctx.appId
|
|
||||||
let userId = ctx.user.userId || ctx.user._id
|
let userId = ctx.user.userId || ctx.user._id
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
|
@ -19,8 +18,8 @@ exports.fetchSelf = async ctx => {
|
||||||
// forward the csrf token from the session
|
// forward the csrf token from the session
|
||||||
user.csrfToken = ctx.user.csrfToken
|
user.csrfToken = ctx.user.csrfToken
|
||||||
|
|
||||||
if (appId) {
|
if (getAppId()) {
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
// remove the full roles structure
|
// remove the full roles structure
|
||||||
delete user.roles
|
delete user.roles
|
||||||
try {
|
try {
|
||||||
|
@ -29,7 +28,7 @@ exports.fetchSelf = async ctx => {
|
||||||
// make sure there is never a stale csrf token
|
// make sure there is never a stale csrf token
|
||||||
delete metadata.csrfToken
|
delete metadata.csrfToken
|
||||||
// specifically needs to make sure is enriched
|
// specifically needs to make sure is enriched
|
||||||
ctx.body = await outputProcessing(ctx, userTable, {
|
ctx.body = await outputProcessing(userTable, {
|
||||||
...user,
|
...user,
|
||||||
...metadata,
|
...metadata,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const actions = require("../../automations/actions")
|
const actions = require("../../automations/actions")
|
||||||
const triggers = require("../../automations/triggers")
|
const triggers = require("../../automations/triggers")
|
||||||
const { getAutomationParams, generateAutomationID } = require("../../db/utils")
|
const { getAutomationParams, generateAutomationID } = require("../../db/utils")
|
||||||
|
@ -10,6 +9,7 @@ const {
|
||||||
const { deleteEntityMetadata } = require("../../utilities")
|
const { deleteEntityMetadata } = require("../../utilities")
|
||||||
const { MetadataTypes } = require("../../constants")
|
const { MetadataTypes } = require("../../constants")
|
||||||
const { setTestFlag, clearTestFlag } = require("../../utilities/redis")
|
const { setTestFlag, clearTestFlag } = require("../../utilities/redis")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
const ACTION_DEFS = removeDeprecated(actions.ACTION_DEFINITIONS)
|
const ACTION_DEFS = removeDeprecated(actions.ACTION_DEFINITIONS)
|
||||||
const TRIGGER_DEFS = removeDeprecated(triggers.TRIGGER_DEFINITIONS)
|
const TRIGGER_DEFS = removeDeprecated(triggers.TRIGGER_DEFINITIONS)
|
||||||
|
@ -20,14 +20,9 @@ const TRIGGER_DEFS = removeDeprecated(triggers.TRIGGER_DEFINITIONS)
|
||||||
* *
|
* *
|
||||||
*************************/
|
*************************/
|
||||||
|
|
||||||
async function cleanupAutomationMetadata(appId, automationId) {
|
async function cleanupAutomationMetadata(automationId) {
|
||||||
|
await deleteEntityMetadata(MetadataTypes.AUTOMATION_TEST_INPUT, automationId)
|
||||||
await deleteEntityMetadata(
|
await deleteEntityMetadata(
|
||||||
appId,
|
|
||||||
MetadataTypes.AUTOMATION_TEST_INPUT,
|
|
||||||
automationId
|
|
||||||
)
|
|
||||||
await deleteEntityMetadata(
|
|
||||||
appId,
|
|
||||||
MetadataTypes.AUTOMATION_TEST_HISTORY,
|
MetadataTypes.AUTOMATION_TEST_HISTORY,
|
||||||
automationId
|
automationId
|
||||||
)
|
)
|
||||||
|
@ -58,7 +53,7 @@ function cleanAutomationInputs(automation) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.create = async function (ctx) {
|
exports.create = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
let automation = ctx.request.body
|
let automation = ctx.request.body
|
||||||
automation.appId = ctx.appId
|
automation.appId = ctx.appId
|
||||||
|
|
||||||
|
@ -72,7 +67,6 @@ exports.create = async function (ctx) {
|
||||||
automation.type = "automation"
|
automation.type = "automation"
|
||||||
automation = cleanAutomationInputs(automation)
|
automation = cleanAutomationInputs(automation)
|
||||||
automation = await checkForWebhooks({
|
automation = await checkForWebhooks({
|
||||||
appId: ctx.appId,
|
|
||||||
newAuto: automation,
|
newAuto: automation,
|
||||||
})
|
})
|
||||||
const response = await db.put(automation)
|
const response = await db.put(automation)
|
||||||
|
@ -89,13 +83,12 @@ exports.create = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.update = async function (ctx) {
|
exports.update = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
let automation = ctx.request.body
|
let automation = ctx.request.body
|
||||||
automation.appId = ctx.appId
|
automation.appId = ctx.appId
|
||||||
const oldAutomation = await db.get(automation._id)
|
const oldAutomation = await db.get(automation._id)
|
||||||
automation = cleanAutomationInputs(automation)
|
automation = cleanAutomationInputs(automation)
|
||||||
automation = await checkForWebhooks({
|
automation = await checkForWebhooks({
|
||||||
appId: ctx.appId,
|
|
||||||
oldAuto: oldAutomation,
|
oldAuto: oldAutomation,
|
||||||
newAuto: automation,
|
newAuto: automation,
|
||||||
})
|
})
|
||||||
|
@ -131,7 +124,7 @@ exports.update = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async function (ctx) {
|
exports.fetch = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const response = await db.allDocs(
|
const response = await db.allDocs(
|
||||||
getAutomationParams(null, {
|
getAutomationParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
|
@ -141,20 +134,19 @@ exports.fetch = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async function (ctx) {
|
exports.find = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
ctx.body = await db.get(ctx.params.id)
|
ctx.body = await db.get(ctx.params.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function (ctx) {
|
exports.destroy = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const automationId = ctx.params.id
|
const automationId = ctx.params.id
|
||||||
const oldAutomation = await db.get(automationId)
|
const oldAutomation = await db.get(automationId)
|
||||||
await checkForWebhooks({
|
await checkForWebhooks({
|
||||||
appId: ctx.appId,
|
|
||||||
oldAuto: oldAutomation,
|
oldAuto: oldAutomation,
|
||||||
})
|
})
|
||||||
// delete metadata first
|
// delete metadata first
|
||||||
await cleanupAutomationMetadata(ctx.appId, automationId)
|
await cleanupAutomationMetadata(automationId)
|
||||||
ctx.body = await db.remove(automationId, ctx.params.rev)
|
ctx.body = await db.remove(automationId, ctx.params.rev)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,12 +172,11 @@ module.exports.getDefinitionList = async function (ctx) {
|
||||||
*********************/
|
*********************/
|
||||||
|
|
||||||
exports.trigger = async function (ctx) {
|
exports.trigger = async function (ctx) {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
let automation = await db.get(ctx.params.id)
|
let automation = await db.get(ctx.params.id)
|
||||||
await triggers.externalTrigger(automation, {
|
await triggers.externalTrigger(automation, {
|
||||||
...ctx.request.body,
|
...ctx.request.body,
|
||||||
appId,
|
appId: ctx.appId,
|
||||||
})
|
})
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: `Automation ${automation._id} has been triggered.`,
|
message: `Automation ${automation._id} has been triggered.`,
|
||||||
|
@ -205,8 +196,7 @@ function prepareTestInput(input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.test = async function (ctx) {
|
exports.test = async function (ctx) {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
let automation = await db.get(ctx.params.id)
|
let automation = await db.get(ctx.params.id)
|
||||||
await setTestFlag(automation._id)
|
await setTestFlag(automation._id)
|
||||||
const testInput = prepareTestInput(ctx.request.body)
|
const testInput = prepareTestInput(ctx.request.body)
|
||||||
|
@ -214,7 +204,7 @@ exports.test = async function (ctx) {
|
||||||
automation,
|
automation,
|
||||||
{
|
{
|
||||||
...testInput,
|
...testInput,
|
||||||
appId,
|
appId: ctx.appId,
|
||||||
},
|
},
|
||||||
{ getResponses: true }
|
{ getResponses: true }
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const { getAllApps } = require("@budibase/backend-core/db")
|
const { getAllApps } = require("@budibase/backend-core/db")
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const {
|
const {
|
||||||
exportDB,
|
exportDB,
|
||||||
sendTempFile,
|
sendTempFile,
|
||||||
|
@ -30,7 +29,7 @@ exports.exportApps = async ctx => {
|
||||||
if (env.SELF_HOSTED || !env.MULTI_TENANCY) {
|
if (env.SELF_HOSTED || !env.MULTI_TENANCY) {
|
||||||
ctx.throw(400, "Exporting only allowed in multi-tenant cloud environments.")
|
ctx.throw(400, "Exporting only allowed in multi-tenant cloud environments.")
|
||||||
}
|
}
|
||||||
const apps = await getAllApps(CouchDB, { all: true })
|
const apps = await getAllApps({ all: true })
|
||||||
const globalDBString = await exportDB(getGlobalDBName(), {
|
const globalDBString = await exportDB(getGlobalDBName(), {
|
||||||
filter: doc => !doc._id.startsWith(DocumentTypes.USER),
|
filter: doc => !doc._id.startsWith(DocumentTypes.USER),
|
||||||
})
|
})
|
||||||
|
@ -63,7 +62,7 @@ async function hasBeenImported() {
|
||||||
if (!env.SELF_HOSTED || env.MULTI_TENANCY) {
|
if (!env.SELF_HOSTED || env.MULTI_TENANCY) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const apps = await getAllApps(CouchDB, { all: true })
|
const apps = await getAllApps({ all: true })
|
||||||
return apps.length !== 0
|
return apps.length !== 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const { DocumentTypes } = require("../../db/utils")
|
const { DocumentTypes } = require("../../db/utils")
|
||||||
const { getComponentLibraryManifest } = require("../../utilities/fileSystem")
|
const { getComponentLibraryManifest } = require("../../utilities/fileSystem")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
exports.fetchAppComponentDefinitions = async function (ctx) {
|
exports.fetchAppComponentDefinitions = async function (ctx) {
|
||||||
const appId = ctx.params.appId || ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const app = await db.get(DocumentTypes.APP_METADATA)
|
const app = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
|
||||||
let componentManifests = await Promise.all(
|
let componentManifests = await Promise.all(
|
||||||
app.componentLibraries.map(async library => {
|
app.componentLibraries.map(async library => {
|
||||||
let manifest = await getComponentLibraryManifest(appId, library)
|
let manifest = await getComponentLibraryManifest(library)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
manifest,
|
manifest,
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const {
|
const {
|
||||||
generateDatasourceID,
|
generateDatasourceID,
|
||||||
getDatasourceParams,
|
getDatasourceParams,
|
||||||
|
@ -11,12 +10,11 @@ const { BuildSchemaErrors, InvalidColumns } = require("../../constants")
|
||||||
const { integrations } = require("../../integrations")
|
const { integrations } = require("../../integrations")
|
||||||
const { getDatasourceAndQuery } = require("./row/utils")
|
const { getDatasourceAndQuery } = require("./row/utils")
|
||||||
const { invalidateDynamicVariables } = require("../../threads/utils")
|
const { invalidateDynamicVariables } = require("../../threads/utils")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
exports.fetch = async function (ctx) {
|
exports.fetch = async function (ctx) {
|
||||||
const database = new CouchDB(ctx.appId)
|
|
||||||
|
|
||||||
// Get internal tables
|
// Get internal tables
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const internalTables = await db.allDocs(
|
const internalTables = await db.allDocs(
|
||||||
getTableParams(null, {
|
getTableParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
|
@ -31,7 +29,7 @@ exports.fetch = async function (ctx) {
|
||||||
|
|
||||||
// Get external datasources
|
// Get external datasources
|
||||||
const datasources = (
|
const datasources = (
|
||||||
await database.allDocs(
|
await db.allDocs(
|
||||||
getDatasourceParams(null, {
|
getDatasourceParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
|
@ -49,7 +47,7 @@ exports.fetch = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.buildSchemaFromDb = async function (ctx) {
|
exports.buildSchemaFromDb = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const datasource = await db.get(ctx.params.datasourceId)
|
const datasource = await db.get(ctx.params.datasourceId)
|
||||||
|
|
||||||
const { tables, error } = await buildSchemaHelper(datasource)
|
const { tables, error } = await buildSchemaHelper(datasource)
|
||||||
|
@ -98,7 +96,7 @@ const invalidateVariables = async (existingDatasource, updatedDatasource) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.update = async function (ctx) {
|
exports.update = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const datasourceId = ctx.params.datasourceId
|
const datasourceId = ctx.params.datasourceId
|
||||||
let datasource = await db.get(datasourceId)
|
let datasource = await db.get(datasourceId)
|
||||||
const auth = datasource.config.auth
|
const auth = datasource.config.auth
|
||||||
|
@ -126,7 +124,7 @@ exports.update = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
exports.save = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const plus = ctx.request.body.datasource.plus
|
const plus = ctx.request.body.datasource.plus
|
||||||
const fetchSchema = ctx.request.body.fetchSchema
|
const fetchSchema = ctx.request.body.fetchSchema
|
||||||
|
|
||||||
|
@ -162,7 +160,7 @@ exports.save = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function (ctx) {
|
exports.destroy = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
|
|
||||||
// Delete all queries for the datasource
|
// Delete all queries for the datasource
|
||||||
const queries = await db.allDocs(
|
const queries = await db.allDocs(
|
||||||
|
@ -184,7 +182,7 @@ exports.destroy = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async function (ctx) {
|
exports.find = async function (ctx) {
|
||||||
const database = new CouchDB(ctx.appId)
|
const database = getAppDB()
|
||||||
ctx.body = await database.get(ctx.params.datasourceId)
|
ctx.body = await database.get(ctx.params.datasourceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +190,7 @@ exports.find = async function (ctx) {
|
||||||
exports.query = async function (ctx) {
|
exports.query = async function (ctx) {
|
||||||
const queryJson = ctx.request.body
|
const queryJson = ctx.request.body
|
||||||
try {
|
try {
|
||||||
ctx.body = await getDatasourceAndQuery(ctx.appId, queryJson)
|
ctx.body = await getDatasourceAndQuery(queryJson)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, err)
|
ctx.throw(400, err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
const newid = require("../../../db/newid")
|
const newid = require("../../../db/newid")
|
||||||
|
const { getAppId } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is used to pass around information about the deployment that is occurring
|
* This is used to pass around information about the deployment that is occurring
|
||||||
*/
|
*/
|
||||||
class Deployment {
|
class Deployment {
|
||||||
constructor(appId, id = null) {
|
constructor(id = null) {
|
||||||
this.appId = appId
|
|
||||||
this._id = id || newid()
|
this._id = id || newid()
|
||||||
}
|
}
|
||||||
|
|
||||||
getAppId() {
|
|
||||||
return this.appId
|
|
||||||
}
|
|
||||||
|
|
||||||
setVerification(verification) {
|
setVerification(verification) {
|
||||||
if (!verification) {
|
if (!verification) {
|
||||||
return
|
return
|
||||||
|
@ -43,7 +39,7 @@ class Deployment {
|
||||||
getJSON() {
|
getJSON() {
|
||||||
const obj = {
|
const obj = {
|
||||||
_id: this._id,
|
_id: this._id,
|
||||||
appId: this.appId,
|
appId: getAppId(),
|
||||||
status: this.status,
|
status: this.status,
|
||||||
}
|
}
|
||||||
if (this.err) {
|
if (this.err) {
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
const CouchDB = require("../../../db")
|
|
||||||
const Deployment = require("./Deployment")
|
const Deployment = require("./Deployment")
|
||||||
const { Replication, getDeployedAppID } = require("@budibase/backend-core/db")
|
const {
|
||||||
|
Replication,
|
||||||
|
getProdAppID,
|
||||||
|
getDevelopmentAppID,
|
||||||
|
} = require("@budibase/backend-core/db")
|
||||||
const { DocumentTypes, getAutomationParams } = require("../../../db/utils")
|
const { DocumentTypes, getAutomationParams } = require("../../../db/utils")
|
||||||
const {
|
const {
|
||||||
disableAllCrons,
|
disableAllCrons,
|
||||||
enableCronTrigger,
|
enableCronTrigger,
|
||||||
} = require("../../../automations/utils")
|
} = require("../../../automations/utils")
|
||||||
const { app: appCache } = require("@budibase/backend-core/cache")
|
const { app: appCache } = require("@budibase/backend-core/cache")
|
||||||
|
const {
|
||||||
|
getAppId,
|
||||||
|
getAppDB,
|
||||||
|
getProdAppDB,
|
||||||
|
} = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
// the max time we can wait for an invalidation to complete before considering it failed
|
// the max time we can wait for an invalidation to complete before considering it failed
|
||||||
const MAX_PENDING_TIME_MS = 30 * 60000
|
const MAX_PENDING_TIME_MS = 30 * 60000
|
||||||
|
@ -34,9 +42,8 @@ async function checkAllDeployments(deployments) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeDeploymentHistory(deployment) {
|
async function storeDeploymentHistory(deployment) {
|
||||||
const appId = deployment.getAppId()
|
|
||||||
const deploymentJSON = deployment.getJSON()
|
const deploymentJSON = deployment.getJSON()
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
|
|
||||||
let deploymentDoc
|
let deploymentDoc
|
||||||
try {
|
try {
|
||||||
|
@ -64,7 +71,7 @@ async function storeDeploymentHistory(deployment) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initDeployedApp(prodAppId) {
|
async function initDeployedApp(prodAppId) {
|
||||||
const db = new CouchDB(prodAppId)
|
const db = getProdAppDB()
|
||||||
console.log("Reading automation docs")
|
console.log("Reading automation docs")
|
||||||
const automations = (
|
const automations = (
|
||||||
await db.allDocs(
|
await db.allDocs(
|
||||||
|
@ -88,10 +95,12 @@ async function initDeployedApp(prodAppId) {
|
||||||
|
|
||||||
async function deployApp(deployment) {
|
async function deployApp(deployment) {
|
||||||
try {
|
try {
|
||||||
const productionAppId = getDeployedAppID(deployment.appId)
|
const appId = getAppId()
|
||||||
|
const devAppId = getDevelopmentAppID(appId)
|
||||||
|
const productionAppId = getProdAppID(appId)
|
||||||
|
|
||||||
const replication = new Replication({
|
const replication = new Replication({
|
||||||
source: deployment.appId,
|
source: devAppId,
|
||||||
target: productionAppId,
|
target: productionAppId,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -99,7 +108,7 @@ async function deployApp(deployment) {
|
||||||
|
|
||||||
await replication.replicate()
|
await replication.replicate()
|
||||||
console.log("replication complete.. replacing app meta doc")
|
console.log("replication complete.. replacing app meta doc")
|
||||||
const db = new CouchDB(productionAppId)
|
const db = getProdAppDB()
|
||||||
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
||||||
appDoc.appId = productionAppId
|
appDoc.appId = productionAppId
|
||||||
appDoc.instance._id = productionAppId
|
appDoc.instance._id = productionAppId
|
||||||
|
@ -122,8 +131,7 @@ async function deployApp(deployment) {
|
||||||
|
|
||||||
exports.fetchDeployments = async function (ctx) {
|
exports.fetchDeployments = async function (ctx) {
|
||||||
try {
|
try {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
||||||
const { updated, deployments } = await checkAllDeployments(
|
const { updated, deployments } = await checkAllDeployments(
|
||||||
deploymentDoc,
|
deploymentDoc,
|
||||||
|
@ -140,8 +148,7 @@ exports.fetchDeployments = async function (ctx) {
|
||||||
|
|
||||||
exports.deploymentProgress = async function (ctx) {
|
exports.deploymentProgress = async function (ctx) {
|
||||||
try {
|
try {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
||||||
ctx.body = deploymentDoc[ctx.params.deploymentId]
|
ctx.body = deploymentDoc[ctx.params.deploymentId]
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -153,7 +160,7 @@ exports.deploymentProgress = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.deployApp = async function (ctx) {
|
exports.deployApp = async function (ctx) {
|
||||||
let deployment = new Deployment(ctx.appId)
|
let deployment = new Deployment()
|
||||||
console.log("Deployment object created")
|
console.log("Deployment object created")
|
||||||
deployment.setStatus(DeploymentStatus.PENDING)
|
deployment.setStatus(DeploymentStatus.PENDING)
|
||||||
console.log("Deployment object set to pending")
|
console.log("Deployment object set to pending")
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const { checkSlashesInUrl } = require("../../utilities")
|
const { checkSlashesInUrl } = require("../../utilities")
|
||||||
const { request } = require("../../utilities/workerRequests")
|
const { request } = require("../../utilities/workerRequests")
|
||||||
const { clearLock } = require("../../utilities/redis")
|
const { clearLock } = require("../../utilities/redis")
|
||||||
const { Replication } = require("@budibase/backend-core/db")
|
const { Replication, getProdAppID } = require("@budibase/backend-core/db")
|
||||||
const { DocumentTypes } = require("../../db/utils")
|
const { DocumentTypes } = require("../../db/utils")
|
||||||
const { app: appCache } = require("@budibase/backend-core/cache")
|
const { app: appCache } = require("@budibase/backend-core/cache")
|
||||||
|
const { getProdAppDB, getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
async function redirect(ctx, method, path = "global") {
|
async function redirect(ctx, method, path = "global") {
|
||||||
const { devPath } = ctx.params
|
const { devPath } = ctx.params
|
||||||
|
@ -77,11 +77,11 @@ exports.clearLock = async ctx => {
|
||||||
|
|
||||||
exports.revert = async ctx => {
|
exports.revert = async ctx => {
|
||||||
const { appId } = ctx.params
|
const { appId } = ctx.params
|
||||||
const productionAppId = appId.replace("_dev", "")
|
const productionAppId = getProdAppID(appId)
|
||||||
|
|
||||||
// App must have been deployed first
|
// App must have been deployed first
|
||||||
try {
|
try {
|
||||||
const db = new CouchDB(productionAppId, { skip_setup: true })
|
const db = getProdAppDB({ skip_setup: true })
|
||||||
const info = await db.info()
|
const info = await db.info()
|
||||||
if (info.error) throw info.error
|
if (info.error) throw info.error
|
||||||
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
||||||
|
@ -103,7 +103,7 @@ exports.revert = async ctx => {
|
||||||
|
|
||||||
await replication.rollback()
|
await replication.rollback()
|
||||||
// update appID in reverted app to be dev version again
|
// update appID in reverted app to be dev version again
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
const appDoc = await db.get(DocumentTypes.APP_METADATA)
|
||||||
appDoc.appId = appId
|
appDoc.appId = appId
|
||||||
appDoc.instance._id = appId
|
appDoc.instance._id = appId
|
||||||
|
|
|
@ -2,11 +2,11 @@ const {
|
||||||
EMPTY_LAYOUT,
|
EMPTY_LAYOUT,
|
||||||
BASE_LAYOUT_PROP_IDS,
|
BASE_LAYOUT_PROP_IDS,
|
||||||
} = require("../../constants/layouts")
|
} = require("../../constants/layouts")
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const { generateLayoutID, getScreenParams } = require("../../db/utils")
|
const { generateLayoutID, getScreenParams } = require("../../db/utils")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
exports.save = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
let layout = ctx.request.body
|
let layout = ctx.request.body
|
||||||
|
|
||||||
if (!layout.props) {
|
if (!layout.props) {
|
||||||
|
@ -26,7 +26,7 @@ exports.save = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function (ctx) {
|
exports.destroy = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const layoutId = ctx.params.layoutId,
|
const layoutId = ctx.params.layoutId,
|
||||||
layoutRev = ctx.params.layoutRev
|
layoutRev = ctx.params.layoutRev
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const { MetadataTypes } = require("../../constants")
|
const { MetadataTypes } = require("../../constants")
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const { generateMetadataID } = require("../../db/utils")
|
const { generateMetadataID } = require("../../db/utils")
|
||||||
const { saveEntityMetadata, deleteEntityMetadata } = require("../../utilities")
|
const { saveEntityMetadata, deleteEntityMetadata } = require("../../utilities")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
exports.getTypes = async ctx => {
|
exports.getTypes = async ctx => {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
|
@ -14,17 +14,12 @@ exports.saveMetadata = async ctx => {
|
||||||
if (type === MetadataTypes.AUTOMATION_TEST_HISTORY) {
|
if (type === MetadataTypes.AUTOMATION_TEST_HISTORY) {
|
||||||
ctx.throw(400, "Cannot save automation history type")
|
ctx.throw(400, "Cannot save automation history type")
|
||||||
}
|
}
|
||||||
ctx.body = await saveEntityMetadata(
|
ctx.body = await saveEntityMetadata(type, entityId, ctx.request.body)
|
||||||
ctx.appId,
|
|
||||||
type,
|
|
||||||
entityId,
|
|
||||||
ctx.request.body
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.deleteMetadata = async ctx => {
|
exports.deleteMetadata = async ctx => {
|
||||||
const { type, entityId } = ctx.params
|
const { type, entityId } = ctx.params
|
||||||
await deleteEntityMetadata(ctx.appId, type, entityId)
|
await deleteEntityMetadata(type, entityId)
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Metadata deleted successfully",
|
message: "Metadata deleted successfully",
|
||||||
}
|
}
|
||||||
|
@ -32,7 +27,7 @@ exports.deleteMetadata = async ctx => {
|
||||||
|
|
||||||
exports.getMetadata = async ctx => {
|
exports.getMetadata = async ctx => {
|
||||||
const { type, entityId } = ctx.params
|
const { type, entityId } = ctx.params
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const id = generateMetadataID(type, entityId)
|
const id = generateMetadataID(type, entityId)
|
||||||
try {
|
try {
|
||||||
ctx.body = await db.get(id)
|
ctx.body = await db.get(id)
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
const { migrate, MIGRATIONS } = require("../../migrations")
|
||||||
|
|
||||||
|
exports.migrate = async ctx => {
|
||||||
|
const options = ctx.request.body
|
||||||
|
// don't await as can take a while, just return
|
||||||
|
migrate(options)
|
||||||
|
ctx.status = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.fetchDefinitions = async ctx => {
|
||||||
|
ctx.body = MIGRATIONS
|
||||||
|
ctx.status = 200
|
||||||
|
}
|
|
@ -6,12 +6,12 @@ const {
|
||||||
getBuiltinRoles,
|
getBuiltinRoles,
|
||||||
} = require("@budibase/backend-core/roles")
|
} = require("@budibase/backend-core/roles")
|
||||||
const { getRoleParams } = require("../../db/utils")
|
const { getRoleParams } = require("../../db/utils")
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const {
|
const {
|
||||||
CURRENTLY_SUPPORTED_LEVELS,
|
CURRENTLY_SUPPORTED_LEVELS,
|
||||||
getBasePermissions,
|
getBasePermissions,
|
||||||
} = require("../../utilities/security")
|
} = require("../../utilities/security")
|
||||||
const { removeFromArray } = require("../../utilities")
|
const { removeFromArray } = require("../../utilities")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
const PermissionUpdateType = {
|
const PermissionUpdateType = {
|
||||||
REMOVE: "remove",
|
REMOVE: "remove",
|
||||||
|
@ -35,7 +35,7 @@ async function updatePermissionOnRole(
|
||||||
{ roleId, resourceId, level },
|
{ roleId, resourceId, level },
|
||||||
updateType
|
updateType
|
||||||
) {
|
) {
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const remove = updateType === PermissionUpdateType.REMOVE
|
const remove = updateType === PermissionUpdateType.REMOVE
|
||||||
const isABuiltin = isBuiltin(roleId)
|
const isABuiltin = isBuiltin(roleId)
|
||||||
const dbRoleId = getDBRoleID(roleId)
|
const dbRoleId = getDBRoleID(roleId)
|
||||||
|
@ -106,7 +106,7 @@ exports.fetchLevels = function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async function (ctx) {
|
exports.fetch = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const roles = await getAllDBRoles(db)
|
const roles = await getAllDBRoles(db)
|
||||||
let permissions = {}
|
let permissions = {}
|
||||||
// create an object with structure role ID -> resource ID -> level
|
// create an object with structure role ID -> resource ID -> level
|
||||||
|
@ -133,7 +133,7 @@ exports.fetch = async function (ctx) {
|
||||||
|
|
||||||
exports.getResourcePerms = async function (ctx) {
|
exports.getResourcePerms = async function (ctx) {
|
||||||
const resourceId = ctx.params.resourceId
|
const resourceId = ctx.params.resourceId
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const body = await db.allDocs(
|
const body = await db.allDocs(
|
||||||
getRoleParams(null, {
|
getRoleParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import CouchDB from "../../../../db"
|
|
||||||
import { queryValidation } from "../validation"
|
import { queryValidation } from "../validation"
|
||||||
import { generateQueryID } from "../../../../db/utils"
|
import { generateQueryID } from "../../../../db/utils"
|
||||||
import { ImportInfo, ImportSource } from "./sources/base"
|
import { ImportInfo, ImportSource } from "./sources/base"
|
||||||
import { OpenAPI2 } from "./sources/openapi2"
|
import { OpenAPI2 } from "./sources/openapi2"
|
||||||
import { Query } from "./../../../../definitions/common"
|
import { Query } from "./../../../../definitions/common"
|
||||||
import { Curl } from "./sources/curl"
|
import { Curl } from "./sources/curl"
|
||||||
|
// @ts-ignore
|
||||||
|
import { getAppDB } from "@budibase/backend-core/context"
|
||||||
interface ImportResult {
|
interface ImportResult {
|
||||||
errorQueries: Query[]
|
errorQueries: Query[]
|
||||||
queries: Query[]
|
queries: Query[]
|
||||||
|
@ -33,10 +34,7 @@ export class RestImporter {
|
||||||
return this.source.getInfo()
|
return this.source.getInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
importQueries = async (
|
importQueries = async (datasourceId: string): Promise<ImportResult> => {
|
||||||
appId: string,
|
|
||||||
datasourceId: string
|
|
||||||
): Promise<ImportResult> => {
|
|
||||||
// constuct the queries
|
// constuct the queries
|
||||||
let queries = await this.source.getQueries(datasourceId)
|
let queries = await this.source.getQueries(datasourceId)
|
||||||
|
|
||||||
|
@ -58,7 +56,7 @@ export class RestImporter {
|
||||||
})
|
})
|
||||||
|
|
||||||
// persist queries
|
// persist queries
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const response = await db.bulkDocs(queries)
|
const response = await db.bulkDocs(queries)
|
||||||
|
|
||||||
// create index to seperate queries and errors
|
// create index to seperate queries and errors
|
||||||
|
|
|
@ -6,6 +6,7 @@ const db = jest.fn(() => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
jest.mock("../../../../../db", () => db)
|
jest.mock("../../../../../db", () => db)
|
||||||
|
require("@budibase/backend-core").init(require("../../../../../db"))
|
||||||
|
|
||||||
const { RestImporter } = require("../index")
|
const { RestImporter } = require("../index")
|
||||||
|
|
||||||
|
@ -77,7 +78,7 @@ describe("Rest Importer", () => {
|
||||||
const testImportQueries = async (key, data, assertions) => {
|
const testImportQueries = async (key, data, assertions) => {
|
||||||
await init(data)
|
await init(data)
|
||||||
bulkDocs.mockReturnValue([])
|
bulkDocs.mockReturnValue([])
|
||||||
const importResult = await restImporter.importQueries("appId", "datasourceId")
|
const importResult = await restImporter.importQueries("datasourceId")
|
||||||
expect(importResult.errorQueries.length).toBe(0)
|
expect(importResult.errorQueries.length).toBe(0)
|
||||||
expect(importResult.queries.length).toBe(assertions[key].count)
|
expect(importResult.queries.length).toBe(assertions[key].count)
|
||||||
expect(bulkDocs).toHaveBeenCalledTimes(1)
|
expect(bulkDocs).toHaveBeenCalledTimes(1)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
const CouchDB = require("../../../db")
|
|
||||||
const {
|
const {
|
||||||
generateQueryID,
|
generateQueryID,
|
||||||
getQueryParams,
|
getQueryParams,
|
||||||
|
@ -10,6 +9,7 @@ const { save: saveDatasource } = require("../datasource")
|
||||||
const { RestImporter } = require("./import")
|
const { RestImporter } = require("./import")
|
||||||
const { invalidateDynamicVariables } = require("../../../threads/utils")
|
const { invalidateDynamicVariables } = require("../../../threads/utils")
|
||||||
const environment = require("../../../environment")
|
const environment = require("../../../environment")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
const Runner = new Thread(ThreadType.QUERY, {
|
const Runner = new Thread(ThreadType.QUERY, {
|
||||||
timeoutMs: environment.QUERY_THREAD_TIMEOUT || 10000,
|
timeoutMs: environment.QUERY_THREAD_TIMEOUT || 10000,
|
||||||
|
@ -28,7 +28,7 @@ function enrichQueries(input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async function (ctx) {
|
exports.fetch = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
|
|
||||||
const body = await db.allDocs(
|
const body = await db.allDocs(
|
||||||
getQueryParams(null, {
|
getQueryParams(null, {
|
||||||
|
@ -69,7 +69,7 @@ exports.import = async ctx => {
|
||||||
datasourceId = body.datasourceId
|
datasourceId = body.datasourceId
|
||||||
}
|
}
|
||||||
|
|
||||||
const importResult = await importer.importQueries(ctx.appId, datasourceId)
|
const importResult = await importer.importQueries(datasourceId)
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
...importResult,
|
...importResult,
|
||||||
|
@ -79,7 +79,7 @@ exports.import = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
exports.save = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const query = ctx.request.body
|
const query = ctx.request.body
|
||||||
|
|
||||||
if (!query._id) {
|
if (!query._id) {
|
||||||
|
@ -94,7 +94,7 @@ exports.save = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async function (ctx) {
|
exports.find = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const query = enrichQueries(await db.get(ctx.params.queryId))
|
const query = enrichQueries(await db.get(ctx.params.queryId))
|
||||||
// remove properties that could be dangerous in real app
|
// remove properties that could be dangerous in real app
|
||||||
if (isProdAppID(ctx.appId)) {
|
if (isProdAppID(ctx.appId)) {
|
||||||
|
@ -105,7 +105,7 @@ exports.find = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.preview = async function (ctx) {
|
exports.preview = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
|
|
||||||
const datasource = await db.get(ctx.request.body.datasourceId)
|
const datasource = await db.get(ctx.request.body.datasourceId)
|
||||||
// preview may not have a queryId as it hasn't been saved, but if it does
|
// preview may not have a queryId as it hasn't been saved, but if it does
|
||||||
|
@ -136,7 +136,7 @@ exports.preview = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function execute(ctx, opts = { rowsOnly: false }) {
|
async function execute(ctx, opts = { rowsOnly: false }) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
|
|
||||||
const query = await db.get(ctx.params.queryId)
|
const query = await db.get(ctx.params.queryId)
|
||||||
const datasource = await db.get(query.datasourceId)
|
const datasource = await db.get(query.datasourceId)
|
||||||
|
@ -181,7 +181,8 @@ exports.executeV2 = async function (ctx) {
|
||||||
return execute(ctx, { rowsOnly: false })
|
return execute(ctx, { rowsOnly: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeDynamicVariables = async (db, queryId) => {
|
const removeDynamicVariables = async queryId => {
|
||||||
|
const db = getAppDB()
|
||||||
const query = await db.get(queryId)
|
const query = await db.get(queryId)
|
||||||
const datasource = await db.get(query.datasourceId)
|
const datasource = await db.get(query.datasourceId)
|
||||||
const dynamicVariables = datasource.config.dynamicVariables
|
const dynamicVariables = datasource.config.dynamicVariables
|
||||||
|
@ -202,8 +203,8 @@ const removeDynamicVariables = async (db, queryId) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function (ctx) {
|
exports.destroy = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
await removeDynamicVariables(db, ctx.params.queryId)
|
await removeDynamicVariables(ctx.params.queryId)
|
||||||
await db.remove(ctx.params.queryId, ctx.params.revId)
|
await db.remove(ctx.params.queryId, ctx.params.revId)
|
||||||
ctx.message = `Query deleted.`
|
ctx.message = `Query deleted.`
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const {
|
const {
|
||||||
Role,
|
Role,
|
||||||
getRole,
|
getRole,
|
||||||
|
@ -10,6 +9,7 @@ const {
|
||||||
getUserMetadataParams,
|
getUserMetadataParams,
|
||||||
InternalTables,
|
InternalTables,
|
||||||
} = require("../../db/utils")
|
} = require("../../db/utils")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
const UpdateRolesOptions = {
|
const UpdateRolesOptions = {
|
||||||
CREATED: "created",
|
CREATED: "created",
|
||||||
|
@ -40,15 +40,15 @@ async function updateRolesOnUserTable(db, roleId, updateOption) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async function (ctx) {
|
exports.fetch = async function (ctx) {
|
||||||
ctx.body = await getAllRoles(ctx.appId)
|
ctx.body = await getAllRoles()
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async function (ctx) {
|
exports.find = async function (ctx) {
|
||||||
ctx.body = await getRole(ctx.appId, ctx.params.roleId)
|
ctx.body = await getRole(ctx.params.roleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
exports.save = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
let { _id, name, inherits, permissionId } = ctx.request.body
|
let { _id, name, inherits, permissionId } = ctx.request.body
|
||||||
if (!_id) {
|
if (!_id) {
|
||||||
_id = generateRoleID()
|
_id = generateRoleID()
|
||||||
|
@ -69,7 +69,7 @@ exports.save = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function (ctx) {
|
exports.destroy = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const roleId = ctx.params.roleId
|
const roleId = ctx.params.roleId
|
||||||
if (isBuiltin(roleId)) {
|
if (isBuiltin(roleId)) {
|
||||||
ctx.throw(400, "Cannot delete builtin role.")
|
ctx.throw(400, "Cannot delete builtin role.")
|
||||||
|
|
|
@ -39,12 +39,11 @@ Routing.prototype.addScreenId = function (fullpath, roleId, screenId) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the full routing structure by querying the routing view and processing the result into the tree.
|
* Gets the full routing structure by querying the routing view and processing the result into the tree.
|
||||||
* @param {string} appId The application to produce the routing structure for.
|
|
||||||
* @returns {Promise<object>} The routing structure, this is the full structure designed for use in the builder,
|
* @returns {Promise<object>} The routing structure, this is the full structure designed for use in the builder,
|
||||||
* if the client routing is required then the updateRoutingStructureForUserRole should be used.
|
* if the client routing is required then the updateRoutingStructureForUserRole should be used.
|
||||||
*/
|
*/
|
||||||
async function getRoutingStructure(appId) {
|
async function getRoutingStructure() {
|
||||||
const screenRoutes = await getRoutingInfo(appId)
|
const screenRoutes = await getRoutingInfo()
|
||||||
const routing = new Routing()
|
const routing = new Routing()
|
||||||
|
|
||||||
for (let screenRoute of screenRoutes) {
|
for (let screenRoute of screenRoutes) {
|
||||||
|
@ -57,13 +56,13 @@ async function getRoutingStructure(appId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
ctx.body = await getRoutingStructure(ctx.appId)
|
ctx.body = await getRoutingStructure()
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.clientFetch = async ctx => {
|
exports.clientFetch = async ctx => {
|
||||||
const routing = await getRoutingStructure(ctx.appId)
|
const routing = await getRoutingStructure()
|
||||||
let roleId = ctx.user.role._id
|
let roleId = ctx.user.role._id
|
||||||
const roleIds = await getUserRoleHierarchy(ctx.appId, roleId)
|
const roleIds = await getUserRoleHierarchy(roleId)
|
||||||
for (let topLevel of Object.values(routing.routes)) {
|
for (let topLevel of Object.values(routing.routes)) {
|
||||||
for (let subpathKey of Object.keys(topLevel.subpaths)) {
|
for (let subpathKey of Object.keys(topLevel.subpaths)) {
|
||||||
let found = false
|
let found = false
|
||||||
|
|
|
@ -19,6 +19,19 @@ import {
|
||||||
isRowId,
|
isRowId,
|
||||||
convertRowId,
|
convertRowId,
|
||||||
} from "../../../integrations/utils"
|
} from "../../../integrations/utils"
|
||||||
|
import { getDatasourceAndQuery } from "./utils"
|
||||||
|
import {
|
||||||
|
DataSourceOperation,
|
||||||
|
FieldTypes,
|
||||||
|
RelationshipTypes,
|
||||||
|
} from "../../../constants"
|
||||||
|
import { breakExternalTableId, isSQL } from "../../../integrations/utils"
|
||||||
|
import { processObjectSync } from "@budibase/string-templates"
|
||||||
|
// @ts-ignore
|
||||||
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
import { processFormulas } from "../../../utilities/rowProcessor/utils"
|
||||||
|
// @ts-ignore
|
||||||
|
import { getAppDB } from "@budibase/backend-core/context"
|
||||||
|
|
||||||
interface ManyRelationship {
|
interface ManyRelationship {
|
||||||
tableId?: string
|
tableId?: string
|
||||||
|
@ -38,18 +51,6 @@ interface RunConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
module External {
|
module External {
|
||||||
const { getDatasourceAndQuery } = require("./utils")
|
|
||||||
const {
|
|
||||||
DataSourceOperation,
|
|
||||||
FieldTypes,
|
|
||||||
RelationshipTypes,
|
|
||||||
} = require("../../../constants")
|
|
||||||
const { breakExternalTableId, isSQL } = require("../../../integrations/utils")
|
|
||||||
const { processObjectSync } = require("@budibase/string-templates")
|
|
||||||
const { cloneDeep } = require("lodash/fp")
|
|
||||||
const CouchDB = require("../../../db")
|
|
||||||
const { processFormulas } = require("../../../utilities/rowProcessor/utils")
|
|
||||||
|
|
||||||
function buildFilters(
|
function buildFilters(
|
||||||
id: string | undefined,
|
id: string | undefined,
|
||||||
filters: SearchFilters,
|
filters: SearchFilters,
|
||||||
|
@ -210,19 +211,12 @@ module External {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExternalRequest {
|
class ExternalRequest {
|
||||||
private readonly appId: string
|
|
||||||
private operation: Operation
|
private operation: Operation
|
||||||
private tableId: string
|
private tableId: string
|
||||||
private datasource: Datasource
|
private datasource: Datasource
|
||||||
private tables: { [key: string]: Table } = {}
|
private tables: { [key: string]: Table } = {}
|
||||||
|
|
||||||
constructor(
|
constructor(operation: Operation, tableId: string, datasource: Datasource) {
|
||||||
appId: string,
|
|
||||||
operation: Operation,
|
|
||||||
tableId: string,
|
|
||||||
datasource: Datasource
|
|
||||||
) {
|
|
||||||
this.appId = appId
|
|
||||||
this.operation = operation
|
this.operation = operation
|
||||||
this.tableId = tableId
|
this.tableId = tableId
|
||||||
this.datasource = datasource
|
this.datasource = datasource
|
||||||
|
@ -231,12 +225,14 @@ module External {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getTable(tableId: string | undefined): Table {
|
getTable(tableId: string | undefined): Table | undefined {
|
||||||
if (!tableId) {
|
if (!tableId) {
|
||||||
throw "Table ID is unknown, cannot find table"
|
throw "Table ID is unknown, cannot find table"
|
||||||
}
|
}
|
||||||
const { tableName } = breakExternalTableId(tableId)
|
const { tableName } = breakExternalTableId(tableId)
|
||||||
return this.tables[tableName]
|
if (tableName) {
|
||||||
|
return this.tables[tableName]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inputProcessing(row: Row | undefined, table: Table) {
|
inputProcessing(row: Row | undefined, table: Table) {
|
||||||
|
@ -272,9 +268,11 @@ module External {
|
||||||
newRow[key] = row[key]
|
newRow[key] = row[key]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const { tableName: linkTableName } = breakExternalTableId(field.tableId)
|
const { tableName: linkTableName } = breakExternalTableId(
|
||||||
|
field?.tableId
|
||||||
|
)
|
||||||
// table has to exist for many to many
|
// table has to exist for many to many
|
||||||
if (!this.tables[linkTableName]) {
|
if (!linkTableName || !this.tables[linkTableName]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const linkTable = this.tables[linkTableName]
|
const linkTable = this.tables[linkTableName]
|
||||||
|
@ -422,7 +420,7 @@ module External {
|
||||||
}
|
}
|
||||||
const { tableName: linkTableName } = breakExternalTableId(field.tableId)
|
const { tableName: linkTableName } = breakExternalTableId(field.tableId)
|
||||||
// no table to link to, this is not a valid relationships
|
// no table to link to, this is not a valid relationships
|
||||||
if (!this.tables[linkTableName]) {
|
if (!linkTableName || !this.tables[linkTableName]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const linkTable = this.tables[linkTableName]
|
const linkTable = this.tables[linkTableName]
|
||||||
|
@ -460,6 +458,9 @@ module External {
|
||||||
async lookupRelations(tableId: string, row: Row) {
|
async lookupRelations(tableId: string, row: Row) {
|
||||||
const related: { [key: string]: any } = {}
|
const related: { [key: string]: any } = {}
|
||||||
const { tableName } = breakExternalTableId(tableId)
|
const { tableName } = breakExternalTableId(tableId)
|
||||||
|
if (!tableName) {
|
||||||
|
return related
|
||||||
|
}
|
||||||
const table = this.tables[tableName]
|
const table = this.tables[tableName]
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const primaryKey = table.primary[0]
|
const primaryKey = table.primary[0]
|
||||||
|
@ -484,7 +485,7 @@ module External {
|
||||||
if (!lookupField || !row[lookupField]) {
|
if (!lookupField || !row[lookupField]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const response = await getDatasourceAndQuery(this.appId, {
|
const response = await getDatasourceAndQuery({
|
||||||
endpoint: getEndpoint(tableId, DataSourceOperation.READ),
|
endpoint: getEndpoint(tableId, DataSourceOperation.READ),
|
||||||
filters: {
|
filters: {
|
||||||
equal: {
|
equal: {
|
||||||
|
@ -515,28 +516,30 @@ module External {
|
||||||
row: Row,
|
row: Row,
|
||||||
relationships: ManyRelationship[]
|
relationships: ManyRelationship[]
|
||||||
) {
|
) {
|
||||||
const { appId } = this
|
|
||||||
// if we're creating (in a through table) need to wipe the existing ones first
|
// if we're creating (in a through table) need to wipe the existing ones first
|
||||||
const promises = []
|
const promises = []
|
||||||
const related = await this.lookupRelations(mainTableId, row)
|
const related = await this.lookupRelations(mainTableId, row)
|
||||||
for (let relationship of relationships) {
|
for (let relationship of relationships) {
|
||||||
const { key, tableId, isUpdate, id, ...rest } = relationship
|
const { key, tableId, isUpdate, id, ...rest } = relationship
|
||||||
const body = processObjectSync(rest, row)
|
const body: { [key: string]: any } = processObjectSync(rest, row, {})
|
||||||
const linkTable = this.getTable(tableId)
|
const linkTable = this.getTable(tableId)
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const linkPrimary = linkTable.primary[0]
|
const linkPrimary = linkTable?.primary[0]
|
||||||
|
if (!linkTable || !linkPrimary) {
|
||||||
|
return
|
||||||
|
}
|
||||||
const rows = related[key].rows || []
|
const rows = related[key].rows || []
|
||||||
const found = rows.find(
|
const found = rows.find(
|
||||||
(row: { [key: string]: any }) =>
|
(row: { [key: string]: any }) =>
|
||||||
row[linkPrimary] === relationship.id ||
|
row[linkPrimary] === relationship.id ||
|
||||||
row[linkPrimary] === body[linkPrimary]
|
row[linkPrimary] === body?.[linkPrimary]
|
||||||
)
|
)
|
||||||
const operation = isUpdate
|
const operation = isUpdate
|
||||||
? DataSourceOperation.UPDATE
|
? DataSourceOperation.UPDATE
|
||||||
: DataSourceOperation.CREATE
|
: DataSourceOperation.CREATE
|
||||||
if (!found) {
|
if (!found) {
|
||||||
promises.push(
|
promises.push(
|
||||||
getDatasourceAndQuery(appId, {
|
getDatasourceAndQuery({
|
||||||
endpoint: getEndpoint(tableId, operation),
|
endpoint: getEndpoint(tableId, operation),
|
||||||
// if we're doing many relationships then we're writing, only one response
|
// if we're doing many relationships then we're writing, only one response
|
||||||
body,
|
body,
|
||||||
|
@ -552,9 +555,12 @@ module External {
|
||||||
for (let [colName, { isMany, rows, tableId }] of Object.entries(
|
for (let [colName, { isMany, rows, tableId }] of Object.entries(
|
||||||
related
|
related
|
||||||
)) {
|
)) {
|
||||||
const table: Table = this.getTable(tableId)
|
const table: Table | undefined = this.getTable(tableId)
|
||||||
// if its not the foreign key skip it, nothing to do
|
// if its not the foreign key skip it, nothing to do
|
||||||
if (table.primary && table.primary.indexOf(colName) !== -1) {
|
if (
|
||||||
|
!table ||
|
||||||
|
(table.primary && table.primary.indexOf(colName) !== -1)
|
||||||
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for (let row of rows) {
|
for (let row of rows) {
|
||||||
|
@ -566,7 +572,7 @@ module External {
|
||||||
: DataSourceOperation.UPDATE
|
: DataSourceOperation.UPDATE
|
||||||
const body = isMany ? null : { [colName]: null }
|
const body = isMany ? null : { [colName]: null }
|
||||||
promises.push(
|
promises.push(
|
||||||
getDatasourceAndQuery(this.appId, {
|
getDatasourceAndQuery({
|
||||||
endpoint: getEndpoint(tableId, op),
|
endpoint: getEndpoint(tableId, op),
|
||||||
body,
|
body,
|
||||||
filters,
|
filters,
|
||||||
|
@ -605,20 +611,25 @@ module External {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const { tableName: linkTableName } = breakExternalTableId(field.tableId)
|
const { tableName: linkTableName } = breakExternalTableId(field.tableId)
|
||||||
const linkTable = this.tables[linkTableName]
|
if (linkTableName) {
|
||||||
if (linkTable) {
|
const linkTable = this.tables[linkTableName]
|
||||||
const linkedFields = extractRealFields(linkTable, fields)
|
if (linkTable) {
|
||||||
fields = fields.concat(linkedFields)
|
const linkedFields = extractRealFields(linkTable, fields)
|
||||||
|
fields = fields.concat(linkedFields)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fields
|
return fields
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(config: RunConfig) {
|
async run(config: RunConfig) {
|
||||||
const { appId, operation, tableId } = this
|
const { operation, tableId } = this
|
||||||
let { datasourceId, tableName } = breakExternalTableId(tableId)
|
let { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||||
|
if (!tableName) {
|
||||||
|
throw "Unable to run without a table name"
|
||||||
|
}
|
||||||
if (!this.datasource) {
|
if (!this.datasource) {
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
this.datasource = await db.get(datasourceId)
|
this.datasource = await db.get(datasourceId)
|
||||||
if (!this.datasource || !this.datasource.entities) {
|
if (!this.datasource || !this.datasource.entities) {
|
||||||
throw "No tables found, fetch tables before query."
|
throw "No tables found, fetch tables before query."
|
||||||
|
@ -670,7 +681,7 @@ module External {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
// can't really use response right now
|
// can't really use response right now
|
||||||
const response = await getDatasourceAndQuery(appId, json)
|
const response = await getDatasourceAndQuery(json)
|
||||||
// handle many to many relationships now if we know the ID (could be auto increment)
|
// handle many to many relationships now if we know the ID (could be auto increment)
|
||||||
if (
|
if (
|
||||||
operation !== DataSourceOperation.READ &&
|
operation !== DataSourceOperation.READ &&
|
||||||
|
|
|
@ -9,9 +9,9 @@ const {
|
||||||
breakRowIdField,
|
breakRowIdField,
|
||||||
} = require("../../../integrations/utils")
|
} = require("../../../integrations/utils")
|
||||||
const ExternalRequest = require("./ExternalRequest")
|
const ExternalRequest = require("./ExternalRequest")
|
||||||
const CouchDB = require("../../../db")
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
async function handleRequest(appId, operation, tableId, opts = {}) {
|
async function handleRequest(operation, tableId, opts = {}) {
|
||||||
// make sure the filters are cleaned up, no empty strings for equals, fuzzy or string
|
// make sure the filters are cleaned up, no empty strings for equals, fuzzy or string
|
||||||
if (opts && opts.filters) {
|
if (opts && opts.filters) {
|
||||||
for (let filterField of NoEmptyFilterStrings) {
|
for (let filterField of NoEmptyFilterStrings) {
|
||||||
|
@ -25,9 +25,7 @@ async function handleRequest(appId, operation, tableId, opts = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new ExternalRequest(appId, operation, tableId, opts.datasource).run(
|
return new ExternalRequest(operation, tableId, opts.datasource).run(opts)
|
||||||
opts
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.handleRequest = handleRequest
|
exports.handleRequest = handleRequest
|
||||||
|
@ -181,7 +179,7 @@ exports.fetchEnrichedRow = async ctx => {
|
||||||
const id = ctx.params.rowId
|
const id = ctx.params.rowId
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
const { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const datasource = await db.get(datasourceId)
|
const datasource = await db.get(datasourceId)
|
||||||
if (!datasource || !datasource.entities) {
|
if (!datasource || !datasource.entities) {
|
||||||
ctx.throw(400, "Datasource has not been configured for plus API.")
|
ctx.throw(400, "Datasource has not been configured for plus API.")
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
const CouchDB = require("../../../db")
|
|
||||||
const linkRows = require("../../../db/linkedRows")
|
const linkRows = require("../../../db/linkedRows")
|
||||||
const {
|
const {
|
||||||
getRowParams,
|
|
||||||
generateRowID,
|
generateRowID,
|
||||||
|
getRowParams,
|
||||||
DocumentTypes,
|
DocumentTypes,
|
||||||
InternalTables,
|
InternalTables,
|
||||||
} = require("../../../db/utils")
|
} = require("../../../db/utils")
|
||||||
|
@ -10,11 +9,9 @@ const userController = require("../user")
|
||||||
const {
|
const {
|
||||||
inputProcessing,
|
inputProcessing,
|
||||||
outputProcessing,
|
outputProcessing,
|
||||||
processAutoColumn,
|
|
||||||
cleanupAttachments,
|
cleanupAttachments,
|
||||||
} = require("../../../utilities/rowProcessor")
|
} = require("../../../utilities/rowProcessor")
|
||||||
const { FieldTypes } = require("../../../constants")
|
const { FieldTypes } = require("../../../constants")
|
||||||
const { isEqual } = require("lodash")
|
|
||||||
const { validate, findRow } = require("./utils")
|
const { validate, findRow } = require("./utils")
|
||||||
const { fullSearch, paginatedSearch } = require("./internalSearch")
|
const { fullSearch, paginatedSearch } = require("./internalSearch")
|
||||||
const { getGlobalUsersFromMetadata } = require("../../../utilities/global")
|
const { getGlobalUsersFromMetadata } = require("../../../utilities/global")
|
||||||
|
@ -27,6 +24,8 @@ const {
|
||||||
getFromMemoryDoc,
|
getFromMemoryDoc,
|
||||||
} = require("../view/utils")
|
} = require("../view/utils")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
const { finaliseRow, updateRelatedFormula } = require("./staticFormula")
|
||||||
|
|
||||||
const CALCULATION_TYPES = {
|
const CALCULATION_TYPES = {
|
||||||
SUM: "sum",
|
SUM: "sum",
|
||||||
|
@ -34,51 +33,6 @@ const CALCULATION_TYPES = {
|
||||||
STATS: "stats",
|
STATS: "stats",
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeResponse(ctx, db, row, oldTable, table) {
|
|
||||||
row.type = "row"
|
|
||||||
// don't worry about rev, tables handle rev/lastID updates
|
|
||||||
// if another row has been written since processing this will
|
|
||||||
// handle the auto ID clash
|
|
||||||
if (!isEqual(oldTable, table)) {
|
|
||||||
try {
|
|
||||||
await db.put(table)
|
|
||||||
} catch (err) {
|
|
||||||
if (err.status === 409) {
|
|
||||||
const updatedTable = await db.get(table._id)
|
|
||||||
let response = processAutoColumn(null, updatedTable, row, {
|
|
||||||
reprocessing: true,
|
|
||||||
})
|
|
||||||
await db.put(response.table)
|
|
||||||
row = response.row
|
|
||||||
} else {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const response = await db.put(row)
|
|
||||||
row._rev = response.rev
|
|
||||||
// process the row before return, to include relationships
|
|
||||||
row = await outputProcessing(ctx, table, row, { squash: false })
|
|
||||||
return { row, table }
|
|
||||||
}
|
|
||||||
|
|
||||||
// doesn't do the outputProcessing
|
|
||||||
async function getRawTableData(ctx, db, tableId) {
|
|
||||||
let rows
|
|
||||||
if (tableId === InternalTables.USER_METADATA) {
|
|
||||||
await userController.fetchMetadata(ctx)
|
|
||||||
rows = ctx.body
|
|
||||||
} else {
|
|
||||||
const response = await db.allDocs(
|
|
||||||
getRowParams(tableId, null, {
|
|
||||||
include_docs: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
rows = response.rows.map(row => row.doc)
|
|
||||||
}
|
|
||||||
return rows
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getView(db, viewName) {
|
async function getView(db, viewName) {
|
||||||
let mainGetter = env.SELF_HOSTED ? getFromDesignDoc : getFromMemoryDoc
|
let mainGetter = env.SELF_HOSTED ? getFromDesignDoc : getFromMemoryDoc
|
||||||
let secondaryGetter = env.SELF_HOSTED ? getFromMemoryDoc : getFromDesignDoc
|
let secondaryGetter = env.SELF_HOSTED ? getFromMemoryDoc : getFromDesignDoc
|
||||||
|
@ -105,9 +59,24 @@ async function getView(db, viewName) {
|
||||||
return viewInfo
|
return viewInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function getRawTableData(ctx, db, tableId) {
|
||||||
|
let rows
|
||||||
|
if (tableId === InternalTables.USER_METADATA) {
|
||||||
|
await userController.fetchMetadata(ctx)
|
||||||
|
rows = ctx.body
|
||||||
|
} else {
|
||||||
|
const response = await db.allDocs(
|
||||||
|
getRowParams(tableId, null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
rows = response.rows.map(row => row.doc)
|
||||||
|
}
|
||||||
|
return rows
|
||||||
|
}
|
||||||
|
|
||||||
exports.patch = async ctx => {
|
exports.patch = async ctx => {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const inputs = ctx.request.body
|
const inputs = ctx.request.body
|
||||||
const tableId = inputs.tableId
|
const tableId = inputs.tableId
|
||||||
const isUserTable = tableId === InternalTables.USER_METADATA
|
const isUserTable = tableId === InternalTables.USER_METADATA
|
||||||
|
@ -146,14 +115,13 @@ exports.patch = async ctx => {
|
||||||
|
|
||||||
// returned row is cleaned and prepared for writing to DB
|
// returned row is cleaned and prepared for writing to DB
|
||||||
row = await linkRows.updateLinks({
|
row = await linkRows.updateLinks({
|
||||||
appId,
|
|
||||||
eventType: linkRows.EventType.ROW_UPDATE,
|
eventType: linkRows.EventType.ROW_UPDATE,
|
||||||
row,
|
row,
|
||||||
tableId: row.tableId,
|
tableId: row.tableId,
|
||||||
table,
|
table,
|
||||||
})
|
})
|
||||||
// check if any attachments removed
|
// check if any attachments removed
|
||||||
await cleanupAttachments(appId, table, { oldRow, row })
|
await cleanupAttachments(table, { oldRow, row })
|
||||||
|
|
||||||
if (isUserTable) {
|
if (isUserTable) {
|
||||||
// the row has been updated, need to put it into the ctx
|
// the row has been updated, need to put it into the ctx
|
||||||
|
@ -162,12 +130,14 @@ exports.patch = async ctx => {
|
||||||
return { row: ctx.body, table }
|
return { row: ctx.body, table }
|
||||||
}
|
}
|
||||||
|
|
||||||
return storeResponse(ctx, db, row, dbTable, table)
|
return finaliseRow(table, row, {
|
||||||
|
oldTable: dbTable,
|
||||||
|
updateFormula: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
exports.save = async function (ctx) {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
let inputs = ctx.request.body
|
let inputs = ctx.request.body
|
||||||
inputs.tableId = ctx.params.tableId
|
inputs.tableId = ctx.params.tableId
|
||||||
|
|
||||||
|
@ -189,18 +159,19 @@ exports.save = async function (ctx) {
|
||||||
|
|
||||||
// make sure link rows are up to date
|
// make sure link rows are up to date
|
||||||
row = await linkRows.updateLinks({
|
row = await linkRows.updateLinks({
|
||||||
appId,
|
|
||||||
eventType: linkRows.EventType.ROW_SAVE,
|
eventType: linkRows.EventType.ROW_SAVE,
|
||||||
row,
|
row,
|
||||||
tableId: row.tableId,
|
tableId: row.tableId,
|
||||||
table,
|
table,
|
||||||
})
|
})
|
||||||
|
|
||||||
return storeResponse(ctx, db, row, dbTable, table)
|
return finaliseRow(table, row, {
|
||||||
|
oldTable: dbTable,
|
||||||
|
updateFormula: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchView = async ctx => {
|
exports.fetchView = async ctx => {
|
||||||
const appId = ctx.appId
|
|
||||||
const viewName = ctx.params.viewName
|
const viewName = ctx.params.viewName
|
||||||
|
|
||||||
// if this is a table view being looked for just transfer to that
|
// if this is a table view being looked for just transfer to that
|
||||||
|
@ -209,7 +180,7 @@ exports.fetchView = async ctx => {
|
||||||
return exports.fetch(ctx)
|
return exports.fetch(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const { calculation, group, field } = ctx.query
|
const { calculation, group, field } = ctx.query
|
||||||
const viewInfo = await getView(db, viewName)
|
const viewInfo = await getView(db, viewName)
|
||||||
let response
|
let response
|
||||||
|
@ -236,7 +207,7 @@ exports.fetchView = async ctx => {
|
||||||
schema: {},
|
schema: {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rows = await outputProcessing(ctx, table, response.rows)
|
rows = await outputProcessing(table, response.rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (calculation === CALCULATION_TYPES.STATS) {
|
if (calculation === CALCULATION_TYPES.STATS) {
|
||||||
|
@ -263,27 +234,24 @@ exports.fetchView = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
|
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
let table = await db.get(tableId)
|
let table = await db.get(tableId)
|
||||||
let rows = await getRawTableData(ctx, db, tableId)
|
let rows = await getRawTableData(ctx, db, tableId)
|
||||||
return outputProcessing(ctx, table, rows)
|
return outputProcessing(table, rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async ctx => {
|
exports.find = async ctx => {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const table = await db.get(ctx.params.tableId)
|
const table = await db.get(ctx.params.tableId)
|
||||||
let row = await findRow(ctx, db, ctx.params.tableId, ctx.params.rowId)
|
let row = await findRow(ctx, ctx.params.tableId, ctx.params.rowId)
|
||||||
row = await outputProcessing(ctx, table, row)
|
row = await outputProcessing(table, row)
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function (ctx) {
|
exports.destroy = async function (ctx) {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const { _id, _rev } = ctx.request.body
|
const { _id, _rev } = ctx.request.body
|
||||||
let row = await db.get(_id)
|
let row = await db.get(_id)
|
||||||
|
|
||||||
|
@ -292,16 +260,17 @@ exports.destroy = async function (ctx) {
|
||||||
}
|
}
|
||||||
const table = await db.get(row.tableId)
|
const table = await db.get(row.tableId)
|
||||||
// update the row to include full relationships before deleting them
|
// update the row to include full relationships before deleting them
|
||||||
row = await outputProcessing(ctx, table, row, { squash: false })
|
row = await outputProcessing(table, row, { squash: false })
|
||||||
// now remove the relationships
|
// now remove the relationships
|
||||||
await linkRows.updateLinks({
|
await linkRows.updateLinks({
|
||||||
appId,
|
|
||||||
eventType: linkRows.EventType.ROW_DELETE,
|
eventType: linkRows.EventType.ROW_DELETE,
|
||||||
row,
|
row,
|
||||||
tableId: row.tableId,
|
tableId: row.tableId,
|
||||||
})
|
})
|
||||||
// remove any attachments that were on the row from object storage
|
// remove any attachments that were on the row from object storage
|
||||||
await cleanupAttachments(appId, table, { row })
|
await cleanupAttachments(table, { row })
|
||||||
|
// remove any static formula
|
||||||
|
await updateRelatedFormula(table, row)
|
||||||
|
|
||||||
let response
|
let response
|
||||||
if (ctx.params.tableId === InternalTables.USER_METADATA) {
|
if (ctx.params.tableId === InternalTables.USER_METADATA) {
|
||||||
|
@ -317,20 +286,18 @@ exports.destroy = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.bulkDestroy = async ctx => {
|
exports.bulkDestroy = async ctx => {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const table = await db.get(tableId)
|
const table = await db.get(tableId)
|
||||||
let { rows } = ctx.request.body
|
let { rows } = ctx.request.body
|
||||||
|
|
||||||
// before carrying out any updates, make sure the rows are ready to be returned
|
// before carrying out any updates, make sure the rows are ready to be returned
|
||||||
// they need to be the full rows (including previous relationships) for automations
|
// they need to be the full rows (including previous relationships) for automations
|
||||||
rows = await outputProcessing(ctx, table, rows, { squash: false })
|
rows = await outputProcessing(table, rows, { squash: false })
|
||||||
|
|
||||||
// remove the relationships first
|
// remove the relationships first
|
||||||
let updates = rows.map(row =>
|
let updates = rows.map(row =>
|
||||||
linkRows.updateLinks({
|
linkRows.updateLinks({
|
||||||
appId,
|
|
||||||
eventType: linkRows.EventType.ROW_DELETE,
|
eventType: linkRows.EventType.ROW_DELETE,
|
||||||
row,
|
row,
|
||||||
tableId: row.tableId,
|
tableId: row.tableId,
|
||||||
|
@ -349,7 +316,8 @@ exports.bulkDestroy = async ctx => {
|
||||||
await db.bulkDocs(rows.map(row => ({ ...row, _deleted: true })))
|
await db.bulkDocs(rows.map(row => ({ ...row, _deleted: true })))
|
||||||
}
|
}
|
||||||
// remove any attachments that were on the rows from object storage
|
// remove any attachments that were on the rows from object storage
|
||||||
await cleanupAttachments(appId, table, { rows })
|
await cleanupAttachments(table, { rows })
|
||||||
|
await updateRelatedFormula(table, rows)
|
||||||
await Promise.all(updates)
|
await Promise.all(updates)
|
||||||
return { response: { ok: true }, rows }
|
return { response: { ok: true }, rows }
|
||||||
}
|
}
|
||||||
|
@ -360,28 +328,27 @@ exports.search = async ctx => {
|
||||||
return { rows: await exports.fetch(ctx) }
|
return { rows: await exports.fetch(ctx) }
|
||||||
}
|
}
|
||||||
|
|
||||||
const appId = ctx.appId
|
|
||||||
const { tableId } = ctx.params
|
const { tableId } = ctx.params
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const { paginate, query, ...params } = ctx.request.body
|
const { paginate, query, ...params } = ctx.request.body
|
||||||
params.version = ctx.version
|
params.version = ctx.version
|
||||||
params.tableId = tableId
|
params.tableId = tableId
|
||||||
|
|
||||||
let response
|
let response
|
||||||
if (paginate) {
|
if (paginate) {
|
||||||
response = await paginatedSearch(appId, query, params)
|
response = await paginatedSearch(query, params)
|
||||||
} else {
|
} else {
|
||||||
response = await fullSearch(appId, query, params)
|
response = await fullSearch(query, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enrich search results with relationships
|
// Enrich search results with relationships
|
||||||
if (response.rows && response.rows.length) {
|
if (response.rows && response.rows.length) {
|
||||||
// enrich with global users if from users table
|
// enrich with global users if from users table
|
||||||
if (tableId === InternalTables.USER_METADATA) {
|
if (tableId === InternalTables.USER_METADATA) {
|
||||||
response.rows = await getGlobalUsersFromMetadata(appId, response.rows)
|
response.rows = await getGlobalUsersFromMetadata(response.rows)
|
||||||
}
|
}
|
||||||
const table = await db.get(tableId)
|
const table = await db.get(tableId)
|
||||||
response.rows = await outputProcessing(ctx, table, response.rows)
|
response.rows = await outputProcessing(table, response.rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -389,25 +356,22 @@ exports.search = async ctx => {
|
||||||
|
|
||||||
exports.validate = async ctx => {
|
exports.validate = async ctx => {
|
||||||
return validate({
|
return validate({
|
||||||
appId: ctx.appId,
|
|
||||||
tableId: ctx.params.tableId,
|
tableId: ctx.params.tableId,
|
||||||
row: ctx.request.body,
|
row: ctx.request.body,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchEnrichedRow = async ctx => {
|
exports.fetchEnrichedRow = async ctx => {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const tableId = ctx.params.tableId
|
const tableId = ctx.params.tableId
|
||||||
const rowId = ctx.params.rowId
|
const rowId = ctx.params.rowId
|
||||||
// need table to work out where links go in row
|
// need table to work out where links go in row
|
||||||
let [table, row] = await Promise.all([
|
let [table, row] = await Promise.all([
|
||||||
db.get(tableId),
|
db.get(tableId),
|
||||||
findRow(ctx, db, tableId, rowId),
|
findRow(ctx, tableId, rowId),
|
||||||
])
|
])
|
||||||
// get the link docs
|
// get the link docs
|
||||||
const linkVals = await linkRows.getLinkDocuments({
|
const linkVals = await linkRows.getLinkDocuments({
|
||||||
appId,
|
|
||||||
tableId,
|
tableId,
|
||||||
rowId,
|
rowId,
|
||||||
})
|
})
|
||||||
|
@ -434,7 +398,7 @@ exports.fetchEnrichedRow = async ctx => {
|
||||||
for (let [tableId, rows] of Object.entries(groups)) {
|
for (let [tableId, rows] of Object.entries(groups)) {
|
||||||
// need to include the IDs in these rows for any links they may have
|
// need to include the IDs in these rows for any links they may have
|
||||||
linkedRows = linkedRows.concat(
|
linkedRows = linkedRows.concat(
|
||||||
await outputProcessing(ctx, tables[tableId], rows)
|
await outputProcessing(tables[tableId], rows)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
const { SearchIndexes } = require("../../../db/utils")
|
const { SearchIndexes } = require("../../../db/utils")
|
||||||
const fetch = require("node-fetch")
|
const fetch = require("node-fetch")
|
||||||
const { getCouchUrl } = require("@budibase/backend-core/db")
|
const { getCouchUrl } = require("@budibase/backend-core/db")
|
||||||
|
const { getAppId } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class to build lucene query URLs.
|
* Class to build lucene query URLs.
|
||||||
* Optionally takes a base lucene query object.
|
* Optionally takes a base lucene query object.
|
||||||
*/
|
*/
|
||||||
class QueryBuilder {
|
class QueryBuilder {
|
||||||
constructor(appId, base) {
|
constructor(base) {
|
||||||
this.appId = appId
|
|
||||||
this.query = {
|
this.query = {
|
||||||
string: {},
|
string: {},
|
||||||
fuzzy: {},
|
fuzzy: {},
|
||||||
|
@ -37,22 +37,30 @@ class QueryBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
setLimit(limit) {
|
setLimit(limit) {
|
||||||
this.limit = limit
|
if (limit != null) {
|
||||||
|
this.limit = limit
|
||||||
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
setSort(sort) {
|
setSort(sort) {
|
||||||
this.sort = sort
|
if (sort != null) {
|
||||||
|
this.sort = sort
|
||||||
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
setSortOrder(sortOrder) {
|
setSortOrder(sortOrder) {
|
||||||
this.sortOrder = sortOrder
|
if (sortOrder != null) {
|
||||||
|
this.sortOrder = sortOrder
|
||||||
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
setSortType(sortType) {
|
setSortType(sortType) {
|
||||||
this.sortType = sortType
|
if (sortType != null) {
|
||||||
|
this.sortType = sortType
|
||||||
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,7 +241,8 @@ class QueryBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
async run() {
|
async run() {
|
||||||
const url = `${getCouchUrl()}/${this.appId}/_design/database/_search/${
|
const appId = getAppId()
|
||||||
|
const url = `${getCouchUrl()}/${appId}/_design/database/_search/${
|
||||||
SearchIndexes.ROWS
|
SearchIndexes.ROWS
|
||||||
}`
|
}`
|
||||||
const body = this.buildSearchBody()
|
const body = this.buildSearchBody()
|
||||||
|
@ -270,7 +279,6 @@ const runQuery = async (url, body) => {
|
||||||
* Gets round the fixed limit of 200 results from a query by fetching as many
|
* Gets round the fixed limit of 200 results from a query by fetching as many
|
||||||
* pages as required and concatenating the results. This recursively operates
|
* pages as required and concatenating the results. This recursively operates
|
||||||
* until enough results have been found.
|
* until enough results have been found.
|
||||||
* @param appId {string} The app ID to search
|
|
||||||
* @param query {object} The JSON query structure
|
* @param query {object} The JSON query structure
|
||||||
* @param params {object} The search params including:
|
* @param params {object} The search params including:
|
||||||
* tableId {string} The table ID to search
|
* tableId {string} The table ID to search
|
||||||
|
@ -283,7 +291,7 @@ const runQuery = async (url, body) => {
|
||||||
* rows {array|null} Current results in the recursive search
|
* rows {array|null} Current results in the recursive search
|
||||||
* @returns {Promise<*[]|*>}
|
* @returns {Promise<*[]|*>}
|
||||||
*/
|
*/
|
||||||
const recursiveSearch = async (appId, query, params) => {
|
const recursiveSearch = async (query, params) => {
|
||||||
const bookmark = params.bookmark
|
const bookmark = params.bookmark
|
||||||
const rows = params.rows || []
|
const rows = params.rows || []
|
||||||
if (rows.length >= params.limit) {
|
if (rows.length >= params.limit) {
|
||||||
|
@ -293,7 +301,7 @@ const recursiveSearch = async (appId, query, params) => {
|
||||||
if (rows.length > params.limit - 200) {
|
if (rows.length > params.limit - 200) {
|
||||||
pageSize = params.limit - rows.length
|
pageSize = params.limit - rows.length
|
||||||
}
|
}
|
||||||
const page = await new QueryBuilder(appId, query)
|
const page = await new QueryBuilder(query)
|
||||||
.setVersion(params.version)
|
.setVersion(params.version)
|
||||||
.setTable(params.tableId)
|
.setTable(params.tableId)
|
||||||
.setBookmark(bookmark)
|
.setBookmark(bookmark)
|
||||||
|
@ -313,14 +321,13 @@ const recursiveSearch = async (appId, query, params) => {
|
||||||
bookmark: page.bookmark,
|
bookmark: page.bookmark,
|
||||||
rows: [...rows, ...page.rows],
|
rows: [...rows, ...page.rows],
|
||||||
}
|
}
|
||||||
return await recursiveSearch(appId, query, newParams)
|
return await recursiveSearch(query, newParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs a paginated search. A bookmark will be returned to allow the next
|
* Performs a paginated search. A bookmark will be returned to allow the next
|
||||||
* page to be fetched. There is a max limit off 200 results per page in a
|
* page to be fetched. There is a max limit off 200 results per page in a
|
||||||
* paginated search.
|
* paginated search.
|
||||||
* @param appId {string} The app ID to search
|
|
||||||
* @param query {object} The JSON query structure
|
* @param query {object} The JSON query structure
|
||||||
* @param params {object} The search params including:
|
* @param params {object} The search params including:
|
||||||
* tableId {string} The table ID to search
|
* tableId {string} The table ID to search
|
||||||
|
@ -332,13 +339,13 @@ const recursiveSearch = async (appId, query, params) => {
|
||||||
* bookmark {string} The bookmark to resume from
|
* bookmark {string} The bookmark to resume from
|
||||||
* @returns {Promise<{hasNextPage: boolean, rows: *[]}>}
|
* @returns {Promise<{hasNextPage: boolean, rows: *[]}>}
|
||||||
*/
|
*/
|
||||||
exports.paginatedSearch = async (appId, query, params) => {
|
exports.paginatedSearch = async (query, params) => {
|
||||||
let limit = params.limit
|
let limit = params.limit
|
||||||
if (limit == null || isNaN(limit) || limit < 0) {
|
if (limit == null || isNaN(limit) || limit < 0) {
|
||||||
limit = 50
|
limit = 50
|
||||||
}
|
}
|
||||||
limit = Math.min(limit, 200)
|
limit = Math.min(limit, 200)
|
||||||
const search = new QueryBuilder(appId, query)
|
const search = new QueryBuilder(query)
|
||||||
.setVersion(params.version)
|
.setVersion(params.version)
|
||||||
.setTable(params.tableId)
|
.setTable(params.tableId)
|
||||||
.setSort(params.sort)
|
.setSort(params.sort)
|
||||||
|
@ -367,7 +374,6 @@ exports.paginatedSearch = async (appId, query, params) => {
|
||||||
* desired amount of results. There is a limit of 1000 results to avoid
|
* desired amount of results. There is a limit of 1000 results to avoid
|
||||||
* heavy performance hits, and to avoid client components breaking from
|
* heavy performance hits, and to avoid client components breaking from
|
||||||
* handling too much data.
|
* handling too much data.
|
||||||
* @param appId {string} The app ID to search
|
|
||||||
* @param query {object} The JSON query structure
|
* @param query {object} The JSON query structure
|
||||||
* @param params {object} The search params including:
|
* @param params {object} The search params including:
|
||||||
* tableId {string} The table ID to search
|
* tableId {string} The table ID to search
|
||||||
|
@ -378,12 +384,12 @@ exports.paginatedSearch = async (appId, query, params) => {
|
||||||
* limit {number} The desired number of results
|
* limit {number} The desired number of results
|
||||||
* @returns {Promise<{rows: *}>}
|
* @returns {Promise<{rows: *}>}
|
||||||
*/
|
*/
|
||||||
exports.fullSearch = async (appId, query, params) => {
|
exports.fullSearch = async (query, params) => {
|
||||||
let limit = params.limit
|
let limit = params.limit
|
||||||
if (limit == null || isNaN(limit) || limit < 0) {
|
if (limit == null || isNaN(limit) || limit < 0) {
|
||||||
limit = 1000
|
limit = 1000
|
||||||
}
|
}
|
||||||
params.limit = Math.min(limit, 1000)
|
params.limit = Math.min(limit, 1000)
|
||||||
const rows = await recursiveSearch(appId, query, params)
|
const rows = await recursiveSearch(query, params)
|
||||||
return { rows }
|
return { rows }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
const { getRowParams } = require("../../../db/utils")
|
||||||
|
const {
|
||||||
|
outputProcessing,
|
||||||
|
processAutoColumn,
|
||||||
|
processFormulas,
|
||||||
|
} = require("../../../utilities/rowProcessor")
|
||||||
|
const { FieldTypes, FormulaTypes } = require("../../../constants")
|
||||||
|
const { isEqual } = require("lodash")
|
||||||
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function runs through a list of enriched rows, looks at the rows which
|
||||||
|
* are related and then checks if they need the state of their formulas
|
||||||
|
* updated.
|
||||||
|
* NOTE: this will only for affect static formulas.
|
||||||
|
*/
|
||||||
|
exports.updateRelatedFormula = async (table, enrichedRows) => {
|
||||||
|
const db = getAppDB()
|
||||||
|
// no formula to update, we're done
|
||||||
|
if (!table.relatedFormula) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let promises = []
|
||||||
|
for (let enrichedRow of Array.isArray(enrichedRows)
|
||||||
|
? enrichedRows
|
||||||
|
: [enrichedRows]) {
|
||||||
|
// the related rows by tableId
|
||||||
|
let relatedRows = {}
|
||||||
|
for (let [key, field] of Object.entries(enrichedRow)) {
|
||||||
|
const columnDefinition = table.schema[key]
|
||||||
|
if (columnDefinition && columnDefinition.type === FieldTypes.LINK) {
|
||||||
|
const relatedTableId = columnDefinition.tableId
|
||||||
|
if (!relatedRows[relatedTableId]) {
|
||||||
|
relatedRows[relatedTableId] = []
|
||||||
|
}
|
||||||
|
relatedRows[relatedTableId] = relatedRows[relatedTableId].concat(field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let tableId of table.relatedFormula) {
|
||||||
|
let relatedTable
|
||||||
|
try {
|
||||||
|
// no rows to update, skip
|
||||||
|
if (!relatedRows[tableId] || relatedRows[tableId].length === 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
relatedTable = await db.get(tableId)
|
||||||
|
} catch (err) {
|
||||||
|
// no error scenario, table doesn't seem to exist anymore, ignore
|
||||||
|
}
|
||||||
|
for (let column of Object.values(relatedTable.schema)) {
|
||||||
|
// needs updated in related rows
|
||||||
|
if (
|
||||||
|
column.type === FieldTypes.FORMULA &&
|
||||||
|
column.formulaType === FormulaTypes.STATIC
|
||||||
|
) {
|
||||||
|
// re-enrich rows for all the related, don't update the related formula for them
|
||||||
|
promises = promises.concat(
|
||||||
|
relatedRows[tableId].map(related =>
|
||||||
|
exports.finaliseRow(relatedTable, related, {
|
||||||
|
updateFormula: false,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(promises)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.updateAllFormulasInTable = async table => {
|
||||||
|
const db = getAppDB()
|
||||||
|
// start by getting the raw rows (which will be written back to DB after update)
|
||||||
|
let rows = (
|
||||||
|
await db.allDocs(
|
||||||
|
getRowParams(table._id, null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).rows.map(row => row.doc)
|
||||||
|
// now enrich the rows, note the clone so that we have the base state of the
|
||||||
|
// rows so that we don't write any of the enriched information back
|
||||||
|
let enrichedRows = await outputProcessing(table, cloneDeep(rows), {
|
||||||
|
squash: false,
|
||||||
|
})
|
||||||
|
const updatedRows = []
|
||||||
|
for (let row of rows) {
|
||||||
|
// find the enriched row, if found process the formulas
|
||||||
|
const enrichedRow = enrichedRows.find(enriched => enriched._id === row._id)
|
||||||
|
if (enrichedRow) {
|
||||||
|
const processed = processFormulas(table, cloneDeep(row), {
|
||||||
|
dynamic: false,
|
||||||
|
contextRows: enrichedRow,
|
||||||
|
})
|
||||||
|
// values have changed, need to add to bulk docs to update
|
||||||
|
if (!isEqual(processed, row)) {
|
||||||
|
updatedRows.push(processed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await db.bulkDocs(updatedRows)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function runs at the end of the save/patch functions of the row controller, all this
|
||||||
|
* really does is enrich the row, handle any static formula processing, then return the enriched
|
||||||
|
* row. The reason we need to return the enriched row is that the automation row created trigger
|
||||||
|
* expects the row to be totally enriched/contain all relationships.
|
||||||
|
*/
|
||||||
|
exports.finaliseRow = async (
|
||||||
|
table,
|
||||||
|
row,
|
||||||
|
{ oldTable, updateFormula } = { updateFormula: true }
|
||||||
|
) => {
|
||||||
|
const db = getAppDB()
|
||||||
|
row.type = "row"
|
||||||
|
// process the row before return, to include relationships
|
||||||
|
let enrichedRow = await outputProcessing(table, cloneDeep(row), {
|
||||||
|
squash: false,
|
||||||
|
})
|
||||||
|
// use enriched row to generate formulas for saving, specifically only use as context
|
||||||
|
row = processFormulas(table, row, {
|
||||||
|
dynamic: false,
|
||||||
|
contextRows: enrichedRow,
|
||||||
|
})
|
||||||
|
|
||||||
|
// don't worry about rev, tables handle rev/lastID updates
|
||||||
|
// if another row has been written since processing this will
|
||||||
|
// handle the auto ID clash
|
||||||
|
if (oldTable && !isEqual(oldTable, table)) {
|
||||||
|
try {
|
||||||
|
await db.put(table)
|
||||||
|
} catch (err) {
|
||||||
|
if (err.status === 409) {
|
||||||
|
const updatedTable = await db.get(table._id)
|
||||||
|
let response = processAutoColumn(null, updatedTable, row, {
|
||||||
|
reprocessing: true,
|
||||||
|
})
|
||||||
|
await db.put(response.table)
|
||||||
|
row = response.row
|
||||||
|
} else {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const response = await db.put(row)
|
||||||
|
// for response, calculate the formulas for the enriched row
|
||||||
|
enrichedRow._rev = response.rev
|
||||||
|
enrichedRow = await processFormulas(table, enrichedRow, { dynamic: false })
|
||||||
|
// this updates the related formulas in other rows based on the relations to this row
|
||||||
|
if (updateFormula) {
|
||||||
|
await exports.updateRelatedFormula(table, enrichedRow)
|
||||||
|
}
|
||||||
|
return { row: enrichedRow, table }
|
||||||
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
const validateJs = require("validate.js")
|
const validateJs = require("validate.js")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
const CouchDB = require("../../../db")
|
|
||||||
const { InternalTables } = require("../../../db/utils")
|
const { InternalTables } = require("../../../db/utils")
|
||||||
const userController = require("../user")
|
const userController = require("../user")
|
||||||
const { FieldTypes } = require("../../../constants")
|
const { FieldTypes } = require("../../../constants")
|
||||||
const { processStringSync } = require("@budibase/string-templates")
|
const { processStringSync } = require("@budibase/string-templates")
|
||||||
const { makeExternalQuery } = require("../../../integrations/base/utils")
|
const { makeExternalQuery } = require("../../../integrations/base/utils")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
validateJs.extend(validateJs.validators.datetime, {
|
validateJs.extend(validateJs.validators.datetime, {
|
||||||
parse: function (value) {
|
parse: function (value) {
|
||||||
|
@ -17,14 +17,15 @@ validateJs.extend(validateJs.validators.datetime, {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
exports.getDatasourceAndQuery = async (appId, json) => {
|
exports.getDatasourceAndQuery = async json => {
|
||||||
const datasourceId = json.endpoint.datasourceId
|
const datasourceId = json.endpoint.datasourceId
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const datasource = await db.get(datasourceId)
|
const datasource = await db.get(datasourceId)
|
||||||
return makeExternalQuery(datasource, json)
|
return makeExternalQuery(datasource, json)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.findRow = async (ctx, db, tableId, rowId) => {
|
exports.findRow = async (ctx, tableId, rowId) => {
|
||||||
|
const db = getAppDB()
|
||||||
let row
|
let row
|
||||||
// TODO remove special user case in future
|
// TODO remove special user case in future
|
||||||
if (tableId === InternalTables.USER_METADATA) {
|
if (tableId === InternalTables.USER_METADATA) {
|
||||||
|
@ -42,9 +43,9 @@ exports.findRow = async (ctx, db, tableId, rowId) => {
|
||||||
return row
|
return row
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.validate = async ({ appId, tableId, row, table }) => {
|
exports.validate = async ({ tableId, row, table }) => {
|
||||||
if (!table) {
|
if (!table) {
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
table = await db.get(tableId)
|
table = await db.get(tableId)
|
||||||
}
|
}
|
||||||
const errors = {}
|
const errors = {}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const { getScreenParams, generateScreenID } = require("../../db/utils")
|
const { getScreenParams, generateScreenID } = require("../../db/utils")
|
||||||
const { AccessController } = require("@budibase/backend-core/roles")
|
const { AccessController } = require("@budibase/backend-core/roles")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
|
|
||||||
const screens = (
|
const screens = (
|
||||||
await db.allDocs(
|
await db.allDocs(
|
||||||
|
@ -14,15 +13,14 @@ exports.fetch = async ctx => {
|
||||||
)
|
)
|
||||||
).rows.map(element => element.doc)
|
).rows.map(element => element.doc)
|
||||||
|
|
||||||
ctx.body = await new AccessController(appId).checkScreensAccess(
|
ctx.body = await new AccessController().checkScreensAccess(
|
||||||
screens,
|
screens,
|
||||||
ctx.user.role._id
|
ctx.user.role._id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async ctx => {
|
exports.save = async ctx => {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
let screen = ctx.request.body
|
let screen = ctx.request.body
|
||||||
|
|
||||||
if (!screen._id) {
|
if (!screen._id) {
|
||||||
|
@ -39,7 +37,7 @@ exports.save = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async ctx => {
|
exports.destroy = async ctx => {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
await db.remove(ctx.params.screenId, ctx.params.screenRev)
|
await db.remove(ctx.params.screenId, ctx.params.screenRev)
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Screen deleted successfully",
|
message: "Screen deleted successfully",
|
||||||
|
|
|
@ -6,7 +6,6 @@ const uuid = require("uuid")
|
||||||
const { ObjectStoreBuckets } = require("../../../constants")
|
const { ObjectStoreBuckets } = require("../../../constants")
|
||||||
const { processString } = require("@budibase/string-templates")
|
const { processString } = require("@budibase/string-templates")
|
||||||
const { getAllApps } = require("@budibase/backend-core/db")
|
const { getAllApps } = require("@budibase/backend-core/db")
|
||||||
const CouchDB = require("../../../db")
|
|
||||||
const {
|
const {
|
||||||
loadHandlebarsFile,
|
loadHandlebarsFile,
|
||||||
NODE_MODULES_PATH,
|
NODE_MODULES_PATH,
|
||||||
|
@ -17,6 +16,7 @@ const { clientLibraryPath } = require("../../../utilities")
|
||||||
const { upload } = require("../../../utilities/fileSystem")
|
const { upload } = require("../../../utilities/fileSystem")
|
||||||
const { attachmentsRelativeURL } = require("../../../utilities")
|
const { attachmentsRelativeURL } = require("../../../utilities")
|
||||||
const { DocumentTypes } = require("../../../db/utils")
|
const { DocumentTypes } = require("../../../db/utils")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
const AWS = require("aws-sdk")
|
const AWS = require("aws-sdk")
|
||||||
const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1"
|
const AWS_REGION = env.AWS_REGION ? env.AWS_REGION : "eu-west-1"
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ async function getAppIdFromUrl(ctx) {
|
||||||
let possibleAppUrl = `/${encodeURI(ctx.params.appId).toLowerCase()}`
|
let possibleAppUrl = `/${encodeURI(ctx.params.appId).toLowerCase()}`
|
||||||
|
|
||||||
// search prod apps for a url that matches, exclude dev where id is always used
|
// search prod apps for a url that matches, exclude dev where id is always used
|
||||||
const apps = await getAllApps(CouchDB, { dev: false })
|
const apps = await getAllApps({ dev: false })
|
||||||
const app = apps.filter(
|
const app = apps.filter(
|
||||||
a => a.url && a.url.toLowerCase() === possibleAppUrl
|
a => a.url && a.url.toLowerCase() === possibleAppUrl
|
||||||
)[0]
|
)[0]
|
||||||
|
@ -85,7 +85,7 @@ exports.uploadFile = async function (ctx) {
|
||||||
exports.serveApp = async function (ctx) {
|
exports.serveApp = async function (ctx) {
|
||||||
let appId = await getAppIdFromUrl(ctx)
|
let appId = await getAppIdFromUrl(ctx)
|
||||||
const App = require("./templates/BudibaseApp.svelte").default
|
const App = require("./templates/BudibaseApp.svelte").default
|
||||||
const db = new CouchDB(appId, { skip_setup: true })
|
const db = getAppDB({ skip_setup: true })
|
||||||
const appInfo = await db.get(DocumentTypes.APP_METADATA)
|
const appInfo = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
|
||||||
const { head, html, css } = App.render({
|
const { head, html, css } = App.render({
|
||||||
|
@ -111,7 +111,7 @@ exports.serveClientLibrary = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getSignedUploadURL = async function (ctx) {
|
exports.getSignedUploadURL = async function (ctx) {
|
||||||
const database = new CouchDB(ctx.appId)
|
const database = getAppDB()
|
||||||
|
|
||||||
// Ensure datasource is valid
|
// Ensure datasource is valid
|
||||||
let datasource
|
let datasource
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
const { FieldTypes, FormulaTypes } = require("../../../constants")
|
||||||
|
const { getAllInternalTables, clearColumns } = require("./utils")
|
||||||
|
const { doesContainStrings } = require("@budibase/string-templates")
|
||||||
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
const { isEqual, uniq } = require("lodash")
|
||||||
|
const { updateAllFormulasInTable } = require("../row/staticFormula")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
|
function isStaticFormula(column) {
|
||||||
|
return (
|
||||||
|
column.type === FieldTypes.FORMULA &&
|
||||||
|
column.formulaType === FormulaTypes.STATIC
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This retrieves the formula columns from a table schema that use a specified column name
|
||||||
|
* in the formula.
|
||||||
|
*/
|
||||||
|
function getFormulaThatUseColumn(table, columnNames) {
|
||||||
|
let formula = []
|
||||||
|
columnNames = Array.isArray(columnNames) ? columnNames : [columnNames]
|
||||||
|
for (let column of Object.values(table.schema)) {
|
||||||
|
// not a static formula, or doesn't contain a relationship
|
||||||
|
if (!isStaticFormula(column)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (!doesContainStrings(column.formula, columnNames)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
formula.push(column.name)
|
||||||
|
}
|
||||||
|
return formula
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This functions checks for when a related table, column or related column is deleted, if any
|
||||||
|
* tables need to have the formula column removed.
|
||||||
|
*/
|
||||||
|
async function checkIfFormulaNeedsCleared(table, { oldTable, deletion }) {
|
||||||
|
// start by retrieving all tables, remove the current table from the list
|
||||||
|
const tables = (await getAllInternalTables()).filter(
|
||||||
|
tbl => tbl._id !== table._id
|
||||||
|
)
|
||||||
|
const schemaToUse = oldTable ? oldTable.schema : table.schema
|
||||||
|
let removedColumns = Object.values(schemaToUse).filter(
|
||||||
|
column => deletion || !table.schema[column.name]
|
||||||
|
)
|
||||||
|
// remove any formula columns that used related columns
|
||||||
|
for (let removed of removedColumns) {
|
||||||
|
let tableToUse = table
|
||||||
|
// if relationship, get the related table
|
||||||
|
if (removed.type === FieldTypes.LINK) {
|
||||||
|
tableToUse = tables.find(table => table._id === removed.tableId)
|
||||||
|
}
|
||||||
|
const columnsToDelete = getFormulaThatUseColumn(tableToUse, removed.name)
|
||||||
|
if (columnsToDelete.length > 0) {
|
||||||
|
await clearColumns(table, columnsToDelete)
|
||||||
|
}
|
||||||
|
// need a special case, where a column has been removed from this table, but was used
|
||||||
|
// in a different, related tables formula
|
||||||
|
if (!table.relatedFormula) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (let relatedTableId of table.relatedFormula) {
|
||||||
|
const relatedColumns = Object.values(table.schema).filter(
|
||||||
|
column => column.tableId === relatedTableId
|
||||||
|
)
|
||||||
|
const relatedTable = tables.find(table => table._id === relatedTableId)
|
||||||
|
// look to see if the column was used in a relationship formula,
|
||||||
|
// relationships won't be used for this
|
||||||
|
if (relatedTable && relatedColumns && removed.type !== FieldTypes.LINK) {
|
||||||
|
let relatedFormulaToRemove = []
|
||||||
|
for (let column of relatedColumns) {
|
||||||
|
relatedFormulaToRemove = relatedFormulaToRemove.concat(
|
||||||
|
getFormulaThatUseColumn(relatedTable, [
|
||||||
|
column.fieldName,
|
||||||
|
removed.name,
|
||||||
|
])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (relatedFormulaToRemove.length > 0) {
|
||||||
|
await clearColumns(relatedTable, uniq(relatedFormulaToRemove))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function adds a note to related tables that they are
|
||||||
|
* used in a static formula - so that the link controller
|
||||||
|
* can manage hydrating related rows formula fields. This is
|
||||||
|
* specifically only for static formula.
|
||||||
|
*/
|
||||||
|
async function updateRelatedFormulaLinksOnTables(
|
||||||
|
table,
|
||||||
|
{ deletion } = { deletion: false }
|
||||||
|
) {
|
||||||
|
const db = getAppDB()
|
||||||
|
// start by retrieving all tables, remove the current table from the list
|
||||||
|
const tables = (await getAllInternalTables()).filter(
|
||||||
|
tbl => tbl._id !== table._id
|
||||||
|
)
|
||||||
|
// clone the tables, so we can compare at end
|
||||||
|
const initialTables = cloneDeep(tables)
|
||||||
|
// first find the related column names
|
||||||
|
const relatedColumns = Object.values(table.schema).filter(
|
||||||
|
col => col.type === FieldTypes.LINK
|
||||||
|
)
|
||||||
|
// we start by removing the formula field from all tables
|
||||||
|
for (let otherTable of tables) {
|
||||||
|
if (!otherTable.relatedFormula) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const index = otherTable.relatedFormula.indexOf(table._id)
|
||||||
|
if (index !== -1) {
|
||||||
|
otherTable.relatedFormula.splice(index, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if deleting, just remove the table IDs, don't try add
|
||||||
|
if (!deletion) {
|
||||||
|
for (let relatedCol of relatedColumns) {
|
||||||
|
let columns = getFormulaThatUseColumn(table, relatedCol.name)
|
||||||
|
if (!columns || columns.length === 0) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const relatedTable = tables.find(
|
||||||
|
related => related._id === relatedCol.tableId
|
||||||
|
)
|
||||||
|
// check if the table is already in the list of related formula, if it isn't, then add it
|
||||||
|
if (
|
||||||
|
relatedTable &&
|
||||||
|
(!relatedTable.relatedFormula ||
|
||||||
|
!relatedTable.relatedFormula.includes(table._id))
|
||||||
|
) {
|
||||||
|
relatedTable.relatedFormula = relatedTable.relatedFormula
|
||||||
|
? [...relatedTable.relatedFormula, table._id]
|
||||||
|
: [table._id]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// now we just need to compare all the tables and see if any need saved
|
||||||
|
for (let initial of initialTables) {
|
||||||
|
const found = tables.find(tbl => initial._id === tbl._id)
|
||||||
|
if (found && !isEqual(initial, found)) {
|
||||||
|
await db.put(found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkIfFormulaUpdated(table, { oldTable }) {
|
||||||
|
// look to see if any formula values have changed
|
||||||
|
const shouldUpdate = Object.values(table.schema).find(
|
||||||
|
column =>
|
||||||
|
isStaticFormula(column) &&
|
||||||
|
(!oldTable ||
|
||||||
|
!oldTable.schema[column.name] ||
|
||||||
|
!isEqual(oldTable.schema[column.name], column))
|
||||||
|
)
|
||||||
|
// if a static formula column has updated, then need to run the update
|
||||||
|
if (shouldUpdate != null) {
|
||||||
|
await updateAllFormulasInTable(table)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.runStaticFormulaChecks = async (table, { oldTable, deletion }) => {
|
||||||
|
await updateRelatedFormulaLinksOnTables(table, { deletion })
|
||||||
|
await checkIfFormulaNeedsCleared(table, { oldTable, deletion })
|
||||||
|
if (!deletion) {
|
||||||
|
await checkIfFormulaUpdated(table, { oldTable })
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
const CouchDB = require("../../../db")
|
|
||||||
const {
|
const {
|
||||||
buildExternalTableId,
|
buildExternalTableId,
|
||||||
breakExternalTableId,
|
breakExternalTableId,
|
||||||
|
@ -19,6 +18,7 @@ const { makeExternalQuery } = require("../../../integrations/base/utils")
|
||||||
const { cloneDeep } = require("lodash/fp")
|
const { cloneDeep } = require("lodash/fp")
|
||||||
const csvParser = require("../../../utilities/csvParser")
|
const csvParser = require("../../../utilities/csvParser")
|
||||||
const { handleRequest } = require("../row/external")
|
const { handleRequest } = require("../row/external")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
async function makeTableRequest(
|
async function makeTableRequest(
|
||||||
datasource,
|
datasource,
|
||||||
|
@ -159,7 +159,6 @@ function isRelationshipSetup(column) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
exports.save = async function (ctx) {
|
||||||
const appId = ctx.appId
|
|
||||||
const table = ctx.request.body
|
const table = ctx.request.body
|
||||||
// can't do this right now
|
// can't do this right now
|
||||||
delete table.dataImport
|
delete table.dataImport
|
||||||
|
@ -176,14 +175,14 @@ exports.save = async function (ctx) {
|
||||||
|
|
||||||
let oldTable
|
let oldTable
|
||||||
if (ctx.request.body && ctx.request.body._id) {
|
if (ctx.request.body && ctx.request.body._id) {
|
||||||
oldTable = await getTable(appId, ctx.request.body._id)
|
oldTable = await getTable(ctx.request.body._id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasTypeChanged(tableToSave, oldTable)) {
|
if (hasTypeChanged(tableToSave, oldTable)) {
|
||||||
ctx.throw(400, "A column type has changed.")
|
ctx.throw(400, "A column type has changed.")
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const datasource = await db.get(datasourceId)
|
const datasource = await db.get(datasourceId)
|
||||||
const oldTables = cloneDeep(datasource.entities)
|
const oldTables = cloneDeep(datasource.entities)
|
||||||
const tables = datasource.entities
|
const tables = datasource.entities
|
||||||
|
@ -267,14 +266,13 @@ exports.save = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function (ctx) {
|
exports.destroy = async function (ctx) {
|
||||||
const appId = ctx.appId
|
const tableToDelete = await getTable(ctx.params.tableId)
|
||||||
const tableToDelete = await getTable(appId, ctx.params.tableId)
|
|
||||||
if (!tableToDelete || !tableToDelete.created) {
|
if (!tableToDelete || !tableToDelete.created) {
|
||||||
ctx.throw(400, "Cannot delete tables which weren't created in Budibase.")
|
ctx.throw(400, "Cannot delete tables which weren't created in Budibase.")
|
||||||
}
|
}
|
||||||
const datasourceId = getDatasourceId(tableToDelete)
|
const datasourceId = getDatasourceId(tableToDelete)
|
||||||
|
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const datasource = await db.get(datasourceId)
|
const datasource = await db.get(datasourceId)
|
||||||
const tables = datasource.entities
|
const tables = datasource.entities
|
||||||
|
|
||||||
|
@ -290,8 +288,7 @@ exports.destroy = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.bulkImport = async function (ctx) {
|
exports.bulkImport = async function (ctx) {
|
||||||
const appId = ctx.appId
|
const table = await getTable(ctx.params.tableId)
|
||||||
const table = await getTable(appId, ctx.params.tableId)
|
|
||||||
const { dataImport } = ctx.request.body
|
const { dataImport } = ctx.request.body
|
||||||
if (!dataImport || !dataImport.schema || !dataImport.csvString) {
|
if (!dataImport || !dataImport.schema || !dataImport.csvString) {
|
||||||
ctx.throw(400, "Provided data import information is invalid.")
|
ctx.throw(400, "Provided data import information is invalid.")
|
||||||
|
@ -300,7 +297,7 @@ exports.bulkImport = async function (ctx) {
|
||||||
...dataImport,
|
...dataImport,
|
||||||
existingTable: table,
|
existingTable: table,
|
||||||
})
|
})
|
||||||
await handleRequest(appId, DataSourceOperation.BULK_CREATE, table._id, {
|
await handleRequest(DataSourceOperation.BULK_CREATE, table._id, {
|
||||||
rows,
|
rows,
|
||||||
})
|
})
|
||||||
return table
|
return table
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
const CouchDB = require("../../../db")
|
|
||||||
const internal = require("./internal")
|
const internal = require("./internal")
|
||||||
const external = require("./external")
|
const external = require("./external")
|
||||||
const csvParser = require("../../../utilities/csvParser")
|
const csvParser = require("../../../utilities/csvParser")
|
||||||
const { isExternalTable, isSQL } = require("../../../integrations/utils")
|
const { isExternalTable, isSQL } = require("../../../integrations/utils")
|
||||||
const {
|
const { getDatasourceParams } = require("../../../db/utils")
|
||||||
getTableParams,
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
getDatasourceParams,
|
const { getTable, getAllInternalTables } = require("./utils")
|
||||||
BudibaseInternalDB,
|
|
||||||
} = require("../../../db/utils")
|
|
||||||
const { getTable } = require("./utils")
|
|
||||||
|
|
||||||
function pickApi({ tableId, table }) {
|
function pickApi({ tableId, table }) {
|
||||||
if (table && !tableId) {
|
if (table && !tableId) {
|
||||||
|
@ -24,19 +20,9 @@ function pickApi({ tableId, table }) {
|
||||||
|
|
||||||
// covers both internal and external
|
// covers both internal and external
|
||||||
exports.fetch = async function (ctx) {
|
exports.fetch = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
|
|
||||||
const internalTables = await db.allDocs(
|
const internal = await getAllInternalTables()
|
||||||
getTableParams(null, {
|
|
||||||
include_docs: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const internal = internalTables.rows.map(tableDoc => ({
|
|
||||||
...tableDoc.doc,
|
|
||||||
type: "internal",
|
|
||||||
sourceId: BudibaseInternalDB._id,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const externalTables = await db.allDocs(
|
const externalTables = await db.allDocs(
|
||||||
getDatasourceParams("plus", {
|
getDatasourceParams("plus", {
|
||||||
|
@ -63,7 +49,7 @@ exports.fetch = async function (ctx) {
|
||||||
|
|
||||||
exports.find = async function (ctx) {
|
exports.find = async function (ctx) {
|
||||||
const tableId = ctx.params.id
|
const tableId = ctx.params.id
|
||||||
ctx.body = await getTable(ctx.appId, tableId)
|
ctx.body = await getTable(tableId)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
exports.save = async function (ctx) {
|
||||||
|
@ -102,7 +88,7 @@ exports.validateCSVSchema = async function (ctx) {
|
||||||
const { csvString, schema = {}, tableId } = ctx.request.body
|
const { csvString, schema = {}, tableId } = ctx.request.body
|
||||||
let existingTable
|
let existingTable
|
||||||
if (tableId) {
|
if (tableId) {
|
||||||
existingTable = await getTable(ctx.appId, tableId)
|
existingTable = await getTable(tableId)
|
||||||
}
|
}
|
||||||
let result = await csvParser.parse(csvString, schema)
|
let result = await csvParser.parse(csvString, schema)
|
||||||
if (existingTable) {
|
if (existingTable) {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
const CouchDB = require("../../../db")
|
|
||||||
const linkRows = require("../../../db/linkedRows")
|
const linkRows = require("../../../db/linkedRows")
|
||||||
const { getRowParams, generateTableID } = require("../../../db/utils")
|
const { getRowParams, generateTableID } = require("../../../db/utils")
|
||||||
const { FieldTypes } = require("../../../constants")
|
const { FieldTypes } = require("../../../constants")
|
||||||
|
@ -9,10 +8,13 @@ const {
|
||||||
handleDataImport,
|
handleDataImport,
|
||||||
} = require("./utils")
|
} = require("./utils")
|
||||||
const usageQuota = require("../../../utilities/usageQuota")
|
const usageQuota = require("../../../utilities/usageQuota")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
const env = require("../../../environment")
|
||||||
|
const { cleanupAttachments } = require("../../../utilities/rowProcessor")
|
||||||
|
const { runStaticFormulaChecks } = require("./bulkFormula")
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
exports.save = async function (ctx) {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const { dataImport, ...rest } = ctx.request.body
|
const { dataImport, ...rest } = ctx.request.body
|
||||||
let tableToSave = {
|
let tableToSave = {
|
||||||
type: "table",
|
type: "table",
|
||||||
|
@ -34,8 +36,7 @@ exports.save = async function (ctx) {
|
||||||
// saving a table is a complex operation, involving many different steps, this
|
// saving a table is a complex operation, involving many different steps, this
|
||||||
// has been broken out into a utility to make it more obvious/easier to manipulate
|
// has been broken out into a utility to make it more obvious/easier to manipulate
|
||||||
const tableSaveFunctions = new TableSaveFunctions({
|
const tableSaveFunctions = new TableSaveFunctions({
|
||||||
db,
|
user: ctx.user,
|
||||||
ctx,
|
|
||||||
oldTable,
|
oldTable,
|
||||||
dataImport,
|
dataImport,
|
||||||
})
|
})
|
||||||
|
@ -80,7 +81,6 @@ exports.save = async function (ctx) {
|
||||||
// update linked rows
|
// update linked rows
|
||||||
try {
|
try {
|
||||||
const linkResp = await linkRows.updateLinks({
|
const linkResp = await linkRows.updateLinks({
|
||||||
appId,
|
|
||||||
eventType: oldTable
|
eventType: oldTable
|
||||||
? linkRows.EventType.TABLE_UPDATED
|
? linkRows.EventType.TABLE_UPDATED
|
||||||
: linkRows.EventType.TABLE_SAVE,
|
: linkRows.EventType.TABLE_SAVE,
|
||||||
|
@ -104,13 +104,13 @@ exports.save = async function (ctx) {
|
||||||
tableToSave._rev = result.rev
|
tableToSave._rev = result.rev
|
||||||
|
|
||||||
tableToSave = await tableSaveFunctions.after(tableToSave)
|
tableToSave = await tableSaveFunctions.after(tableToSave)
|
||||||
|
// has to run after, make sure it has _id
|
||||||
|
await runStaticFormulaChecks(tableToSave, { oldTable })
|
||||||
return tableToSave
|
return tableToSave
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function (ctx) {
|
exports.destroy = async function (ctx) {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const tableToDelete = await db.get(ctx.params.tableId)
|
const tableToDelete = await db.get(ctx.params.tableId)
|
||||||
|
|
||||||
// Delete all rows for that table
|
// Delete all rows for that table
|
||||||
|
@ -124,7 +124,6 @@ exports.destroy = async function (ctx) {
|
||||||
|
|
||||||
// update linked rows
|
// update linked rows
|
||||||
await linkRows.updateLinks({
|
await linkRows.updateLinks({
|
||||||
appId,
|
|
||||||
eventType: linkRows.EventType.TABLE_DELETE,
|
eventType: linkRows.EventType.TABLE_DELETE,
|
||||||
table: tableToDelete,
|
table: tableToDelete,
|
||||||
})
|
})
|
||||||
|
@ -133,21 +132,25 @@ exports.destroy = async function (ctx) {
|
||||||
await db.remove(tableToDelete)
|
await db.remove(tableToDelete)
|
||||||
|
|
||||||
// remove table search index
|
// remove table search index
|
||||||
const currentIndexes = await db.getIndexes()
|
if (!env.isTest()) {
|
||||||
const existingIndex = currentIndexes.indexes.find(
|
const currentIndexes = await db.getIndexes()
|
||||||
existing => existing.name === `search:${ctx.params.tableId}`
|
const existingIndex = currentIndexes.indexes.find(
|
||||||
)
|
existing => existing.name === `search:${ctx.params.tableId}`
|
||||||
if (existingIndex) {
|
)
|
||||||
await db.deleteIndex(existingIndex)
|
if (existingIndex) {
|
||||||
|
await db.deleteIndex(existingIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// has to run after, make sure it has _id
|
||||||
|
await runStaticFormulaChecks(tableToDelete, { deletion: true })
|
||||||
|
await cleanupAttachments(tableToDelete, { rows })
|
||||||
return tableToDelete
|
return tableToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.bulkImport = async function (ctx) {
|
exports.bulkImport = async function (ctx) {
|
||||||
const appId = ctx.appId
|
const table = await getTable(ctx.params.tableId)
|
||||||
const table = await getTable(appId, ctx.params.tableId)
|
|
||||||
const { dataImport } = ctx.request.body
|
const { dataImport } = ctx.request.body
|
||||||
await handleDataImport(appId, ctx.user, table, dataImport)
|
await handleDataImport(ctx.user, table, dataImport)
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
const CouchDB = require("../../../db")
|
|
||||||
const csvParser = require("../../../utilities/csvParser")
|
const csvParser = require("../../../utilities/csvParser")
|
||||||
const {
|
const {
|
||||||
getRowParams,
|
getRowParams,
|
||||||
generateRowID,
|
generateRowID,
|
||||||
InternalTables,
|
InternalTables,
|
||||||
|
getTableParams,
|
||||||
|
BudibaseInternalDB,
|
||||||
} = require("../../../db/utils")
|
} = require("../../../db/utils")
|
||||||
const { isEqual } = require("lodash/fp")
|
const { isEqual } = require("lodash")
|
||||||
const { AutoFieldSubTypes, FieldTypes } = require("../../../constants")
|
const { AutoFieldSubTypes, FieldTypes } = require("../../../constants")
|
||||||
const { inputProcessing } = require("../../../utilities/rowProcessor")
|
const {
|
||||||
const { USERS_TABLE_SCHEMA, SwitchableTypes } = require("../../../constants")
|
inputProcessing,
|
||||||
|
cleanupAttachments,
|
||||||
|
} = require("../../../utilities/rowProcessor")
|
||||||
|
const {
|
||||||
|
USERS_TABLE_SCHEMA,
|
||||||
|
SwitchableTypes,
|
||||||
|
CanSwitchTypes,
|
||||||
|
} = require("../../../constants")
|
||||||
const {
|
const {
|
||||||
isExternalTable,
|
isExternalTable,
|
||||||
breakExternalTableId,
|
breakExternalTableId,
|
||||||
|
@ -17,8 +25,26 @@ const {
|
||||||
const { getViews, saveView } = require("../view/utils")
|
const { getViews, saveView } = require("../view/utils")
|
||||||
const viewTemplate = require("../view/viewBuilder")
|
const viewTemplate = require("../view/viewBuilder")
|
||||||
const usageQuota = require("../../../utilities/usageQuota")
|
const usageQuota = require("../../../utilities/usageQuota")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
|
||||||
exports.checkForColumnUpdates = async (db, oldTable, updatedTable) => {
|
exports.clearColumns = async (table, columnNames) => {
|
||||||
|
const db = getAppDB()
|
||||||
|
const rows = await db.allDocs(
|
||||||
|
getRowParams(table._id, null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return db.bulkDocs(
|
||||||
|
rows.rows.map(({ doc }) => {
|
||||||
|
columnNames.forEach(colName => delete doc[colName])
|
||||||
|
return doc
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.checkForColumnUpdates = async (oldTable, updatedTable) => {
|
||||||
|
const db = getAppDB()
|
||||||
let updatedRows = []
|
let updatedRows = []
|
||||||
const rename = updatedTable._rename
|
const rename = updatedTable._rename
|
||||||
let deletedColumns = []
|
let deletedColumns = []
|
||||||
|
@ -35,18 +61,22 @@ exports.checkForColumnUpdates = async (db, oldTable, updatedTable) => {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
updatedRows = rows.rows.map(({ doc }) => {
|
const rawRows = rows.rows.map(({ doc }) => doc)
|
||||||
|
updatedRows = rawRows.map(row => {
|
||||||
|
row = cloneDeep(row)
|
||||||
if (rename) {
|
if (rename) {
|
||||||
doc[rename.updated] = doc[rename.old]
|
row[rename.updated] = row[rename.old]
|
||||||
delete doc[rename.old]
|
delete row[rename.old]
|
||||||
} else if (deletedColumns.length !== 0) {
|
} else if (deletedColumns.length !== 0) {
|
||||||
deletedColumns.forEach(colName => delete doc[colName])
|
deletedColumns.forEach(colName => delete row[colName])
|
||||||
}
|
}
|
||||||
return doc
|
return row
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// cleanup any attachments from object storage for deleted attachment columns
|
||||||
|
await cleanupAttachments(updatedTable, { oldTable, rows: rawRows })
|
||||||
// Update views
|
// Update views
|
||||||
await exports.checkForViewUpdates(db, updatedTable, rename, deletedColumns)
|
await exports.checkForViewUpdates(updatedTable, rename, deletedColumns)
|
||||||
delete updatedTable._rename
|
delete updatedTable._rename
|
||||||
}
|
}
|
||||||
return { rows: updatedRows, table: updatedTable }
|
return { rows: updatedRows, table: updatedTable }
|
||||||
|
@ -73,12 +103,12 @@ exports.makeSureTableUpToDate = (table, tableToSave) => {
|
||||||
return tableToSave
|
return tableToSave
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.handleDataImport = async (appId, user, table, dataImport) => {
|
exports.handleDataImport = async (user, table, dataImport) => {
|
||||||
if (!dataImport || !dataImport.csvString) {
|
if (!dataImport || !dataImport.csvString) {
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
// Populate the table with rows imported from CSV in a bulk update
|
// Populate the table with rows imported from CSV in a bulk update
|
||||||
const data = await csvParser.transform({
|
const data = await csvParser.transform({
|
||||||
...dataImport,
|
...dataImport,
|
||||||
|
@ -123,8 +153,8 @@ exports.handleDataImport = async (appId, user, table, dataImport) => {
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.handleSearchIndexes = async (appId, table) => {
|
exports.handleSearchIndexes = async table => {
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
// create relevant search indexes
|
// create relevant search indexes
|
||||||
if (table.indexes && table.indexes.length > 0) {
|
if (table.indexes && table.indexes.length > 0) {
|
||||||
const currentIndexes = await db.getIndexes()
|
const currentIndexes = await db.getIndexes()
|
||||||
|
@ -181,12 +211,9 @@ exports.checkStaticTables = table => {
|
||||||
}
|
}
|
||||||
|
|
||||||
class TableSaveFunctions {
|
class TableSaveFunctions {
|
||||||
constructor({ db, ctx, oldTable, dataImport }) {
|
constructor({ user, oldTable, dataImport }) {
|
||||||
this.db = db
|
this.db = getAppDB()
|
||||||
this.ctx = ctx
|
this.user = user
|
||||||
if (this.ctx && this.ctx.user) {
|
|
||||||
this.appId = this.ctx.appId
|
|
||||||
}
|
|
||||||
this.oldTable = oldTable
|
this.oldTable = oldTable
|
||||||
this.dataImport = dataImport
|
this.dataImport = dataImport
|
||||||
// any rows that need updated
|
// any rows that need updated
|
||||||
|
@ -204,24 +231,15 @@ class TableSaveFunctions {
|
||||||
|
|
||||||
// when confirmed valid
|
// when confirmed valid
|
||||||
async mid(table) {
|
async mid(table) {
|
||||||
let response = await exports.checkForColumnUpdates(
|
let response = await exports.checkForColumnUpdates(this.oldTable, table)
|
||||||
this.db,
|
|
||||||
this.oldTable,
|
|
||||||
table
|
|
||||||
)
|
|
||||||
this.rows = this.rows.concat(response.rows)
|
this.rows = this.rows.concat(response.rows)
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
// after saving
|
// after saving
|
||||||
async after(table) {
|
async after(table) {
|
||||||
table = await exports.handleSearchIndexes(this.appId, table)
|
table = await exports.handleSearchIndexes(table)
|
||||||
table = await exports.handleDataImport(
|
table = await exports.handleDataImport(this.user, table, this.dataImport)
|
||||||
this.appId,
|
|
||||||
this.ctx.user,
|
|
||||||
table,
|
|
||||||
this.dataImport
|
|
||||||
)
|
|
||||||
return table
|
return table
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,8 +248,22 @@ class TableSaveFunctions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getAllExternalTables = async (appId, datasourceId) => {
|
exports.getAllInternalTables = async () => {
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
|
const internalTables = await db.allDocs(
|
||||||
|
getTableParams(null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return internalTables.rows.map(tableDoc => ({
|
||||||
|
...tableDoc.doc,
|
||||||
|
type: "internal",
|
||||||
|
sourceId: BudibaseInternalDB._id,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.getAllExternalTables = async datasourceId => {
|
||||||
|
const db = getAppDB()
|
||||||
const datasource = await db.get(datasourceId)
|
const datasource = await db.get(datasourceId)
|
||||||
if (!datasource || !datasource.entities) {
|
if (!datasource || !datasource.entities) {
|
||||||
throw "Datasource is not configured fully."
|
throw "Datasource is not configured fully."
|
||||||
|
@ -239,25 +271,25 @@ exports.getAllExternalTables = async (appId, datasourceId) => {
|
||||||
return datasource.entities
|
return datasource.entities
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getExternalTable = async (appId, datasourceId, tableName) => {
|
exports.getExternalTable = async (datasourceId, tableName) => {
|
||||||
const entities = await exports.getAllExternalTables(appId, datasourceId)
|
const entities = await exports.getAllExternalTables(datasourceId)
|
||||||
return entities[tableName]
|
return entities[tableName]
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getTable = async (appId, tableId) => {
|
exports.getTable = async tableId => {
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
if (isExternalTable(tableId)) {
|
if (isExternalTable(tableId)) {
|
||||||
let { datasourceId, tableName } = breakExternalTableId(tableId)
|
let { datasourceId, tableName } = breakExternalTableId(tableId)
|
||||||
const datasource = await db.get(datasourceId)
|
const datasource = await db.get(datasourceId)
|
||||||
const table = await exports.getExternalTable(appId, datasourceId, tableName)
|
const table = await exports.getExternalTable(datasourceId, tableName)
|
||||||
return { ...table, sql: isSQL(datasource) }
|
return { ...table, sql: isSQL(datasource) }
|
||||||
} else {
|
} else {
|
||||||
return db.get(tableId)
|
return db.get(tableId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.checkForViewUpdates = async (db, table, rename, deletedColumns) => {
|
exports.checkForViewUpdates = async (table, rename, deletedColumns) => {
|
||||||
const views = await getViews(db)
|
const views = await getViews()
|
||||||
const tableViews = views.filter(view => view.meta.tableId === table._id)
|
const tableViews = views.filter(view => view.meta.tableId === table._id)
|
||||||
|
|
||||||
// Check each table view to see if impacted by this table action
|
// Check each table view to see if impacted by this table action
|
||||||
|
@ -319,7 +351,7 @@ exports.checkForViewUpdates = async (db, table, rename, deletedColumns) => {
|
||||||
// Update view if required
|
// Update view if required
|
||||||
if (needsUpdated) {
|
if (needsUpdated) {
|
||||||
const newViewTemplate = viewTemplate(view.meta)
|
const newViewTemplate = viewTemplate(view.meta)
|
||||||
await saveView(db, null, view.name, newViewTemplate)
|
await saveView(null, view.name, newViewTemplate)
|
||||||
if (!newViewTemplate.meta.schema) {
|
if (!newViewTemplate.meta.schema) {
|
||||||
newViewTemplate.meta.schema = table.schema
|
newViewTemplate.meta.schema = table.schema
|
||||||
}
|
}
|
||||||
|
@ -348,6 +380,23 @@ exports.foreignKeyStructure = (keyName, meta = null) => {
|
||||||
return structure
|
return structure
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.areSwitchableTypes = (type1, type2) => {
|
||||||
|
if (
|
||||||
|
SwitchableTypes.indexOf(type1) === -1 &&
|
||||||
|
SwitchableTypes.indexOf(type2) === -1
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for (let option of CanSwitchTypes) {
|
||||||
|
const index1 = option.indexOf(type1),
|
||||||
|
index2 = option.indexOf(type2)
|
||||||
|
if (index1 !== -1 && index2 !== -1 && index1 !== index2) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
exports.hasTypeChanged = (table, oldTable) => {
|
exports.hasTypeChanged = (table, oldTable) => {
|
||||||
if (!oldTable) {
|
if (!oldTable) {
|
||||||
return false
|
return false
|
||||||
|
@ -358,7 +407,7 @@ exports.hasTypeChanged = (table, oldTable) => {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const newType = table.schema[key].type
|
const newType = table.schema[key].type
|
||||||
if (oldType !== newType && SwitchableTypes.indexOf(oldType) === -1) {
|
if (oldType !== newType && !exports.areSwitchableTypes(oldType, newType)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const {
|
const {
|
||||||
generateUserMetadataID,
|
generateUserMetadataID,
|
||||||
getUserMetadataParams,
|
getUserMetadataParams,
|
||||||
|
@ -11,12 +10,14 @@ const { isEqual } = require("lodash")
|
||||||
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
|
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
|
||||||
const {
|
const {
|
||||||
getDevelopmentAppID,
|
getDevelopmentAppID,
|
||||||
getDeployedAppIDs,
|
getProdAppIDs,
|
||||||
|
dbExists,
|
||||||
} = require("@budibase/backend-core/db")
|
} = require("@budibase/backend-core/db")
|
||||||
const { doesDatabaseExist } = require("../../utilities")
|
|
||||||
const { UserStatus } = require("@budibase/backend-core/constants")
|
const { UserStatus } = require("@budibase/backend-core/constants")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
async function rawMetadata(db) {
|
async function rawMetadata() {
|
||||||
|
const db = getAppDB()
|
||||||
return (
|
return (
|
||||||
await db.allDocs(
|
await db.allDocs(
|
||||||
getUserMetadataParams(null, {
|
getUserMetadataParams(null, {
|
||||||
|
@ -54,13 +55,10 @@ function combineMetadataAndUser(user, metadata) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.syncGlobalUsers = async appId => {
|
exports.syncGlobalUsers = async () => {
|
||||||
// sync user metadata
|
// sync user metadata
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const [users, metadata] = await Promise.all([
|
const [users, metadata] = await Promise.all([getGlobalUsers(), rawMetadata()])
|
||||||
getGlobalUsers(appId),
|
|
||||||
rawMetadata(db),
|
|
||||||
])
|
|
||||||
const toWrite = []
|
const toWrite = []
|
||||||
for (let user of users) {
|
for (let user of users) {
|
||||||
const combined = await combineMetadataAndUser(user, metadata)
|
const combined = await combineMetadataAndUser(user, metadata)
|
||||||
|
@ -94,7 +92,7 @@ exports.syncUser = async function (ctx) {
|
||||||
let prodAppIds
|
let prodAppIds
|
||||||
// if they are a builder then get all production app IDs
|
// if they are a builder then get all production app IDs
|
||||||
if ((user.builder && user.builder.global) || deleting) {
|
if ((user.builder && user.builder.global) || deleting) {
|
||||||
prodAppIds = await getDeployedAppIDs(CouchDB)
|
prodAppIds = await getProdAppIDs()
|
||||||
} else {
|
} else {
|
||||||
prodAppIds = Object.entries(roles)
|
prodAppIds = Object.entries(roles)
|
||||||
.filter(entry => entry[1] !== BUILTIN_ROLE_IDS.PUBLIC)
|
.filter(entry => entry[1] !== BUILTIN_ROLE_IDS.PUBLIC)
|
||||||
|
@ -104,10 +102,10 @@ exports.syncUser = async function (ctx) {
|
||||||
const roleId = roles[prodAppId]
|
const roleId = roles[prodAppId]
|
||||||
const devAppId = getDevelopmentAppID(prodAppId)
|
const devAppId = getDevelopmentAppID(prodAppId)
|
||||||
for (let appId of [prodAppId, devAppId]) {
|
for (let appId of [prodAppId, devAppId]) {
|
||||||
if (!(await doesDatabaseExist(appId))) {
|
if (!(await dbExists(appId))) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const db = new CouchDB(appId)
|
const db = getAppDB()
|
||||||
const metadataId = generateUserMetadataID(userId)
|
const metadataId = generateUserMetadataID(userId)
|
||||||
let metadata
|
let metadata
|
||||||
try {
|
try {
|
||||||
|
@ -143,8 +141,8 @@ exports.syncUser = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchMetadata = async function (ctx) {
|
exports.fetchMetadata = async function (ctx) {
|
||||||
const database = new CouchDB(ctx.appId)
|
const database = getAppDB()
|
||||||
const global = await getGlobalUsers(ctx.appId)
|
const global = await getGlobalUsers()
|
||||||
const metadata = await rawMetadata(database)
|
const metadata = await rawMetadata(database)
|
||||||
const users = []
|
const users = []
|
||||||
for (let user of global) {
|
for (let user of global) {
|
||||||
|
@ -173,8 +171,7 @@ exports.updateSelfMetadata = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.updateMetadata = async function (ctx) {
|
exports.updateMetadata = async function (ctx) {
|
||||||
const appId = ctx.appId
|
const db = getAppDB()
|
||||||
const db = new CouchDB(appId)
|
|
||||||
const user = ctx.request.body
|
const user = ctx.request.body
|
||||||
// this isn't applicable to the user
|
// this isn't applicable to the user
|
||||||
delete user.roles
|
delete user.roles
|
||||||
|
@ -186,7 +183,7 @@ exports.updateMetadata = async function (ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroyMetadata = async function (ctx) {
|
exports.destroyMetadata = async function (ctx) {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
try {
|
try {
|
||||||
const dbUser = await db.get(ctx.params.id)
|
const dbUser = await db.get(ctx.params.id)
|
||||||
await db.remove(dbUser._id, dbUser._rev)
|
await db.remove(dbUser._id, dbUser._rev)
|
||||||
|
@ -209,7 +206,7 @@ exports.setFlag = async function (ctx) {
|
||||||
ctx.throw(400, "Must supply a 'flag' field in request body.")
|
ctx.throw(400, "Must supply a 'flag' field in request body.")
|
||||||
}
|
}
|
||||||
const flagDocId = generateUserFlagID(userId)
|
const flagDocId = generateUserFlagID(userId)
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
let doc
|
let doc
|
||||||
try {
|
try {
|
||||||
doc = await db.get(flagDocId)
|
doc = await db.get(flagDocId)
|
||||||
|
@ -224,7 +221,7 @@ exports.setFlag = async function (ctx) {
|
||||||
exports.getFlags = async function (ctx) {
|
exports.getFlags = async function (ctx) {
|
||||||
const userId = ctx.user._id
|
const userId = ctx.user._id
|
||||||
const docId = generateUserFlagID(userId)
|
const docId = generateUserFlagID(userId)
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
let doc
|
let doc
|
||||||
try {
|
try {
|
||||||
doc = await db.get(docId)
|
doc = await db.get(docId)
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
const CouchDB = require("../../../db")
|
|
||||||
const viewTemplate = require("./viewBuilder")
|
const viewTemplate = require("./viewBuilder")
|
||||||
const { apiFileReturn } = require("../../../utilities/fileSystem")
|
const { apiFileReturn } = require("../../../utilities/fileSystem")
|
||||||
const exporters = require("./exporters")
|
const exporters = require("./exporters")
|
||||||
|
@ -6,14 +5,14 @@ const { saveView, getView, getViews, deleteView } = require("./utils")
|
||||||
const { fetchView } = require("../row")
|
const { fetchView } = require("../row")
|
||||||
const { getTable } = require("../table/utils")
|
const { getTable } = require("../table/utils")
|
||||||
const { FieldTypes } = require("../../../constants")
|
const { FieldTypes } = require("../../../constants")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
const db = new CouchDB(ctx.appId)
|
ctx.body = await getViews()
|
||||||
ctx.body = await getViews(db)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async ctx => {
|
exports.save = async ctx => {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const { originalName, ...viewToSave } = ctx.request.body
|
const { originalName, ...viewToSave } = ctx.request.body
|
||||||
const view = viewTemplate(viewToSave)
|
const view = viewTemplate(viewToSave)
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ exports.save = async ctx => {
|
||||||
ctx.throw(400, "Cannot create view without a name")
|
ctx.throw(400, "Cannot create view without a name")
|
||||||
}
|
}
|
||||||
|
|
||||||
await saveView(db, originalName, viewToSave.name, view)
|
await saveView(originalName, viewToSave.name, view)
|
||||||
|
|
||||||
// add views to table document
|
// add views to table document
|
||||||
const table = await db.get(ctx.request.body.tableId)
|
const table = await db.get(ctx.request.body.tableId)
|
||||||
|
@ -42,9 +41,9 @@ exports.save = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async ctx => {
|
exports.destroy = async ctx => {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const viewName = decodeURI(ctx.params.viewName)
|
const viewName = decodeURI(ctx.params.viewName)
|
||||||
const view = await deleteView(db, viewName)
|
const view = await deleteView(viewName)
|
||||||
const table = await db.get(view.meta.tableId)
|
const table = await db.get(view.meta.tableId)
|
||||||
delete table.views[viewName]
|
delete table.views[viewName]
|
||||||
await db.put(table)
|
await db.put(table)
|
||||||
|
@ -53,9 +52,8 @@ exports.destroy = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.exportView = async ctx => {
|
exports.exportView = async ctx => {
|
||||||
const db = new CouchDB(ctx.appId)
|
|
||||||
const viewName = decodeURI(ctx.query.view)
|
const viewName = decodeURI(ctx.query.view)
|
||||||
const view = await getView(db, viewName)
|
const view = await getView(viewName)
|
||||||
|
|
||||||
const format = ctx.query.format
|
const format = ctx.query.format
|
||||||
if (!format || !Object.values(exporters.ExportFormats).includes(format)) {
|
if (!format || !Object.values(exporters.ExportFormats).includes(format)) {
|
||||||
|
@ -83,7 +81,7 @@ exports.exportView = async ctx => {
|
||||||
let schema = view && view.meta && view.meta.schema
|
let schema = view && view.meta && view.meta.schema
|
||||||
if (!schema) {
|
if (!schema) {
|
||||||
const tableId = ctx.params.tableId || view.meta.tableId
|
const tableId = ctx.params.tableId || view.meta.tableId
|
||||||
const table = await getTable(ctx.appId, tableId)
|
const table = await getTable(tableId)
|
||||||
schema = table.schema
|
schema = table.schema
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,10 @@ const {
|
||||||
SEPARATOR,
|
SEPARATOR,
|
||||||
} = require("../../../db/utils")
|
} = require("../../../db/utils")
|
||||||
const env = require("../../../environment")
|
const env = require("../../../environment")
|
||||||
|
const { getAppDB } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
exports.getView = async (db, viewName) => {
|
exports.getView = async viewName => {
|
||||||
|
const db = getAppDB()
|
||||||
if (env.SELF_HOSTED) {
|
if (env.SELF_HOSTED) {
|
||||||
const designDoc = await db.get("_design/database")
|
const designDoc = await db.get("_design/database")
|
||||||
return designDoc.views[viewName]
|
return designDoc.views[viewName]
|
||||||
|
@ -22,7 +24,8 @@ exports.getView = async (db, viewName) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getViews = async db => {
|
exports.getViews = async () => {
|
||||||
|
const db = getAppDB()
|
||||||
const response = []
|
const response = []
|
||||||
if (env.SELF_HOSTED) {
|
if (env.SELF_HOSTED) {
|
||||||
const designDoc = await db.get("_design/database")
|
const designDoc = await db.get("_design/database")
|
||||||
|
@ -54,7 +57,8 @@ exports.getViews = async db => {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.saveView = async (db, originalName, viewName, viewTemplate) => {
|
exports.saveView = async (originalName, viewName, viewTemplate) => {
|
||||||
|
const db = getAppDB()
|
||||||
if (env.SELF_HOSTED) {
|
if (env.SELF_HOSTED) {
|
||||||
const designDoc = await db.get("_design/database")
|
const designDoc = await db.get("_design/database")
|
||||||
designDoc.views = {
|
designDoc.views = {
|
||||||
|
@ -91,7 +95,8 @@ exports.saveView = async (db, originalName, viewName, viewTemplate) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.deleteView = async (db, viewName) => {
|
exports.deleteView = async viewName => {
|
||||||
|
const db = getAppDB()
|
||||||
if (env.SELF_HOSTED) {
|
if (env.SELF_HOSTED) {
|
||||||
const designDoc = await db.get("_design/database")
|
const designDoc = await db.get("_design/database")
|
||||||
const view = designDoc.views[viewName]
|
const view = designDoc.views[viewName]
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
const CouchDB = require("../../db")
|
|
||||||
const { generateWebhookID, getWebhookParams } = require("../../db/utils")
|
const { generateWebhookID, getWebhookParams } = require("../../db/utils")
|
||||||
const toJsonSchema = require("to-json-schema")
|
const toJsonSchema = require("to-json-schema")
|
||||||
const validate = require("jsonschema").validate
|
const validate = require("jsonschema").validate
|
||||||
const triggers = require("../../automations/triggers")
|
const triggers = require("../../automations/triggers")
|
||||||
const { getDeployedAppID } = require("@budibase/backend-core/db")
|
const { getProdAppID } = require("@budibase/backend-core/db")
|
||||||
|
const { getAppDB, updateAppId } = require("@budibase/backend-core/context")
|
||||||
|
|
||||||
const AUTOMATION_DESCRIPTION = "Generated from Webhook Schema"
|
const AUTOMATION_DESCRIPTION = "Generated from Webhook Schema"
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ exports.WebhookType = {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
exports.fetch = async ctx => {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const response = await db.allDocs(
|
const response = await db.allDocs(
|
||||||
getWebhookParams(null, {
|
getWebhookParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
|
@ -33,7 +33,7 @@ exports.fetch = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async ctx => {
|
exports.save = async ctx => {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
const webhook = ctx.request.body
|
const webhook = ctx.request.body
|
||||||
webhook.appId = ctx.appId
|
webhook.appId = ctx.appId
|
||||||
|
|
||||||
|
@ -52,12 +52,13 @@ exports.save = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async ctx => {
|
exports.destroy = async ctx => {
|
||||||
const db = new CouchDB(ctx.appId)
|
const db = getAppDB()
|
||||||
ctx.body = await db.remove(ctx.params.id, ctx.params.rev)
|
ctx.body = await db.remove(ctx.params.id, ctx.params.rev)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.buildSchema = async ctx => {
|
exports.buildSchema = async ctx => {
|
||||||
const db = new CouchDB(ctx.params.instance)
|
updateAppId(ctx.params.instance)
|
||||||
|
const db = getAppDB()
|
||||||
const webhook = await db.get(ctx.params.id)
|
const webhook = await db.get(ctx.params.id)
|
||||||
webhook.bodySchema = toJsonSchema(ctx.request.body)
|
webhook.bodySchema = toJsonSchema(ctx.request.body)
|
||||||
// update the automation outputs
|
// update the automation outputs
|
||||||
|
@ -81,9 +82,10 @@ exports.buildSchema = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.trigger = async ctx => {
|
exports.trigger = async ctx => {
|
||||||
const prodAppId = getDeployedAppID(ctx.params.instance)
|
const prodAppId = getProdAppID(ctx.params.instance)
|
||||||
|
updateAppId(prodAppId)
|
||||||
try {
|
try {
|
||||||
const db = new CouchDB(prodAppId)
|
const db = getAppDB()
|
||||||
const webhook = await db.get(ctx.params.id)
|
const webhook = await db.get(ctx.params.id)
|
||||||
// validate against the schema
|
// validate against the schema
|
||||||
if (webhook.bodySchema) {
|
if (webhook.bodySchema) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ const backupRoutes = require("./backup")
|
||||||
const metadataRoutes = require("./metadata")
|
const metadataRoutes = require("./metadata")
|
||||||
const devRoutes = require("./dev")
|
const devRoutes = require("./dev")
|
||||||
const cloudRoutes = require("./cloud")
|
const cloudRoutes = require("./cloud")
|
||||||
|
const migrationRoutes = require("./migrations")
|
||||||
|
|
||||||
exports.mainRoutes = [
|
exports.mainRoutes = [
|
||||||
authRoutes,
|
authRoutes,
|
||||||
|
@ -53,6 +54,7 @@ exports.mainRoutes = [
|
||||||
// this could be breaking as koa may recognise other routes as this
|
// this could be breaking as koa may recognise other routes as this
|
||||||
tableRoutes,
|
tableRoutes,
|
||||||
rowRoutes,
|
rowRoutes,
|
||||||
|
migrationRoutes,
|
||||||
]
|
]
|
||||||
|
|
||||||
exports.staticRoutes = staticRoutes
|
exports.staticRoutes = staticRoutes
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue