Merge pull request #4276 from Budibase/lab-day/refactor-app-db

Refactoring app DB usage - one DB load per request
This commit is contained in:
Michael Drury 2022-02-01 16:25:39 +00:00 committed by GitHub
commit 6107f1472c
95 changed files with 1256 additions and 2134 deletions

View File

@ -0,0 +1,17 @@
const {
getAppDB,
getDevAppDB,
getProdAppDB,
getAppId,
updateAppId,
doInAppContext,
} = require("./src/context")
module.exports = {
getAppDB,
getDevAppDB,
getProdAppDB,
getAppId,
updateAppId,
doInAppContext,
}

View File

@ -1,5 +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"), ...require("./src/db/views"),
} }

View File

@ -1 +1 @@
module.exports = require("./src/tenancy/deprovision") module.exports = require("./src/context/deprovision")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = (

View File

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

View File

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

View File

@ -1,4 +1,4 @@
module.exports = { module.exports = {
...require("./context"), ...require("../context"),
...require("./tenancy"), ...require("./tenancy"),
} }

View File

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

View File

@ -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/dist/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()
}

View File

@ -0,0 +1,4 @@
// @ts-ignore
import { run } from "../setup"
run("../../server/src/index", "../../worker/src/index")

View File

@ -11,12 +11,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": {
@ -106,6 +107,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"

View File

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

View File

@ -970,10 +970,10 @@
svelte-flatpickr "^3.2.3" svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0" svelte-portal "^1.0.0"
"@budibase/bbui@^1.0.46", "@budibase/bbui@^1.0.46-alpha.3": "@budibase/bbui@^1.0.46-alpha.6", "@budibase/bbui@^1.0.47":
version "1.0.46" version "1.0.47"
resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.0.46.tgz#7306d4eda7f2c827577a4affa1fd314b38ba1198" resolved "https://registry.yarnpkg.com/@budibase/bbui/-/bbui-1.0.47.tgz#df2848b89f881fe603e7156855d6a6c31d4f58bf"
integrity sha512-padm0qq2SBNIslXEQW+HIv32pkIHFzloR93FDzSXh0sO43Q+/d2gbAhjI9ZUSAVncx9JNc46dolL1CwrvHFElg== integrity sha512-RRm/BgK5aSx2/vGjMGljw240/48Ksc3/h4yB1nhQj8Xx3fKhlGnWDvWNy+sakvA6+fJvEXuti8RoxHtQ6lXmqA==
dependencies: dependencies:
"@adobe/spectrum-css-workflow-icons" "^1.2.1" "@adobe/spectrum-css-workflow-icons" "^1.2.1"
"@spectrum-css/actionbutton" "^1.0.1" "@spectrum-css/actionbutton" "^1.0.1"
@ -1020,14 +1020,14 @@
svelte-flatpickr "^3.2.3" svelte-flatpickr "^3.2.3"
svelte-portal "^1.0.0" svelte-portal "^1.0.0"
"@budibase/client@^1.0.46-alpha.3": "@budibase/client@^1.0.46-alpha.6":
version "1.0.46" version "1.0.47"
resolved "https://registry.yarnpkg.com/@budibase/client/-/client-1.0.46.tgz#e6ef8945b9d7046b6e6d6761628aa1d85387acca" resolved "https://registry.yarnpkg.com/@budibase/client/-/client-1.0.47.tgz#ce9e2fbd300e5dc389ea29a3a3347897f096c824"
integrity sha512-jI3z1G/EsfJNCQCvrqzsR4vR1zLoVefzCXCEASIPg9BPzdiAFSwuUJVLijLFIIKfuDVeveUll94fgu7XNY8U2w== integrity sha512-jB/al8v+nY/VLc6sH5Jt9JzWONVo+24/cI95iXlZSV5xwiKIVGj4+2F5QjKZ0c9Gm7SrrfP2T571N+4XaXNCGg==
dependencies: dependencies:
"@budibase/bbui" "^1.0.46" "@budibase/bbui" "^1.0.47"
"@budibase/standard-components" "^0.9.139" "@budibase/standard-components" "^0.9.139"
"@budibase/string-templates" "^1.0.46" "@budibase/string-templates" "^1.0.47"
regexparam "^1.3.0" regexparam "^1.3.0"
shortid "^2.2.15" shortid "^2.2.15"
svelte-spa-router "^3.0.5" svelte-spa-router "^3.0.5"
@ -1082,10 +1082,10 @@
svelte-apexcharts "^1.0.2" svelte-apexcharts "^1.0.2"
svelte-flatpickr "^3.1.0" svelte-flatpickr "^3.1.0"
"@budibase/string-templates@^1.0.46", "@budibase/string-templates@^1.0.46-alpha.3": "@budibase/string-templates@^1.0.46-alpha.6", "@budibase/string-templates@^1.0.47":
version "1.0.46" version "1.0.47"
resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.46.tgz#5beef1687b451e4512a465b4e143c8ab46234006" resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.47.tgz#626b9fc4542c7b36a0ae24e820d25a704c527bec"
integrity sha512-t4ZAUkSz2XatjAN0faex5ovmD3mFz672lV/aBk7tfLFzZiKlWjngqdwpLLQNnsqeGvYo75JP2J06j86SX6O83w== integrity sha512-87BUfOPr8FGKH8Pt88jhKNGT9PcOmkLRCeen4xi1dI113pAQznBO9vgV+cXOChUBBEQka9Rrt85LMJXidiwVgg==
dependencies: dependencies:
"@budibase/handlebars-helpers" "^0.11.7" "@budibase/handlebars-helpers" "^0.11.7"
dayjs "^1.10.4" dayjs "^1.10.4"
@ -1102,6 +1102,18 @@
exec-sh "^0.3.2" exec-sh "^0.3.2"
minimist "^1.2.0" minimist "^1.2.0"
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==
"@cspotcode/source-map-support@0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5"
integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==
dependencies:
"@cspotcode/source-map-consumer" "0.8.0"
"@cypress/listr-verbose-renderer@^0.4.1": "@cypress/listr-verbose-renderer@^0.4.1":
version "0.4.1" version "0.4.1"
resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a"
@ -1795,6 +1807,26 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@tsconfig/node10@^1.0.7":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==
"@tsconfig/node12@^1.0.7":
version "1.0.9"
resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c"
integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==
"@tsconfig/node14@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2"
integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==
"@tsconfig/node16@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e"
integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==
"@types/aria-query@^4.2.0": "@types/aria-query@^4.2.0":
version "4.2.2" version "4.2.2"
resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc"
@ -1971,6 +2003,11 @@ acorn-walk@^7.1.1:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
acorn-walk@^8.1.1:
version "8.2.0"
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
acorn@^7.1.1: acorn@^7.1.1:
version "7.4.1" version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
@ -1981,6 +2018,11 @@ acorn@^8.2.4:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2"
integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==
acorn@^8.4.1:
version "8.7.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
agent-base@6: agent-base@6:
version "6.0.2" version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@ -2087,6 +2129,11 @@ arch@^2.1.2:
resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
arg@^4.1.0:
version "4.1.3"
resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
argparse@^1.0.10, argparse@^1.0.7: argparse@^1.0.10, argparse@^1.0.7:
version "1.0.10" version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@ -2720,6 +2767,11 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
cross-spawn@^6.0.0: cross-spawn@^6.0.0:
version "6.0.5" version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@ -2965,6 +3017,11 @@ diff-sequences@^27.0.6:
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.0.6.tgz#3305cb2e55a033924054695cc66019fd7f8e5723"
integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ== integrity sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
dir-glob@^3.0.1: dir-glob@^3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
@ -5004,6 +5061,11 @@ make-dir@^3.0.0:
dependencies: dependencies:
semver "^6.0.0" semver "^6.0.0"
make-error@^1.1.1:
version "1.3.6"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
makeerror@1.0.12: makeerror@1.0.12:
version "1.0.12" version "1.0.12"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
@ -6587,6 +6649,24 @@ tr46@~0.0.3:
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
ts-node@^10.4.0:
version "10.4.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7"
integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==
dependencies:
"@cspotcode/source-map-support" "0.7.0"
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
"@tsconfig/node16" "^1.0.2"
acorn "^8.4.1"
acorn-walk "^8.1.1"
arg "^4.1.0"
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
yn "3.1.1"
tslib@^1.9.0, tslib@^1.9.3: tslib@^1.9.0, tslib@^1.9.3:
version "1.14.1" version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
@ -6655,6 +6735,11 @@ typeof-article@^0.1.1:
dependencies: dependencies:
kind-of "^3.1.0" kind-of "^3.1.0"
typescript@^4.5.5:
version "4.5.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.5.tgz#d8c953832d28924a9e3d37c73d729c846c5896f3"
integrity sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==
uglify-js@^3.1.4: uglify-js@^3.1.4:
version "3.14.5" version "3.14.5"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.5.tgz#cdabb7d4954231d80cb4a927654c4655e51f4859" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.14.5.tgz#cdabb7d4954231d80cb4a927654c4655e51f4859"
@ -7011,6 +7096,11 @@ year@^0.2.1:
resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0" resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0"
integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A= integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A=
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
yup@0.29.2: yup@0.29.2:
version "0.29.2" version "0.29.2"
resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.2.tgz#5302abd9024cca335b987793f8df868e410b7b67" resolved "https://registry.yarnpkg.com/yup/-/yup-0.29.2.tgz#5302abd9024cca335b987793f8df868e410b7b67"

View File

@ -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, {
@ -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,7 +222,7 @@ 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 = exports.getAppUrl(ctx) const url = exports.getAppUrl(ctx)
@ -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,7 +286,7 @@ 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) {
@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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]
const rows = related[key]?.rows || [] if (!linkTable || !linkPrimary) {
return
}
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 &&

View File

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

View File

@ -1,4 +1,3 @@
const CouchDB = require("../../../db")
const linkRows = require("../../../db/linkedRows") const linkRows = require("../../../db/linkedRows")
const { const {
generateRowID, generateRowID,
@ -25,6 +24,7 @@ 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 { finaliseRow, updateRelatedFormula } = require("./staticFormula")
const CALCULATION_TYPES = { const CALCULATION_TYPES = {
@ -76,8 +76,7 @@ async function getRawTableData(ctx, db, tableId) {
} }
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
@ -116,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
@ -132,15 +130,14 @@ exports.patch = async ctx => {
return { row: ctx.body, table } return { row: ctx.body, table }
} }
return finaliseRow(ctx.appId, table, row, { return finaliseRow(table, row, {
oldTable: dbTable, oldTable: dbTable,
updateFormula: true, 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
@ -162,21 +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 finaliseRow(ctx.appId, table, row, { return finaliseRow(table, row, {
oldTable: dbTable, oldTable: dbTable,
updateFormula: true, 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
@ -185,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
@ -212,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) {
@ -239,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)
@ -268,18 +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 // remove any static formula
await updateRelatedFormula(appId, table, row) await updateRelatedFormula(table, row)
let response let response
if (ctx.params.tableId === InternalTables.USER_METADATA) { if (ctx.params.tableId === InternalTables.USER_METADATA) {
@ -295,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,
@ -327,8 +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(appId, table, rows) await updateRelatedFormula(table, rows)
await Promise.all(updates) await Promise.all(updates)
return { response: { ok: true }, rows } return { response: { ok: true }, rows }
} }
@ -339,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
@ -368,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,
}) })
@ -413,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)
) )
} }

View File

@ -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: {},
@ -241,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()
@ -278,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
@ -291,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) {
@ -301,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)
@ -321,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
@ -340,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)
@ -375,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
@ -386,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 }
} }

View File

@ -1,4 +1,3 @@
const CouchDB = require("../../../db")
const { getRowParams } = require("../../../db/utils") const { getRowParams } = require("../../../db/utils")
const { const {
outputProcessing, outputProcessing,
@ -8,6 +7,7 @@ const {
const { FieldTypes, FormulaTypes } = require("../../../constants") const { FieldTypes, FormulaTypes } = require("../../../constants")
const { isEqual } = require("lodash") const { isEqual } = require("lodash")
const { cloneDeep } = require("lodash/fp") 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 * This function runs through a list of enriched rows, looks at the rows which
@ -15,8 +15,8 @@ const { cloneDeep } = require("lodash/fp")
* updated. * updated.
* NOTE: this will only for affect static formulas. * NOTE: this will only for affect static formulas.
*/ */
exports.updateRelatedFormula = async (appId, table, enrichedRows) => { exports.updateRelatedFormula = async (table, enrichedRows) => {
const db = new CouchDB(appId) const db = getAppDB()
// no formula to update, we're done // no formula to update, we're done
if (!table.relatedFormula) { if (!table.relatedFormula) {
return return
@ -57,7 +57,7 @@ exports.updateRelatedFormula = async (appId, table, enrichedRows) => {
// re-enrich rows for all the related, don't update the related formula for them // re-enrich rows for all the related, don't update the related formula for them
promises = promises.concat( promises = promises.concat(
relatedRows[tableId].map(related => relatedRows[tableId].map(related =>
exports.finaliseRow(appId, relatedTable, related, { exports.finaliseRow(relatedTable, related, {
updateFormula: false, updateFormula: false,
}) })
) )
@ -69,8 +69,8 @@ exports.updateRelatedFormula = async (appId, table, enrichedRows) => {
await Promise.all(promises) await Promise.all(promises)
} }
exports.updateAllFormulasInTable = async (appId, table) => { exports.updateAllFormulasInTable = async table => {
const db = new CouchDB(appId) const db = getAppDB()
// start by getting the raw rows (which will be written back to DB after update) // start by getting the raw rows (which will be written back to DB after update)
let rows = ( let rows = (
await db.allDocs( await db.allDocs(
@ -81,7 +81,7 @@ exports.updateAllFormulasInTable = async (appId, table) => {
).rows.map(row => row.doc) ).rows.map(row => row.doc)
// now enrich the rows, note the clone so that we have the base state of the // 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 // rows so that we don't write any of the enriched information back
let enrichedRows = await outputProcessing({ appId }, table, cloneDeep(rows), { let enrichedRows = await outputProcessing(table, cloneDeep(rows), {
squash: false, squash: false,
}) })
const updatedRows = [] const updatedRows = []
@ -109,15 +109,14 @@ exports.updateAllFormulasInTable = async (appId, table) => {
* expects the row to be totally enriched/contain all relationships. * expects the row to be totally enriched/contain all relationships.
*/ */
exports.finaliseRow = async ( exports.finaliseRow = async (
appId,
table, table,
row, row,
{ oldTable, updateFormula } = { updateFormula: true } { oldTable, updateFormula } = { updateFormula: true }
) => { ) => {
const db = new CouchDB(appId) const db = getAppDB()
row.type = "row" row.type = "row"
// process the row before return, to include relationships // process the row before return, to include relationships
let enrichedRow = await outputProcessing({ appId }, table, cloneDeep(row), { let enrichedRow = await outputProcessing(table, cloneDeep(row), {
squash: false, squash: false,
}) })
// use enriched row to generate formulas for saving, specifically only use as context // use enriched row to generate formulas for saving, specifically only use as context
@ -151,7 +150,7 @@ exports.finaliseRow = async (
enrichedRow = await processFormulas(table, enrichedRow, { dynamic: false }) enrichedRow = await processFormulas(table, enrichedRow, { dynamic: false })
// this updates the related formulas in other rows based on the relations to this row // this updates the related formulas in other rows based on the relations to this row
if (updateFormula) { if (updateFormula) {
await exports.updateRelatedFormula(appId, table, enrichedRow) await exports.updateRelatedFormula(table, enrichedRow)
} }
return { row: enrichedRow, table } return { row: enrichedRow, table }
} }

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
const CouchDB = require("../../../db")
const { FieldTypes, FormulaTypes } = require("../../../constants") const { FieldTypes, FormulaTypes } = require("../../../constants")
const { getAllInternalTables, clearColumns } = require("./utils") const { getAllInternalTables, clearColumns } = require("./utils")
const { doesContainStrings } = require("@budibase/string-templates") const { doesContainStrings } = require("@budibase/string-templates")
const { cloneDeep } = require("lodash/fp") const { cloneDeep } = require("lodash/fp")
const { isEqual, uniq } = require("lodash") const { isEqual, uniq } = require("lodash")
const { updateAllFormulasInTable } = require("../row/staticFormula") const { updateAllFormulasInTable } = require("../row/staticFormula")
const { getAppDB } = require("@budibase/backend-core/context")
function isStaticFormula(column) { function isStaticFormula(column) {
return ( return (
@ -37,14 +37,9 @@ function getFormulaThatUseColumn(table, columnNames) {
* This functions checks for when a related table, column or related column is deleted, if any * This functions checks for when a related table, column or related column is deleted, if any
* tables need to have the formula column removed. * tables need to have the formula column removed.
*/ */
async function checkIfFormulaNeedsCleared( async function checkIfFormulaNeedsCleared(table, { oldTable, deletion }) {
appId,
table,
{ oldTable, deletion }
) {
const db = new CouchDB(appId)
// start by retrieving all tables, remove the current table from the list // start by retrieving all tables, remove the current table from the list
const tables = (await getAllInternalTables(appId)).filter( const tables = (await getAllInternalTables()).filter(
tbl => tbl._id !== table._id tbl => tbl._id !== table._id
) )
const schemaToUse = oldTable ? oldTable.schema : table.schema const schemaToUse = oldTable ? oldTable.schema : table.schema
@ -60,7 +55,7 @@ async function checkIfFormulaNeedsCleared(
} }
const columnsToDelete = getFormulaThatUseColumn(tableToUse, removed.name) const columnsToDelete = getFormulaThatUseColumn(tableToUse, removed.name)
if (columnsToDelete.length > 0) { if (columnsToDelete.length > 0) {
await clearColumns(db, table, columnsToDelete) await clearColumns(table, columnsToDelete)
} }
// need a special case, where a column has been removed from this table, but was used // need a special case, where a column has been removed from this table, but was used
// in a different, related tables formula // in a different, related tables formula
@ -85,7 +80,7 @@ async function checkIfFormulaNeedsCleared(
) )
} }
if (relatedFormulaToRemove.length > 0) { if (relatedFormulaToRemove.length > 0) {
await clearColumns(db, relatedTable, uniq(relatedFormulaToRemove)) await clearColumns(relatedTable, uniq(relatedFormulaToRemove))
} }
} }
} }
@ -99,13 +94,12 @@ async function checkIfFormulaNeedsCleared(
* specifically only for static formula. * specifically only for static formula.
*/ */
async function updateRelatedFormulaLinksOnTables( async function updateRelatedFormulaLinksOnTables(
appId,
table, table,
{ deletion } = { deletion: false } { deletion } = { deletion: false }
) { ) {
const db = new CouchDB(appId) const db = getAppDB()
// start by retrieving all tables, remove the current table from the list // start by retrieving all tables, remove the current table from the list
const tables = (await getAllInternalTables(appId)).filter( const tables = (await getAllInternalTables()).filter(
tbl => tbl._id !== table._id tbl => tbl._id !== table._id
) )
// clone the tables, so we can compare at end // clone the tables, so we can compare at end
@ -155,7 +149,7 @@ async function updateRelatedFormulaLinksOnTables(
} }
} }
async function checkIfFormulaUpdated(appId, table, { oldTable }) { async function checkIfFormulaUpdated(table, { oldTable }) {
// look to see if any formula values have changed // look to see if any formula values have changed
const shouldUpdate = Object.values(table.schema).find( const shouldUpdate = Object.values(table.schema).find(
column => column =>
@ -166,18 +160,14 @@ async function checkIfFormulaUpdated(appId, table, { oldTable }) {
) )
// if a static formula column has updated, then need to run the update // if a static formula column has updated, then need to run the update
if (shouldUpdate != null) { if (shouldUpdate != null) {
await updateAllFormulasInTable(appId, table) await updateAllFormulasInTable(table)
} }
} }
exports.runStaticFormulaChecks = async ( exports.runStaticFormulaChecks = async (table, { oldTable, deletion }) => {
appId, await updateRelatedFormulaLinksOnTables(table, { deletion })
table, await checkIfFormulaNeedsCleared(table, { oldTable, deletion })
{ oldTable, deletion }
) => {
await updateRelatedFormulaLinksOnTables(appId, table, { deletion })
await checkIfFormulaNeedsCleared(appId, table, { oldTable, deletion })
if (!deletion) { if (!deletion) {
await checkIfFormulaUpdated(appId, table, { oldTable }) await checkIfFormulaUpdated(table, { oldTable })
} }
} }

View File

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

View File

@ -1,9 +1,9 @@
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 { getDatasourceParams } = require("../../../db/utils") const { getDatasourceParams } = require("../../../db/utils")
const { getAppDB } = require("@budibase/backend-core/context")
const { getTable, getAllInternalTables } = require("./utils") const { getTable, getAllInternalTables } = require("./utils")
function pickApi({ tableId, table }) { function pickApi({ tableId, table }) {
@ -20,9 +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 internal = await getAllInternalTables(ctx.appId) const internal = await getAllInternalTables()
const externalTables = await db.allDocs( const externalTables = await db.allDocs(
getDatasourceParams("plus", { getDatasourceParams("plus", {
@ -49,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) {
@ -88,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) {

View File

@ -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,12 +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 { cleanupAttachments } = require("../../../utilities/rowProcessor")
const { runStaticFormulaChecks } = require("./bulkFormula") 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",
@ -36,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,
}) })
@ -82,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,
@ -107,13 +105,12 @@ exports.save = async function (ctx) {
tableToSave = await tableSaveFunctions.after(tableToSave) tableToSave = await tableSaveFunctions.after(tableToSave)
// has to run after, make sure it has _id // has to run after, make sure it has _id
await runStaticFormulaChecks(appId, tableToSave, { oldTable }) 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
@ -127,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,
}) })
@ -136,24 +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 // has to run after, make sure it has _id
await runStaticFormulaChecks(appId, tableToDelete, { deletion: true }) await runStaticFormulaChecks(tableToDelete, { deletion: true })
await cleanupAttachments(appId, tableToDelete, { rows }) 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
} }

View File

@ -1,4 +1,3 @@
const CouchDB = require("../../../db")
const csvParser = require("../../../utilities/csvParser") const csvParser = require("../../../utilities/csvParser")
const { const {
getRowParams, getRowParams,
@ -26,10 +25,11 @@ 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") const { cloneDeep } = require("lodash/fp")
exports.clearColumns = async (appId, table, columnNames) => { exports.clearColumns = async (table, columnNames) => {
const db = new CouchDB(appId) const db = getAppDB()
const rows = await db.allDocs( const rows = await db.allDocs(
getRowParams(table._id, null, { getRowParams(table._id, null, {
include_docs: true, include_docs: true,
@ -43,7 +43,8 @@ exports.clearColumns = async (appId, table, columnNames) => {
) )
} }
exports.checkForColumnUpdates = async (appId, db, oldTable, updatedTable) => { exports.checkForColumnUpdates = async (oldTable, updatedTable) => {
const db = getAppDB()
let updatedRows = [] let updatedRows = []
const rename = updatedTable._rename const rename = updatedTable._rename
let deletedColumns = [] let deletedColumns = []
@ -73,9 +74,9 @@ exports.checkForColumnUpdates = async (appId, db, oldTable, updatedTable) => {
}) })
// cleanup any attachments from object storage for deleted attachment columns // cleanup any attachments from object storage for deleted attachment columns
await cleanupAttachments(appId, updatedTable, { oldTable, rows: rawRows }) 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 }
@ -102,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,
@ -152,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()
@ -210,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
@ -233,25 +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.appId,
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
} }
@ -260,8 +248,8 @@ class TableSaveFunctions {
} }
} }
exports.getAllInternalTables = async appId => { exports.getAllInternalTables = async () => {
const db = new CouchDB(appId) const db = getAppDB()
const internalTables = await db.allDocs( const internalTables = await db.allDocs(
getTableParams(null, { getTableParams(null, {
include_docs: true, include_docs: true,
@ -274,8 +262,8 @@ exports.getAllInternalTables = async appId => {
})) }))
} }
exports.getAllExternalTables = async (appId, datasourceId) => { exports.getAllExternalTables = async datasourceId => {
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) {
throw "Datasource is not configured fully." throw "Datasource is not configured fully."
@ -283,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
@ -363,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
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -145,6 +145,7 @@ describe("/automations", () => {
let table = await config.createTable() let table = await config.createTable()
automation.definition.trigger.inputs.tableId = table._id automation.definition.trigger.inputs.tableId = table._id
automation.definition.steps[0].inputs.row.tableId = table._id automation.definition.steps[0].inputs.row.tableId = table._id
automation.appId = config.appId
automation = await config.createAutomation(automation) automation = await config.createAutomation(automation)
await setup.delay(500) await setup.delay(500)
const res = await testAutomation(config, automation) const res = await testAutomation(config, automation)

View File

@ -82,7 +82,6 @@ describe("run misc tests", () => {
dataImport.schema[col] = { type: "string" } dataImport.schema[col] = { type: "string" }
} }
await tableUtils.handleDataImport( await tableUtils.handleDataImport(
config.getAppId(),
{ userId: "test" }, { userId: "test" },
table, table,
dataImport dataImport

View File

@ -1,10 +1,15 @@
const setup = require("./utilities") const setup = require("./utilities")
const { basicScreen } = setup.structures const { basicScreen } = setup.structures
const { checkBuilderEndpoint } = require("./utilities/TestFunctions") const { checkBuilderEndpoint, runInProd } = require("./utilities/TestFunctions")
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles") const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
const { doInAppContext } = require("@budibase/backend-core/context")
const route = "/test" const route = "/test"
// there are checks which are disabled in test env,
// these checks need to be enabled for this test
describe("/routing", () => { describe("/routing", () => {
let request = setup.getRequest() let request = setup.getRequest()
let config = setup.getConfig() let config = setup.getConfig()
@ -26,20 +31,24 @@ describe("/routing", () => {
describe("fetch", () => { describe("fetch", () => {
it("prevents a public user from accessing development app", async () => { it("prevents a public user from accessing development app", async () => {
await request await runInProd(() => {
.get(`/api/routing/client`) return request
.set(config.publicHeaders({ prodApp: false })) .get(`/api/routing/client`)
.expect(302) .set(config.publicHeaders({ prodApp: false }))
.expect(302)
})
}) })
it("prevents a non builder from accessing development app", async () => { it("prevents a non builder from accessing development app", async () => {
await request await runInProd(async () => {
.get(`/api/routing/client`) return request
.set(await config.roleHeaders({ .get(`/api/routing/client`)
roleId: BUILTIN_ROLE_IDS.BASIC, .set(await config.roleHeaders({
prodApp: false roleId: BUILTIN_ROLE_IDS.BASIC,
})) prodApp: false
.expect(302) }))
.expect(302)
})
}) })
it("returns the correct routing for basic user", async () => { it("returns the correct routing for basic user", async () => {
const res = await request const res = await request

View File

@ -1,6 +1,7 @@
const { outputProcessing } = require("../../../utilities/rowProcessor") const { outputProcessing } = require("../../../utilities/rowProcessor")
const setup = require("./utilities") const setup = require("./utilities")
const { basicRow } = setup.structures const { basicRow } = setup.structures
const { doInAppContext } = require("@budibase/backend-core/context")
// mock the fetch for the search system // mock the fetch for the search system
jest.mock("node-fetch") jest.mock("node-fetch")
@ -387,10 +388,12 @@ describe("/rows", () => {
}) })
// the environment needs configured for this // the environment needs configured for this
await setup.switchToSelfHosted(async () => { await setup.switchToSelfHosted(async () => {
const enriched = await outputProcessing({ appId: config.getAppId() }, table, [row]) doInAppContext(config.getAppId(), async () => {
expect(enriched[0].attachment[0].url).toBe( const enriched = await outputProcessing(table, [row])
`/prod-budi-app-assets/${config.getAppId()}/attachments/test/thing.csv` expect(enriched[0].attachment[0].url).toBe(
) `/prod-budi-app-assets/${config.getAppId()}/attachments/test/thing.csv`
)
})
}) })
}) })
}) })

View File

@ -1,9 +1,10 @@
const rowController = require("../../../controllers/row") const rowController = require("../../../controllers/row")
const appController = require("../../../controllers/application") const appController = require("../../../controllers/application")
const CouchDB = require("../../../../db")
const { AppStatus } = require("../../../../db/utils") const { AppStatus } = require("../../../../db/utils")
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles") const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
const { TENANT_ID } = require("../../../../tests/utilities/structures") const { TENANT_ID } = require("../../../../tests/utilities/structures")
const { getAppDB, doInAppContext } = require("@budibase/backend-core/context")
const env = require("../../../../environment")
function Request(appId, params) { function Request(appId, params) {
this.appId = appId this.appId = appId
@ -11,9 +12,15 @@ function Request(appId, params) {
this.request = {} this.request = {}
} }
function runRequest(appId, controlFunc, request) {
return doInAppContext(appId, async () => {
return controlFunc(request)
})
}
exports.getAllTableRows = async config => { exports.getAllTableRows = async config => {
const req = new Request(config.appId, { tableId: config.table._id }) const req = new Request(config.appId, { tableId: config.table._id })
await rowController.fetch(req) await runRequest(config.appId, rowController.fetch, req)
return req.body return req.body
} }
@ -26,14 +33,17 @@ exports.clearAllApps = async (tenantId = TENANT_ID) => {
} }
for (let app of apps) { for (let app of apps) {
const { appId } = app const { appId } = app
await appController.delete(new Request(null, { appId })) const req = new Request(null, { appId })
await runRequest(appId, appController.delete, req)
} }
} }
exports.clearAllAutomations = async config => { exports.clearAllAutomations = async config => {
const automations = await config.getAllAutomations() const automations = await config.getAllAutomations()
for (let auto of automations) { for (let auto of automations) {
await config.deleteAutomation(auto) await doInAppContext(config.appId, async () => {
await config.deleteAutomation(auto)
})
} }
} }
@ -96,20 +106,32 @@ exports.checkPermissionsEndpoint = async ({
.expect(403) .expect(403)
} }
exports.getDB = config => { exports.getDB = () => {
return new CouchDB(config.getAppId()) return getAppDB()
} }
exports.testAutomation = async (config, automation) => { exports.testAutomation = async (config, automation) => {
return await config.request return runRequest(automation.appId, async () => {
.post(`/api/automations/${automation._id}/test`) return await config.request
.send({ .post(`/api/automations/${automation._id}/test`)
row: { .send({
name: "Test", row: {
description: "TEST", name: "Test",
}, description: "TEST",
}) },
.set(config.defaultHeaders()) })
.expect("Content-Type", /json/) .set(config.defaultHeaders())
.expect(200) .expect("Content-Type", /json/)
.expect(200)
})
}
exports.runInProd = async func => {
const nodeEnv = env.NODE_ENV
const workerId = env.JEST_WORKER_ID
env._set("NODE_ENV", "PRODUCTION")
env._set("JEST_WORKER_ID", null)
await func()
env._set("NODE_ENV", nodeEnv)
env._set("JEST_WORKER_ID", workerId)
} }

View File

@ -53,13 +53,12 @@ exports.cleanInputValues = (inputs, schema) => {
* the automation but is instead part of the Table/Table. This function will get the table schema and use it to instead * the automation but is instead part of the Table/Table. This function will get the table schema and use it to instead
* perform the cleanInputValues function on the input row. * perform the cleanInputValues function on the input row.
* *
* @param {string} appId The instance which the Table/Table is contained under.
* @param {string} tableId The ID of the Table/Table which the schema is to be retrieved for. * @param {string} tableId The ID of the Table/Table which the schema is to be retrieved for.
* @param {object} row The input row structure which requires clean-up after having been through template statements. * @param {object} row The input row structure which requires clean-up after having been through template statements.
* @returns {Promise<Object>} The cleaned up rows object, will should now have all the required primitive types. * @returns {Promise<Object>} The cleaned up rows object, will should now have all the required primitive types.
*/ */
exports.cleanUpRow = async (appId, tableId, row) => { exports.cleanUpRow = async (tableId, row) => {
let table = await getTable(appId, tableId) let table = await getTable(tableId)
return exports.cleanInputValues(row, { properties: table.schema }) return exports.cleanInputValues(row, { properties: table.schema })
} }

View File

@ -78,7 +78,6 @@ exports.run = async function ({ inputs, appId, emitter }) {
try { try {
inputs.row = await automationUtils.cleanUpRow( inputs.row = await automationUtils.cleanUpRow(
appId,
inputs.row.tableId, inputs.row.tableId,
inputs.row inputs.row
) )

View File

@ -87,7 +87,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
try { try {
if (tableId) { if (tableId) {
inputs.row = await automationUtils.cleanUpRow(appId, tableId, inputs.row) inputs.row = await automationUtils.cleanUpRow(tableId, inputs.row)
} }
await rowController.patch(ctx) await rowController.patch(ctx)
return { return {

View File

@ -1,4 +1,3 @@
const CouchDB = require("../db")
const emitter = require("../events/index") const emitter = require("../events/index")
const { getAutomationParams } = require("../db/utils") const { getAutomationParams } = require("../db/utils")
const { coerce } = require("../utilities/rowProcessor") const { coerce } = require("../utilities/rowProcessor")
@ -9,6 +8,7 @@ const { queue } = require("./bullboard")
const { checkTestFlag } = require("../utilities/redis") const { checkTestFlag } = require("../utilities/redis")
const utils = require("./utils") const utils = require("./utils")
const env = require("../environment") const env = require("../environment")
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
const TRIGGER_DEFINITIONS = definitions const TRIGGER_DEFINITIONS = definitions
const JOB_OPTS = { const JOB_OPTS = {
@ -21,39 +21,41 @@ async function queueRelevantRowAutomations(event, eventType) {
throw `No appId specified for ${eventType} - check event emitters.` throw `No appId specified for ${eventType} - check event emitters.`
} }
const db = new CouchDB(event.appId) doInAppContext(event.appId, async () => {
let automations = await db.allDocs( const db = getAppDB()
getAutomationParams(null, { include_docs: true }) let automations = await db.allDocs(
) getAutomationParams(null, { include_docs: true })
)
// filter down to the correct event type // filter down to the correct event type
automations = automations.rows automations = automations.rows
.map(automation => automation.doc) .map(automation => automation.doc)
.filter(automation => { .filter(automation => {
const trigger = automation.definition.trigger const trigger = automation.definition.trigger
return trigger && trigger.event === eventType return trigger && trigger.event === eventType
}) })
for (let automation of automations) { for (let automation of automations) {
let automationDef = automation.definition let automationDef = automation.definition
let automationTrigger = automationDef ? automationDef.trigger : {} let automationTrigger = automationDef ? automationDef.trigger : {}
// don't queue events which are for dev apps, only way to test automations is // don't queue events which are for dev apps, only way to test automations is
// running tests on them, in production the test flag will never // running tests on them, in production the test flag will never
// be checked due to lazy evaluation (first always false) // be checked due to lazy evaluation (first always false)
if ( if (
!env.ALLOW_DEV_AUTOMATIONS && !env.ALLOW_DEV_AUTOMATIONS &&
isDevAppID(event.appId) && isDevAppID(event.appId) &&
!(await checkTestFlag(automation._id)) !(await checkTestFlag(automation._id))
) { ) {
continue continue
}
if (
automationTrigger.inputs &&
automationTrigger.inputs.tableId === event.row.tableId
) {
await queue.add({ automation, event }, JOB_OPTS)
}
} }
if ( })
automationTrigger.inputs &&
automationTrigger.inputs.tableId === event.row.tableId
) {
await queue.add({ automation, event }, JOB_OPTS)
}
}
} }
emitter.on("row:save", async function (event) { emitter.on("row:save", async function (event) {

View File

@ -6,8 +6,9 @@ const { queue } = require("./bullboard")
const newid = require("../db/newid") const newid = require("../db/newid")
const { updateEntityMetadata } = require("../utilities") const { updateEntityMetadata } = require("../utilities")
const { MetadataTypes } = require("../constants") const { MetadataTypes } = require("../constants")
const { getDeployedAppID } = require("@budibase/backend-core/db") const { getProdAppID } = require("@budibase/backend-core/db")
const { cloneDeep } = require("lodash/fp") const { cloneDeep } = require("lodash/fp")
const { getAppDB, getAppId } = require("@budibase/backend-core/context")
const WH_STEP_ID = definitions.WEBHOOK.stepId const WH_STEP_ID = definitions.WEBHOOK.stepId
const CRON_STEP_ID = definitions.CRON.stepId const CRON_STEP_ID = definitions.CRON.stepId
@ -27,7 +28,6 @@ exports.processEvent = async job => {
exports.updateTestHistory = async (appId, automation, history) => { exports.updateTestHistory = async (appId, automation, history) => {
return updateEntityMetadata( return updateEntityMetadata(
appId,
MetadataTypes.AUTOMATION_TEST_HISTORY, MetadataTypes.AUTOMATION_TEST_HISTORY,
automation._id, automation._id,
metadata => { metadata => {
@ -93,6 +93,9 @@ exports.enableCronTrigger = async (appId, automation) => {
) )
// Assign cron job ID from bull so we can remove it later if the cron trigger is removed // Assign cron job ID from bull so we can remove it later if the cron trigger is removed
trigger.cronJobId = job.id trigger.cronJobId = job.id
// can't use getAppDB here as this is likely to be called from dev app,
// but this call could be for dev app or prod app, need to just use what
// was passed in
const db = new CouchDB(appId) const db = new CouchDB(appId)
const response = await db.put(automation) const response = await db.put(automation)
automation._id = response.id automation._id = response.id
@ -109,7 +112,8 @@ exports.enableCronTrigger = async (appId, automation) => {
* @returns {Promise<object|undefined>} After this is complete the new automation object may have been updated and should be * @returns {Promise<object|undefined>} After this is complete the new automation object may have been updated and should be
* written to DB (this does not write to DB as it would be wasteful to repeat). * written to DB (this does not write to DB as it would be wasteful to repeat).
*/ */
exports.checkForWebhooks = async ({ appId, oldAuto, newAuto }) => { exports.checkForWebhooks = async ({ oldAuto, newAuto }) => {
const appId = getAppId()
const oldTrigger = oldAuto ? oldAuto.definition.trigger : null const oldTrigger = oldAuto ? oldAuto.definition.trigger : null
const newTrigger = newAuto ? newAuto.definition.trigger : null const newTrigger = newAuto ? newAuto.definition.trigger : null
const triggerChanged = const triggerChanged =
@ -128,7 +132,7 @@ exports.checkForWebhooks = async ({ appId, oldAuto, newAuto }) => {
oldTrigger.webhookId oldTrigger.webhookId
) { ) {
try { try {
let db = new CouchDB(appId) let db = getAppDB()
// need to get the webhook to get the rev // need to get the webhook to get the rev
const webhook = await db.get(oldTrigger.webhookId) const webhook = await db.get(oldTrigger.webhookId)
const ctx = { const ctx = {
@ -166,7 +170,7 @@ exports.checkForWebhooks = async ({ appId, oldAuto, newAuto }) => {
// the app ID has to be development for this endpoint // the app ID has to be development for this endpoint
// it can only be used when building the app // it can only be used when building the app
// but the trigger endpoint will always be used in production // but the trigger endpoint will always be used in production
const prodAppId = getDeployedAppID(appId) const prodAppId = getProdAppID(appId)
newTrigger.inputs = { newTrigger.inputs = {
schemaUrl: `api/webhooks/schema/${appId}/${id}`, schemaUrl: `api/webhooks/schema/${appId}/${id}`,
triggerUrl: `api/webhooks/trigger/${prodAppId}/${id}`, triggerUrl: `api/webhooks/trigger/${prodAppId}/${id}`,

View File

@ -1,4 +1,3 @@
const CouchDB = require("../index")
const { IncludeDocs, getLinkDocuments } = require("./linkUtils") const { IncludeDocs, getLinkDocuments } = require("./linkUtils")
const { const {
generateLinkID, generateLinkID,
@ -7,6 +6,7 @@ const {
} = require("../utils") } = require("../utils")
const Sentry = require("@sentry/node") const Sentry = require("@sentry/node")
const { FieldTypes, RelationshipTypes } = require("../../constants") const { FieldTypes, RelationshipTypes } = require("../../constants")
const { getAppDB } = require("@budibase/backend-core/context")
/** /**
* Creates a new link document structure which can be put to the database. It is important to * Creates a new link document structure which can be put to the database. It is important to
@ -52,9 +52,8 @@ function LinkDocument(
} }
class LinkController { class LinkController {
constructor({ appId, tableId, row, table, oldTable }) { constructor({ tableId, row, table, oldTable }) {
this._appId = appId this._db = getAppDB()
this._db = new CouchDB(appId)
this._tableId = tableId this._tableId = tableId
this._row = row this._row = row
this._table = table this._table = table
@ -99,7 +98,6 @@ class LinkController {
*/ */
getRowLinkDocs(rowId) { getRowLinkDocs(rowId) {
return getLinkDocuments({ return getLinkDocuments({
appId: this._appId,
tableId: this._tableId, tableId: this._tableId,
rowId, rowId,
includeDocs: IncludeDocs.INCLUDE, includeDocs: IncludeDocs.INCLUDE,
@ -111,7 +109,6 @@ class LinkController {
*/ */
getTableLinkDocs() { getTableLinkDocs() {
return getLinkDocuments({ return getLinkDocuments({
appId: this._appId,
tableId: this._tableId, tableId: this._tableId,
includeDocs: IncludeDocs.INCLUDE, includeDocs: IncludeDocs.INCLUDE,
}) })
@ -230,7 +227,6 @@ class LinkController {
if (linkedSchema.relationshipType === RelationshipTypes.ONE_TO_MANY) { if (linkedSchema.relationshipType === RelationshipTypes.ONE_TO_MANY) {
let links = ( let links = (
await getLinkDocuments({ await getLinkDocuments({
appId: this._appId,
tableId: field.tableId, tableId: field.tableId,
rowId: linkId, rowId: linkId,
includeDocs: IncludeDocs.EXCLUDE, includeDocs: IncludeDocs.EXCLUDE,

View File

@ -9,12 +9,12 @@ const {
getLinkedTable, getLinkedTable,
} = require("./linkUtils") } = require("./linkUtils")
const { flatten } = require("lodash") const { flatten } = require("lodash")
const CouchDB = require("../../db")
const { FieldTypes } = require("../../constants") const { FieldTypes } = require("../../constants")
const { getMultiIDParams, USER_METDATA_PREFIX } = require("../../db/utils") const { getMultiIDParams, USER_METDATA_PREFIX } = require("../../db/utils")
const { partition } = require("lodash") const { partition } = require("lodash")
const { getGlobalUsersFromMetadata } = require("../../utilities/global") const { getGlobalUsersFromMetadata } = require("../../utilities/global")
const { processFormulas } = require("../../utilities/rowProcessor/utils") const { processFormulas } = require("../../utilities/rowProcessor/utils")
const { getAppDB } = require("@budibase/backend-core/context")
/** /**
* This functionality makes sure that when rows with links are created, updated or deleted they are processed * This functionality makes sure that when rows with links are created, updated or deleted they are processed
@ -48,14 +48,13 @@ function clearRelationshipFields(table, rows) {
return rows return rows
} }
async function getLinksForRows(appId, rows) { async function getLinksForRows(rows) {
const tableIds = [...new Set(rows.map(el => el.tableId))] const tableIds = [...new Set(rows.map(el => el.tableId))]
// start by getting all the link values for performance reasons // start by getting all the link values for performance reasons
const responses = flatten( const responses = flatten(
await Promise.all( await Promise.all(
tableIds.map(tableId => tableIds.map(tableId =>
getLinkDocuments({ getLinkDocuments({
appId,
tableId: tableId, tableId: tableId,
includeDocs: IncludeDocs.EXCLUDE, includeDocs: IncludeDocs.EXCLUDE,
}) })
@ -72,9 +71,9 @@ async function getLinksForRows(appId, rows) {
) )
} }
async function getFullLinkedDocs(appId, links) { async function getFullLinkedDocs(links) {
// create DBs // create DBs
const db = new CouchDB(appId) const db = getAppDB()
const linkedRowIds = links.map(link => link.id) const linkedRowIds = links.map(link => link.id)
const uniqueRowIds = [...new Set(linkedRowIds)] const uniqueRowIds = [...new Set(linkedRowIds)]
let dbRows = (await db.allDocs(getMultiIDParams(uniqueRowIds))).rows.map( let dbRows = (await db.allDocs(getMultiIDParams(uniqueRowIds))).rows.map(
@ -88,7 +87,7 @@ async function getFullLinkedDocs(appId, links) {
let [users, other] = partition(linked, linkRow => let [users, other] = partition(linked, linkRow =>
linkRow._id.startsWith(USER_METDATA_PREFIX) linkRow._id.startsWith(USER_METDATA_PREFIX)
) )
users = await getGlobalUsersFromMetadata(appId, users) users = await getGlobalUsersFromMetadata(users)
return [...other, ...users] return [...other, ...users]
} }
@ -96,20 +95,16 @@ async function getFullLinkedDocs(appId, links) {
* Update link documents for a row or table - this is to be called by the API controller when a change is occurring. * Update link documents for a row or table - this is to be called by the API controller when a change is occurring.
* @param {string} args.eventType states what type of change which is occurring, means this can be expanded upon in the * @param {string} args.eventType states what type of change which is occurring, means this can be expanded upon in the
* future quite easily (all updates go through one function). * future quite easily (all updates go through one function).
* @param {string} args.appId The ID of the instance in which the change is occurring.
* @param {string} args.tableId The ID of the of the table which is being changed. * @param {string} args.tableId The ID of the of the table which is being changed.
* @param {object|null} args.row The row which is changing, e.g. created, updated or deleted. * @param {object|undefined} args.row The row which is changing, e.g. created, updated or deleted.
* @param {object|null} args.table If the table has already been retrieved this can be used to reduce database gets. * @param {object|undefined} args.table If the table has already been retrieved this can be used to reduce database gets.
* @param {object|null} args.oldTable If the table is being updated then the old table can be provided for differencing. * @param {object|undefined} args.oldTable If the table is being updated then the old table can be provided for differencing.
* @returns {Promise<object>} When the update is complete this will respond successfully. Returns the row for * @returns {Promise<object>} When the update is complete this will respond successfully. Returns the row for
* row operations and the table for table operations. * row operations and the table for table operations.
*/ */
exports.updateLinks = async function (args) { exports.updateLinks = async function (args) {
const { eventType, appId, row, tableId, table, oldTable } = args const { eventType, row, tableId, table, oldTable } = args
const baseReturnObj = row == null ? table : row const baseReturnObj = row == null ? table : row
if (appId == null) {
throw "Cannot operate without an instance ID."
}
// make sure table ID is set // make sure table ID is set
if (tableId == null && table != null) { if (tableId == null && table != null) {
args.tableId = table._id args.tableId = table._id
@ -146,26 +141,23 @@ exports.updateLinks = async function (args) {
/** /**
* Given a table and a list of rows this will retrieve all of the attached docs and enrich them into the row. * Given a table and a list of rows this will retrieve all of the attached docs and enrich them into the row.
* This is required for formula fields, this may only be utilised internally (for now). * This is required for formula fields, this may only be utilised internally (for now).
* @param {string} appId The ID of the app which this request is in the context of.
* @param {object} table The table from which the rows originated. * @param {object} table The table from which the rows originated.
* @param {array<object>} rows The rows which are to be enriched. * @param {array<object>} rows The rows which are to be enriched.
* @return {Promise<*>} returns the rows with all of the enriched relationships on it. * @return {Promise<*>} returns the rows with all of the enriched relationships on it.
*/ */
exports.attachFullLinkedDocs = async (appId, table, rows) => { exports.attachFullLinkedDocs = async (table, rows) => {
const linkedTableIds = getLinkedTableIDs(table) const linkedTableIds = getLinkedTableIDs(table)
if (linkedTableIds.length === 0) { if (linkedTableIds.length === 0) {
return rows return rows
} }
// create DBs
const db = new CouchDB(appId)
// get all the links // get all the links
const links = (await getLinksForRows(appId, rows)).filter(link => const links = (await getLinksForRows(rows)).filter(link =>
rows.some(row => row._id === link.thisId) rows.some(row => row._id === link.thisId)
) )
// clear any existing links that could be dupe'd // clear any existing links that could be dupe'd
rows = clearRelationshipFields(table, rows) rows = clearRelationshipFields(table, rows)
// now get the docs and combine into the rows // now get the docs and combine into the rows
let linked = await getFullLinkedDocs(appId, links) let linked = await getFullLinkedDocs(links)
const linkedTables = [] const linkedTables = []
for (let row of rows) { for (let row of rows) {
for (let link of links.filter(link => link.thisId === row._id)) { for (let link of links.filter(link => link.thisId === row._id)) {
@ -176,11 +168,7 @@ exports.attachFullLinkedDocs = async (appId, table, rows) => {
if (linkedRow) { if (linkedRow) {
const linkedTableId = const linkedTableId =
linkedRow.tableId || getRelatedTableForField(table, link.fieldName) linkedRow.tableId || getRelatedTableForField(table, link.fieldName)
const linkedTable = await getLinkedTable( const linkedTable = await getLinkedTable(linkedTableId, linkedTables)
db,
linkedTableId,
linkedTables
)
if (linkedTable) { if (linkedTable) {
row[link.fieldName].push(processFormulas(linkedTable, linkedRow)) row[link.fieldName].push(processFormulas(linkedTable, linkedRow))
} }
@ -192,18 +180,16 @@ exports.attachFullLinkedDocs = async (appId, table, rows) => {
/** /**
* This function will take the given enriched rows and squash the links to only contain the primary display field. * This function will take the given enriched rows and squash the links to only contain the primary display field.
* @param {string} appId The app in which the tables/rows/links exist.
* @param {object} table The table from which the rows originated. * @param {object} table The table from which the rows originated.
* @param {array<object>} enriched The pre-enriched rows (full docs) which are to be squashed. * @param {array<object>} enriched The pre-enriched rows (full docs) which are to be squashed.
* @returns {Promise<Array>} The rows after having their links squashed to only contain the ID and primary display. * @returns {Promise<Array>} The rows after having their links squashed to only contain the ID and primary display.
*/ */
exports.squashLinksToPrimaryDisplay = async (appId, table, enriched) => { exports.squashLinksToPrimaryDisplay = async (table, enriched) => {
const db = new CouchDB(appId)
// will populate this as we find them // will populate this as we find them
const linkedTables = [table] const linkedTables = [table]
for (let row of enriched) { for (let row of enriched) {
// this only fetches the table if its not already in array // this only fetches the table if its not already in array
const rowTable = await getLinkedTable(db, row.tableId, linkedTables) const rowTable = await getLinkedTable(row.tableId, linkedTables)
for (let [column, schema] of Object.entries(rowTable.schema)) { for (let [column, schema] of Object.entries(rowTable.schema)) {
if (schema.type !== FieldTypes.LINK || !Array.isArray(row[column])) { if (schema.type !== FieldTypes.LINK || !Array.isArray(row[column])) {
continue continue
@ -211,7 +197,7 @@ exports.squashLinksToPrimaryDisplay = async (appId, table, enriched) => {
const newLinks = [] const newLinks = []
for (let link of row[column]) { for (let link of row[column]) {
const linkTblId = link.tableId || getRelatedTableForField(table, column) const linkTblId = link.tableId || getRelatedTableForField(table, column)
const linkedTable = await getLinkedTable(db, linkTblId, linkedTables) const linkedTable = await getLinkedTable(linkTblId, linkedTables)
const obj = { _id: link._id } const obj = { _id: link._id }
if (link[linkedTable.primaryDisplay]) { if (link[linkedTable.primaryDisplay]) {
obj.primaryDisplay = link[linkedTable.primaryDisplay] obj.primaryDisplay = link[linkedTable.primaryDisplay]

View File

@ -1,8 +1,8 @@
const CouchDB = require("../index")
const Sentry = require("@sentry/node") const Sentry = require("@sentry/node")
const { ViewNames, getQueryIndex } = require("../utils") const { ViewNames, getQueryIndex } = require("../utils")
const { FieldTypes } = require("../../constants") const { FieldTypes } = require("../../constants")
const { createLinkView } = require("../views/staticViews") const { createLinkView } = require("../views/staticViews")
const { getAppDB } = require("@budibase/backend-core/context")
/** /**
* Only needed so that boolean parameters are being used for includeDocs * Only needed so that boolean parameters are being used for includeDocs
@ -17,7 +17,6 @@ exports.createLinkView = createLinkView
/** /**
* Gets the linking documents, not the linked documents themselves. * Gets the linking documents, not the linked documents themselves.
* @param {string} args.appId The instance in which we are searching for linked rows.
* @param {string} args.tableId The table which we are searching for linked rows against. * @param {string} args.tableId The table which we are searching for linked rows against.
* @param {string|null} args.fieldName The name of column/field which is being altered, only looking for * @param {string|null} args.fieldName The name of column/field which is being altered, only looking for
* linking documents that are related to it. If this is not specified then the table level will be assumed. * linking documents that are related to it. If this is not specified then the table level will be assumed.
@ -30,8 +29,8 @@ exports.createLinkView = createLinkView
* (if any). * (if any).
*/ */
exports.getLinkDocuments = async function (args) { exports.getLinkDocuments = async function (args) {
const { appId, tableId, rowId, includeDocs } = args const { tableId, rowId, includeDocs } = args
const db = new CouchDB(appId) const db = getAppDB()
let params let params
if (rowId != null) { if (rowId != null) {
params = { key: [tableId, rowId] } params = { key: [tableId, rowId] }
@ -68,7 +67,7 @@ exports.getLinkDocuments = async function (args) {
} catch (err) { } catch (err) {
// check if the view doesn't exist, it should for all new instances // check if the view doesn't exist, it should for all new instances
if (err != null && err.name === "not_found") { if (err != null && err.name === "not_found") {
await exports.createLinkView(appId) await exports.createLinkView()
return exports.getLinkDocuments(arguments[0]) return exports.getLinkDocuments(arguments[0])
} else { } else {
/* istanbul ignore next */ /* istanbul ignore next */
@ -89,7 +88,8 @@ exports.getLinkedTableIDs = table => {
.map(column => column.tableId) .map(column => column.tableId)
} }
exports.getLinkedTable = async (db, id, tables) => { exports.getLinkedTable = async (id, tables) => {
const db = getAppDB()
let linkedTable = tables.find(table => table._id === id) let linkedTable = tables.find(table => table._id === id)
if (linkedTable) { if (linkedTable) {
return linkedTable return linkedTable

View File

@ -20,7 +20,6 @@ describe("test the link controller", () => {
function createLinkController(table, row = null, oldTable = null) { function createLinkController(table, row = null, oldTable = null) {
const linkConfig = { const linkConfig = {
appId: config.getAppId(),
tableId: table._id, tableId: table._id,
table, table,
} }

View File

@ -1,8 +1,8 @@
const TestConfig = require("../../tests/utilities/TestConfiguration") const TestConfig = require("../../tests/utilities/TestConfiguration")
const { basicTable, basicLinkedRow } = require("../../tests/utilities/structures") const { basicTable } = require("../../tests/utilities/structures")
const linkUtils = require("../linkedRows/linkUtils") const linkUtils = require("../linkedRows/linkUtils")
const links = require("../linkedRows")
const CouchDB = require("../index") const CouchDB = require("../index")
const { getAppDB } = require("@budibase/backend-core/context")
describe("test link functionality", () => { describe("test link functionality", () => {
const config = new TestConfig(false) const config = new TestConfig(false)
@ -11,18 +11,18 @@ describe("test link functionality", () => {
let db, table let db, table
beforeEach(async () => { beforeEach(async () => {
await config.init() await config.init()
db = new CouchDB(config.getAppId()) db = getAppDB()
table = await config.createTable() table = await config.createTable()
}) })
it("should be able to retrieve a linked table from a list", async () => { it("should be able to retrieve a linked table from a list", async () => {
const retrieved = await linkUtils.getLinkedTable(db, table._id, [table]) const retrieved = await linkUtils.getLinkedTable(table._id, [table])
expect(retrieved._id).toBe(table._id) expect(retrieved._id).toBe(table._id)
}) })
it("should be able to retrieve a table from DB and update list", async () => { it("should be able to retrieve a table from DB and update list", async () => {
const tables = [] const tables = []
const retrieved = await linkUtils.getLinkedTable(db, table._id, tables) const retrieved = await linkUtils.getLinkedTable(table._id, tables)
expect(retrieved._id).toBe(table._id) expect(retrieved._id).toBe(table._id)
expect(tables[0]).toBeDefined() expect(tables[0]).toBeDefined()
}) })
@ -51,7 +51,6 @@ describe("test link functionality", () => {
const db = new CouchDB("test") const db = new CouchDB("test")
await db.put({ _id: "_design/database", views: {} }) await db.put({ _id: "_design/database", views: {} })
const output = await linkUtils.getLinkDocuments({ const output = await linkUtils.getLinkDocuments({
appId: "test",
tableId: "test", tableId: "test",
rowId: "test", rowId: "test",
includeDocs: false, includeDocs: false,

View File

@ -1,4 +1,4 @@
const CouchDB = require("../index") const { getAppDB } = require("@budibase/backend-core/context")
const { const {
DocumentTypes, DocumentTypes,
SEPARATOR, SEPARATOR,
@ -21,12 +21,11 @@ const SCREEN_PREFIX = DocumentTypes.SCREEN + SEPARATOR
/** /**
* Creates the link view for the instance, this will overwrite the existing one, but this should only * Creates the link view for the instance, this will overwrite the existing one, but this should only
* be called if it is found that the view does not exist. * be called if it is found that the view does not exist.
* @param {string} appId The instance to which the view should be added.
* @returns {Promise<void>} The view now exists, please note that the next view of this query will actually build it, * @returns {Promise<void>} The view now exists, please note that the next view of this query will actually build it,
* so it may be slow. * so it may be slow.
*/ */
exports.createLinkView = async appId => { exports.createLinkView = async () => {
const db = new CouchDB(appId) const db = getAppDB()
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
const view = { const view = {
map: function (doc) { map: function (doc) {
@ -57,8 +56,8 @@ exports.createLinkView = async appId => {
await db.put(designDoc) await db.put(designDoc)
} }
exports.createRoutingView = async appId => { exports.createRoutingView = async () => {
const db = new CouchDB(appId) const db = getAppDB()
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
const view = { const view = {
// if using variables in a map function need to inject them before use // if using variables in a map function need to inject them before use
@ -78,8 +77,8 @@ exports.createRoutingView = async appId => {
await db.put(designDoc) await db.put(designDoc)
} }
async function searchIndex(appId, indexName, fnString) { async function searchIndex(indexName, fnString) {
const db = new CouchDB(appId) const db = getAppDB()
const designDoc = await db.get("_design/database") const designDoc = await db.get("_design/database")
designDoc.indexes = { designDoc.indexes = {
[indexName]: { [indexName]: {
@ -90,9 +89,8 @@ async function searchIndex(appId, indexName, fnString) {
await db.put(designDoc) await db.put(designDoc)
} }
exports.createAllSearchIndex = async appId => { exports.createAllSearchIndex = async () => {
await searchIndex( await searchIndex(
appId,
SearchIndexes.ROWS, SearchIndexes.ROWS,
function (doc) { function (doc) {
function idx(input, prev) { function idx(input, prev) {

View File

@ -2,7 +2,8 @@ function isTest() {
return ( return (
process.env.NODE_ENV === "jest" || process.env.NODE_ENV === "jest" ||
process.env.NODE_ENV === "cypress" || process.env.NODE_ENV === "cypress" ||
process.env.JEST_WORKER_ID != null (process.env.JEST_WORKER_ID != null &&
process.env.JEST_WORKER_ID !== "null")
) )
} }

View File

@ -52,7 +52,10 @@ export function buildExternalTableId(datasourceId: string, tableName: string) {
return `${datasourceId}${DOUBLE_SEPARATOR}${tableName}` return `${datasourceId}${DOUBLE_SEPARATOR}${tableName}`
} }
export function breakExternalTableId(tableId: string) { export function breakExternalTableId(tableId: string | undefined) {
if (!tableId) {
return {}
}
const parts = tableId.split(DOUBLE_SEPARATOR) const parts = tableId.split(DOUBLE_SEPARATOR)
let tableName = parts.pop() let tableName = parts.pop()
// if they need joined // if they need joined

View File

@ -10,6 +10,7 @@ const {
const builderMiddleware = require("./builder") const builderMiddleware = require("./builder")
const { isWebhookEndpoint } = require("./utils") const { isWebhookEndpoint } = require("./utils")
const { buildCsrfMiddleware } = require("@budibase/backend-core/auth") const { buildCsrfMiddleware } = require("@budibase/backend-core/auth")
const { getAppId } = require("@budibase/backend-core/context")
function hasResource(ctx) { function hasResource(ctx) {
return ctx.resourceId != null return ctx.resourceId != null
@ -45,7 +46,7 @@ const checkAuthorizedResource = async (
) => { ) => {
// get the user's roles // get the user's roles
const roleId = ctx.roleId || BUILTIN_ROLE_IDS.PUBLIC const roleId = ctx.roleId || BUILTIN_ROLE_IDS.PUBLIC
const userRoles = await getUserRoleHierarchy(ctx.appId, roleId, { const userRoles = await getUserRoleHierarchy(roleId, {
idOnly: false, idOnly: false,
}) })
const permError = "User does not have permission" const permError = "User does not have permission"
@ -81,8 +82,9 @@ module.exports =
// get the resource roles // get the resource roles
let resourceRoles = [] let resourceRoles = []
if (ctx.appId && hasResource(ctx)) { const appId = getAppId()
resourceRoles = await getRequiredResourceRole(ctx.appId, permLevel, ctx) if (appId && hasResource(ctx)) {
resourceRoles = await getRequiredResourceRole(permLevel, ctx)
} }
// if the resource is public, proceed // if the resource is public, proceed

View File

@ -5,7 +5,7 @@ const {
checkDebounce, checkDebounce,
setDebounce, setDebounce,
} = require("../utilities/redis") } = require("../utilities/redis")
const CouchDB = require("../db") const { getDB } = require("@budibase/backend-core/db")
const { DocumentTypes } = require("../db/utils") const { DocumentTypes } = require("../db/utils")
const { PermissionTypes } = require("@budibase/backend-core/permissions") const { PermissionTypes } = require("@budibase/backend-core/permissions")
const { app: appCache } = require("@budibase/backend-core/cache") const { app: appCache } = require("@budibase/backend-core/cache")
@ -48,7 +48,7 @@ async function updateAppUpdatedAt(ctx) {
if (ctx.method === "GET" || (await checkDebounce(appId))) { if (ctx.method === "GET" || (await checkDebounce(appId))) {
return return
} }
const db = new CouchDB(appId) const db = getDB(appId)
const metadata = await db.get(DocumentTypes.APP_METADATA) const metadata = await db.get(DocumentTypes.APP_METADATA)
metadata.updatedAt = new Date().toISOString() metadata.updatedAt = new Date().toISOString()
const response = await db.put(metadata) const response = await db.put(metadata)

View File

@ -11,9 +11,9 @@ const { generateUserMetadataID, isDevAppID } = require("../db/utils")
const { dbExists } = require("@budibase/backend-core/db") const { dbExists } = require("@budibase/backend-core/db")
const { isUserInAppTenant } = require("@budibase/backend-core/tenancy") const { isUserInAppTenant } = require("@budibase/backend-core/tenancy")
const { getCachedSelf } = require("../utilities/global") const { getCachedSelf } = require("../utilities/global")
const CouchDB = require("../db")
const env = require("../environment") const env = require("../environment")
const { isWebhookEndpoint } = require("./utils") const { isWebhookEndpoint } = require("./utils")
const { doInAppContext } = require("@budibase/backend-core/context")
module.exports = async (ctx, next) => { module.exports = async (ctx, next) => {
// try to get the appID from the request // try to get the appID from the request
@ -31,7 +31,7 @@ module.exports = async (ctx, next) => {
// check the app exists referenced in cookie // check the app exists referenced in cookie
if (appCookie) { if (appCookie) {
const appId = appCookie.appId const appId = appCookie.appId
const exists = await dbExists(CouchDB, appId) const exists = await dbExists(appId)
if (!exists) { if (!exists) {
clearCookie(ctx, Cookies.CurrentApp) clearCookie(ctx, Cookies.CurrentApp)
return next() return next()
@ -41,13 +41,15 @@ module.exports = async (ctx, next) => {
} }
// deny access to application preview // deny access to application preview
if ( if (!env.isTest()) {
isDevAppID(requestAppId) && if (
!isWebhookEndpoint(ctx) && isDevAppID(requestAppId) &&
(!ctx.user || !ctx.user.builder || !ctx.user.builder.global) !isWebhookEndpoint(ctx) &&
) { (!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
clearCookie(ctx, Cookies.CurrentApp) ) {
return ctx.redirect("/") clearCookie(ctx, Cookies.CurrentApp)
return ctx.redirect("/")
}
} }
let appId, let appId,
@ -68,44 +70,46 @@ module.exports = async (ctx, next) => {
return next() return next()
} }
let noCookieSet = false return doInAppContext(appId, async () => {
// if the user not in the right tenant then make sure they have no permissions let noCookieSet = false
// need to judge this only based on the request app ID, // if the user not in the right tenant then make sure they have no permissions
if ( // need to judge this only based on the request app ID,
env.MULTI_TENANCY && if (
ctx.user && env.MULTI_TENANCY &&
requestAppId && ctx.user &&
!isUserInAppTenant(requestAppId) requestAppId &&
) { !isUserInAppTenant(requestAppId)
// don't error, simply remove the users rights (they are a public user) ) {
delete ctx.user.builder // don't error, simply remove the users rights (they are a public user)
delete ctx.user.admin delete ctx.user.builder
delete ctx.user.roles delete ctx.user.admin
roleId = BUILTIN_ROLE_IDS.PUBLIC delete ctx.user.roles
noCookieSet = true roleId = BUILTIN_ROLE_IDS.PUBLIC
} noCookieSet = true
ctx.appId = appId
if (roleId) {
ctx.roleId = roleId
const userId = ctx.user ? generateUserMetadataID(ctx.user._id) : null
ctx.user = {
...ctx.user,
// override userID with metadata one
_id: userId,
userId,
roleId,
role: await getRole(appId, roleId),
} }
}
if (
(requestAppId !== appId ||
appCookie == null ||
appCookie.appId !== requestAppId) &&
!noCookieSet
) {
setCookie(ctx, { appId }, Cookies.CurrentApp)
}
return next() ctx.appId = appId
if (roleId) {
ctx.roleId = roleId
const userId = ctx.user ? generateUserMetadataID(ctx.user._id) : null
ctx.user = {
...ctx.user,
// override userID with metadata one
_id: userId,
userId,
roleId,
role: await getRole(roleId),
}
}
if (
(requestAppId !== appId ||
appCookie == null ||
appCookie.appId !== requestAppId) &&
!noCookieSet
) {
setCookie(ctx, { appId }, Cookies.CurrentApp)
}
return next()
})
} }

View File

@ -11,6 +11,9 @@ const authorizedMiddleware = require("../authorized")
const env = require("../../environment") const env = require("../../environment")
const { PermissionTypes, PermissionLevels } = require("@budibase/backend-core/permissions") const { PermissionTypes, PermissionLevels } = require("@budibase/backend-core/permissions")
require("@budibase/backend-core").init(require("../../db")) require("@budibase/backend-core").init(require("../../db"))
const { doInAppContext } = require("@budibase/backend-core/context")
const APP_ID = ""
class TestConfiguration { class TestConfiguration {
constructor(role) { constructor(role) {
@ -23,7 +26,7 @@ class TestConfiguration {
request: { request: {
url: "" url: ""
}, },
appId: "", appId: APP_ID,
auth: {}, auth: {},
next: this.next, next: this.next,
throw: this.throw, throw: this.throw,
@ -32,7 +35,9 @@ class TestConfiguration {
} }
executeMiddleware() { executeMiddleware() {
return this.middleware(this.ctx, this.next) return doInAppContext(APP_ID, () => {
return this.middleware(this.ctx, this.next)
})
} }
setUser(user) { setUser(user) {

View File

@ -1,6 +1,11 @@
mockAuthWithNoCookie() mockAuthWithNoCookie()
mockWorker() mockWorker()
jest.mock("@budibase/backend-core/db", () => ({
...jest.requireActual("@budibase/backend-core/db"),
dbExists: () => true,
}))
function mockWorker() { function mockWorker() {
jest.mock("../../utilities/workerRequests", () => ({ jest.mock("../../utilities/workerRequests", () => ({
getGlobalSelf: () => { getGlobalSelf: () => {
@ -50,6 +55,7 @@ function mockAuthWithCookie() {
return "app_test" return "app_test"
}, },
setCookie: jest.fn(), setCookie: jest.fn(),
clearCookie: jest.fn(),
getCookie: () => ({appId: "app_different", roleId: "PUBLIC"}), getCookie: () => ({appId: "app_different", roleId: "PUBLIC"}),
})) }))
jest.mock("@budibase/backend-core/constants", () => ({ jest.mock("@budibase/backend-core/constants", () => ({

View File

@ -1,10 +1,10 @@
const CouchDB = require("../db")
const usageQuota = require("../utilities/usageQuota") const usageQuota = require("../utilities/usageQuota")
const { getUniqueRows } = require("../utilities/usageQuota/rows") const { getUniqueRows } = require("../utilities/usageQuota/rows")
const { const {
isExternalTable, isExternalTable,
isRowId: isExternalRowId, isRowId: isExternalRowId,
} = require("../integrations/utils") } = require("../integrations/utils")
const { getAppDB } = require("@budibase/backend-core/context")
// currently only counting new writes and deletes // currently only counting new writes and deletes
const METHOD_MAP = { const METHOD_MAP = {
@ -46,7 +46,7 @@ module.exports = async (ctx, next) => {
const usageId = ctx.request.body._id const usageId = ctx.request.body._id
try { try {
if (ctx.appId) { if (ctx.appId) {
const db = new CouchDB(ctx.appId) const db = getAppDB()
await db.get(usageId) await db.get(usageId)
} }
return next() return next()

View File

@ -1,12 +1,13 @@
const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy") // @ts-ignore
const { getAllApps } = require("@budibase/backend-core/db") import { getGlobalDB, getTenantId } from "@budibase/backend-core/tenancy"
import CouchDB from "../../../db" // @ts-ignore
import { getAllApps } from "@budibase/backend-core/db"
import { getUsageQuotaDoc } from "../../../utilities/usageQuota" import { getUsageQuotaDoc } from "../../../utilities/usageQuota"
export const run = async () => { export const run = async () => {
const db = getGlobalDB() const db = getGlobalDB()
// get app count // get app count
const devApps = await getAllApps(CouchDB, { dev: true }) const devApps = await getAllApps({ dev: true })
const appCount = devApps ? devApps.length : 0 const appCount = devApps ? devApps.length : 0
// sync app count // sync app count

View File

@ -1,13 +1,14 @@
const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy") // @ts-ignore
const { getAllApps } = require("@budibase/backend-core/db") import { getGlobalDB, getTenantId } from "@budibase/backend-core/tenancy"
import CouchDB from "../../../db" // @ts-ignore
import { getAllApps } from "@budibase/backend-core/db"
import { getUsageQuotaDoc } from "../../../utilities/usageQuota" import { getUsageQuotaDoc } from "../../../utilities/usageQuota"
import { getUniqueRows } from "../../../utilities/usageQuota/rows" import { getUniqueRows } from "../../../utilities/usageQuota/rows"
export const run = async () => { export const run = async () => {
const db = getGlobalDB() const db = getGlobalDB()
// get all rows in all apps // get all rows in all apps
const allApps = await getAllApps(CouchDB, { all: true }) const allApps = await getAllApps({ all: true })
const appIds = allApps ? allApps.map((app: { appId: any }) => app.appId) : [] const appIds = allApps ? allApps.map((app: { appId: any }) => app.appId) : []
const rows = await getUniqueRows(appIds) const rows = await getUniqueRows(appIds)
const rowCount = rows ? rows.length : 0 const rowCount = rows ? rows.length : 0

View File

@ -1,3 +1,6 @@
const core = require("@budibase/backend-core")
const CouchDB = require("../../db")
core.init(CouchDB)
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles") const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
const env = require("../../environment") const env = require("../../environment")
const { const {
@ -17,13 +20,11 @@ const supertest = require("supertest")
const { cleanup } = require("../../utilities/fileSystem") const { cleanup } = require("../../utilities/fileSystem")
const { Cookies, Headers } = require("@budibase/backend-core/constants") const { Cookies, Headers } = require("@budibase/backend-core/constants")
const { jwt } = require("@budibase/backend-core/auth") const { jwt } = require("@budibase/backend-core/auth")
const core = require("@budibase/backend-core")
const { getGlobalDB } = require("@budibase/backend-core/tenancy") const { getGlobalDB } = require("@budibase/backend-core/tenancy")
const { createASession } = require("@budibase/backend-core/sessions") const { createASession } = require("@budibase/backend-core/sessions")
const { user: userCache } = require("@budibase/backend-core/cache") const { user: userCache } = require("@budibase/backend-core/cache")
const CouchDB = require("../../db")
const newid = require("../../db/newid") const newid = require("../../db/newid")
core.init(CouchDB) const context = require("@budibase/backend-core/context")
const GLOBAL_USER_ID = "us_uuid1" const GLOBAL_USER_ID = "us_uuid1"
const EMAIL = "babs@babs.com" const EMAIL = "babs@babs.com"
@ -65,11 +66,21 @@ class TestConfiguration {
request.request = { request.request = {
body: config, body: config,
} }
if (params) { async function run() {
request.params = params if (params) {
request.params = params
}
await controlFunc(request)
return request.body
}
// check if already in a context
if (context.getAppId() == null) {
return context.doInAppContext(this.appId, async () => {
return run()
})
} else {
return run()
} }
await controlFunc(request)
return request.body
} }
async globalUser({ async globalUser({
@ -175,6 +186,7 @@ class TestConfiguration {
// create dev app // create dev app
this.app = await this._req({ name: appName }, null, controllers.app.create) this.app = await this._req({ name: appName }, null, controllers.app.create)
this.appId = this.app.appId this.appId = this.app.appId
context.updateAppId(this.appId)
// create production app // create production app
this.prodApp = await this.deploy() this.prodApp = await this.deploy()
@ -187,14 +199,16 @@ class TestConfiguration {
} }
async deploy() { async deploy() {
const deployment = await this._req(null, null, controllers.deploy.deployApp) await this._req(null, null, controllers.deploy.deployApp)
const prodAppId = deployment.appId.replace("_dev", "") const prodAppId = this.getAppId().replace("_dev", "")
const appPackage = await this._req( return context.doInAppContext(prodAppId, async () => {
null, const appPackage = await this._req(
{ appId: prodAppId }, null,
controllers.app.fetchAppPackage { appId: prodAppId },
) controllers.app.fetchAppPackage
return appPackage.application )
return appPackage.application
})
} }
async updateTable(config = null) { async updateTable(config = null) {
@ -423,42 +437,47 @@ class TestConfiguration {
async login({ roleId, userId, builder, prodApp = false } = {}) { async login({ roleId, userId, builder, prodApp = false } = {}) {
const appId = prodApp ? this.prodAppId : this.appId const appId = prodApp ? this.prodAppId : this.appId
return context.doInAppContext(appId, async () => {
userId = !userId ? `us_uuid1` : userId userId = !userId ? `us_uuid1` : userId
if (!this.request) { if (!this.request) {
throw "Server has not been opened, cannot login." throw "Server has not been opened, cannot login."
} }
// make sure the user exists in the global DB // make sure the user exists in the global DB
if (roleId !== BUILTIN_ROLE_IDS.PUBLIC) { if (roleId !== BUILTIN_ROLE_IDS.PUBLIC) {
await this.globalUser({ await this.globalUser({
userId, id: userId,
builder, builder,
roles: { [this.prodAppId]: roleId }, roles: { [this.prodAppId]: roleId },
})
}
await createASession(userId, {
sessionId: "sessionid",
tenantId: TENANT_ID,
}) })
} // have to fake this
// have to fake this const auth = {
const auth = { userId,
userId, sessionId: "sessionid",
sessionId: "sessionid", tenantId: TENANT_ID,
tenantId: TENANT_ID, }
} const app = {
const app = { roleId: roleId,
roleId: roleId, appId,
appId, }
} const authToken = jwt.sign(auth, env.JWT_SECRET)
const authToken = jwt.sign(auth, env.JWT_SECRET) const appToken = jwt.sign(app, env.JWT_SECRET)
const appToken = jwt.sign(app, env.JWT_SECRET)
// returning necessary request headers // returning necessary request headers
await userCache.invalidateUser(userId) await userCache.invalidateUser(userId)
return { return {
Accept: "application/json", Accept: "application/json",
Cookie: [ Cookie: [
`${Cookies.Auth}=${authToken}`, `${Cookies.Auth}=${authToken}`,
`${Cookies.CurrentApp}=${appToken}`, `${Cookies.CurrentApp}=${appToken}`,
], ],
[Headers.APP_ID]: appId, [Headers.APP_ID]: appId,
} }
})
} }
} }

View File

@ -5,11 +5,11 @@ const automationUtils = require("../automations/automationUtils")
const AutomationEmitter = require("../events/AutomationEmitter") const AutomationEmitter = require("../events/AutomationEmitter")
const { processObject } = require("@budibase/string-templates") const { processObject } = require("@budibase/string-templates")
const { DEFAULT_TENANT_ID } = require("@budibase/backend-core/constants") const { DEFAULT_TENANT_ID } = require("@budibase/backend-core/constants")
const CouchDB = require("../db")
const { DocumentTypes, isDevAppID } = require("../db/utils") const { DocumentTypes, isDevAppID } = require("../db/utils")
const { doInTenant } = require("@budibase/backend-core/tenancy") const { doInTenant } = require("@budibase/backend-core/tenancy")
const usage = require("../utilities/usageQuota") const usage = require("../utilities/usageQuota")
const { definitions: triggerDefs } = require("../automations/triggerInfo") const { definitions: triggerDefs } = require("../automations/triggerInfo")
const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId const FILTER_STEP_ID = actions.ACTION_DEFINITIONS.FILTER.stepId
const CRON_STEP_ID = triggerDefs.CRON.stepId const CRON_STEP_ID = triggerDefs.CRON.stepId
@ -59,11 +59,10 @@ class Orchestrator {
} }
async getApp() { async getApp() {
const appId = this._appId
if (this._app) { if (this._app) {
return this._app return this._app
} }
const db = new CouchDB(appId) const db = getAppDB()
this._app = await db.get(DocumentTypes.APP_METADATA) this._app = await db.get(DocumentTypes.APP_METADATA)
return this._app return this._app
} }
@ -131,16 +130,19 @@ class Orchestrator {
} }
module.exports = (input, callback) => { module.exports = (input, callback) => {
const automationOrchestrator = new Orchestrator( const appId = input.data.event.appId
input.data.automation, doInAppContext(appId, () => {
input.data.event const automationOrchestrator = new Orchestrator(
) input.data.automation,
automationOrchestrator input.data.event
.execute() )
.then(response => { automationOrchestrator
callback(null, response) .execute()
}) .then(response => {
.catch(err => { callback(null, response)
callback(err) })
}) .catch(err => {
callback(err)
})
})
} }

View File

@ -3,14 +3,13 @@ threadUtils.threadSetup()
const ScriptRunner = require("../utilities/scriptRunner") const ScriptRunner = require("../utilities/scriptRunner")
const { integrations } = require("../integrations") const { integrations } = require("../integrations")
const { processStringSync } = require("@budibase/string-templates") const { processStringSync } = require("@budibase/string-templates")
const CouchDB = require("../db") const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
const IS_TRIPLE_BRACE = new RegExp(/^{{3}.*}{3}$/) const IS_TRIPLE_BRACE = new RegExp(/^{{3}.*}{3}$/)
const IS_HANDLEBARS = new RegExp(/^{{2}.*}{2}$/) const IS_HANDLEBARS = new RegExp(/^{{2}.*}{2}$/)
class QueryRunner { class QueryRunner {
constructor(input, flags = { noRecursiveQuery: false }) { constructor(input, flags = { noRecursiveQuery: false }) {
this.appId = input.appId
this.datasource = input.datasource this.datasource = input.datasource
this.queryVerb = input.queryVerb this.queryVerb = input.queryVerb
this.fields = input.fields this.fields = input.fields
@ -104,12 +103,11 @@ class QueryRunner {
} }
async runAnotherQuery(queryId, parameters) { async runAnotherQuery(queryId, parameters) {
const db = new CouchDB(this.appId) 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)
return new QueryRunner( return new QueryRunner(
{ {
appId: this.appId,
datasource, datasource,
queryVerb: query.queryVerb, queryVerb: query.queryVerb,
fields: query.fields, fields: query.fields,
@ -223,12 +221,14 @@ class QueryRunner {
} }
module.exports = (input, callback) => { module.exports = (input, callback) => {
const Runner = new QueryRunner(input) doInAppContext(input.appId, () => {
Runner.execute() const Runner = new QueryRunner(input)
.then(response => { Runner.execute()
callback(null, response) .then(response => {
}) callback(null, response)
.catch(err => { })
callback(err) .catch(err => {
}) callback(err)
})
})
} }

View File

@ -1,5 +1,4 @@
const { budibaseTempDir } = require("../budibaseDir") const { budibaseTempDir } = require("../budibaseDir")
const { isDev } = require("../index")
const fs = require("fs") const fs = require("fs")
const { join } = require("path") const { join } = require("path")
const uuid = require("uuid/v4") const uuid = require("uuid/v4")
@ -20,6 +19,7 @@ const {
LINK_USER_METADATA_PREFIX, LINK_USER_METADATA_PREFIX,
} = require("../../db/utils") } = require("../../db/utils")
const MemoryStream = require("memorystream") const MemoryStream = require("memorystream")
const { getAppId } = require("@budibase/backend-core/context")
const TOP_LEVEL_PATH = join(__dirname, "..", "..", "..") const TOP_LEVEL_PATH = join(__dirname, "..", "..", "..")
const NODE_MODULES_PATH = join(TOP_LEVEL_PATH, "node_modules") const NODE_MODULES_PATH = join(TOP_LEVEL_PATH, "node_modules")
@ -51,7 +51,7 @@ exports.init = () => {
* everything required to function is ready. * everything required to function is ready.
*/ */
exports.checkDevelopmentEnvironment = () => { exports.checkDevelopmentEnvironment = () => {
if (!isDev()) { if (!env.isDev() || env.isTest()) {
return return
} }
if (!fs.existsSync(budibaseTempDir())) { if (!fs.existsSync(budibaseTempDir())) {
@ -251,7 +251,8 @@ exports.downloadTemplate = async (type, name) => {
/** /**
* Retrieves component libraries from object store (or tmp symlink if in local) * Retrieves component libraries from object store (or tmp symlink if in local)
*/ */
exports.getComponentLibraryManifest = async (appId, library) => { exports.getComponentLibraryManifest = async library => {
const appId = getAppId()
const filename = "manifest.json" const filename = "manifest.json"
/* istanbul ignore next */ /* istanbul ignore next */
// when testing in cypress and so on we need to get the package // when testing in cypress and so on we need to get the package

View File

@ -3,7 +3,7 @@ const {
getGlobalIDFromUserMetadataID, getGlobalIDFromUserMetadataID,
} = require("../db/utils") } = require("../db/utils")
const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles") const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles")
const { getDeployedAppID } = require("@budibase/backend-core/db") const { getProdAppID } = require("@budibase/backend-core/db")
const { getGlobalUserParams } = require("@budibase/backend-core/db") const { getGlobalUserParams } = require("@budibase/backend-core/db")
const { user: userCache } = require("@budibase/backend-core/cache") const { user: userCache } = require("@budibase/backend-core/cache")
const { const {
@ -11,8 +11,10 @@ const {
isUserInAppTenant, isUserInAppTenant,
} = require("@budibase/backend-core/tenancy") } = require("@budibase/backend-core/tenancy")
const env = require("../environment") const env = require("../environment")
const { getAppId } = require("@budibase/backend-core/context")
exports.updateAppRole = (appId, user) => { exports.updateAppRole = (user, { appId } = {}) => {
appId = appId || getAppId()
if (!user || !user.roles) { if (!user || !user.roles) {
return user return user
} }
@ -24,7 +26,7 @@ exports.updateAppRole = (appId, user) => {
return user return user
} }
// always use the deployed app // always use the deployed app
user.roleId = user.roles[getDeployedAppID(appId)] user.roleId = user.roles[getProdAppID(appId)]
// if a role wasn't found then either set as admin (builder) or public (everyone else) // if a role wasn't found then either set as admin (builder) or public (everyone else)
if (!user.roleId && user.builder && user.builder.global) { if (!user.roleId && user.builder && user.builder.global) {
user.roleId = BUILTIN_ROLE_IDS.ADMIN user.roleId = BUILTIN_ROLE_IDS.ADMIN
@ -35,18 +37,18 @@ exports.updateAppRole = (appId, user) => {
return user return user
} }
function processUser(appId, user) { function processUser(user, { appId } = {}) {
if (user) { if (user) {
delete user.password delete user.password
} }
return exports.updateAppRole(appId, user) return exports.updateAppRole(user, { appId })
} }
exports.getCachedSelf = async (ctx, appId) => { exports.getCachedSelf = async (ctx, appId) => {
// this has to be tenant aware, can't depend on the context to find it out // this has to be tenant aware, can't depend on the context to find it out
// running some middlewares before the tenancy causes context to break // running some middlewares before the tenancy causes context to break
const user = await userCache.getUser(ctx.user._id) const user = await userCache.getUser(ctx.user._id)
return processUser(appId, user) return processUser(user, { appId })
} }
exports.getRawGlobalUser = async userId => { exports.getRawGlobalUser = async userId => {
@ -54,12 +56,13 @@ exports.getRawGlobalUser = async userId => {
return db.get(getGlobalIDFromUserMetadataID(userId)) return db.get(getGlobalIDFromUserMetadataID(userId))
} }
exports.getGlobalUser = async (appId, userId) => { exports.getGlobalUser = async userId => {
let user = await exports.getRawGlobalUser(userId) let user = await exports.getRawGlobalUser(userId)
return processUser(appId, user) return processUser(user)
} }
exports.getGlobalUsers = async (appId = null, users = null) => { exports.getGlobalUsers = async (users = null) => {
const appId = getAppId()
const db = getGlobalDB() const db = getGlobalDB()
let globalUsers let globalUsers
if (users) { if (users) {
@ -86,11 +89,11 @@ exports.getGlobalUsers = async (appId = null, users = null) => {
if (!appId) { if (!appId) {
return globalUsers return globalUsers
} }
return globalUsers.map(user => exports.updateAppRole(appId, user)) return globalUsers.map(user => exports.updateAppRole(user))
} }
exports.getGlobalUsersFromMetadata = async (appId, users) => { exports.getGlobalUsersFromMetadata = async users => {
const globalUsers = await exports.getGlobalUsers(appId, users) const globalUsers = await exports.getGlobalUsers(users)
return users.map(user => { return users.map(user => {
const globalUser = globalUsers.find( const globalUser = globalUsers.find(
globalUser => globalUser && user._id.includes(globalUser._id) globalUser => globalUser && user._id.includes(globalUser._id)

View File

@ -1,9 +1,9 @@
const env = require("../environment") const env = require("../environment")
const { OBJ_STORE_DIRECTORY } = require("../constants") const { OBJ_STORE_DIRECTORY } = require("../constants")
const { sanitizeKey } = require("@budibase/backend-core/objectStore") const { sanitizeKey } = require("@budibase/backend-core/objectStore")
const CouchDB = require("../db")
const { generateMetadataID } = require("../db/utils") const { generateMetadataID } = require("../db/utils")
const Readable = require("stream").Readable const Readable = require("stream").Readable
const { getAppDB } = require("@budibase/backend-core/context")
const BB_CDN = "https://cdn.budi.live" const BB_CDN = "https://cdn.budi.live"
@ -73,8 +73,8 @@ exports.attachmentsRelativeURL = attachmentKey => {
) )
} }
exports.updateEntityMetadata = async (appId, type, entityId, updateFn) => { exports.updateEntityMetadata = async (type, entityId, updateFn) => {
const db = new CouchDB(appId) const db = getAppDB()
const id = generateMetadataID(type, entityId) const id = generateMetadataID(type, entityId)
// read it to see if it exists, we'll overwrite it no matter what // read it to see if it exists, we'll overwrite it no matter what
let rev, let rev,
@ -99,14 +99,14 @@ exports.updateEntityMetadata = async (appId, type, entityId, updateFn) => {
} }
} }
exports.saveEntityMetadata = async (appId, type, entityId, metadata) => { exports.saveEntityMetadata = async (type, entityId, metadata) => {
return exports.updateEntityMetadata(appId, type, entityId, () => { return exports.updateEntityMetadata(type, entityId, () => {
return metadata return metadata
}) })
} }
exports.deleteEntityMetadata = async (appId, type, entityId) => { exports.deleteEntityMetadata = async (type, entityId) => {
const db = new CouchDB(appId) const db = getAppDB()
const id = generateMetadataID(type, entityId) const id = generateMetadataID(type, entityId)
let rev let rev
try { try {
@ -141,16 +141,6 @@ exports.stringToReadStream = string => {
}) })
} }
exports.doesDatabaseExist = async dbName => {
try {
const db = new CouchDB(dbName, { skip_setup: true })
const info = await db.info()
return info && !info.error
} catch (err) {
return false
}
}
exports.formatBytes = bytes => { exports.formatBytes = bytes => {
const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"] const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
const byteIncrements = 1024 const byteIncrements = 1024

View File

@ -1,9 +1,9 @@
const CouchDB = require("../../db")
const { createRoutingView } = require("../../db/views/staticViews") const { createRoutingView } = require("../../db/views/staticViews")
const { ViewNames, getQueryIndex, UNICODE_MAX } = require("../../db/utils") const { ViewNames, getQueryIndex, UNICODE_MAX } = require("../../db/utils")
const { getAppDB } = require("@budibase/backend-core/context")
exports.getRoutingInfo = async appId => { exports.getRoutingInfo = async () => {
const db = new CouchDB(appId) const db = getAppDB()
try { try {
const allRouting = await db.query(getQueryIndex(ViewNames.ROUTING), { const allRouting = await db.query(getQueryIndex(ViewNames.ROUTING), {
startKey: "", startKey: "",
@ -14,8 +14,8 @@ exports.getRoutingInfo = async appId => {
// check if the view doesn't exist, it should for all new instances // check if the view doesn't exist, it should for all new instances
/* istanbul ignore next */ /* istanbul ignore next */
if (err != null && err.name === "not_found") { if (err != null && err.name === "not_found") {
await createRoutingView(appId) await createRoutingView()
return exports.getRoutingInfo(appId) return exports.getRoutingInfo()
} else { } else {
throw err throw err
} }

View File

@ -7,10 +7,10 @@ const { deleteFiles } = require("../../utilities/fileSystem/utilities")
const { ObjectStoreBuckets } = require("../../constants") const { ObjectStoreBuckets } = require("../../constants")
const { const {
isProdAppID, isProdAppID,
getDeployedAppID, getProdAppID,
dbExists, dbExists,
} = require("@budibase/backend-core/db") } = require("@budibase/backend-core/db")
const CouchDB = require("../../db") const { getAppId } = require("@budibase/backend-core/context")
const BASE_AUTO_ID = 1 const BASE_AUTO_ID = 1
@ -253,26 +253,20 @@ exports.inputProcessing = (
/** /**
* This function enriches the input rows with anything they are supposed to contain, for example * This function enriches the input rows with anything they are supposed to contain, for example
* link records or attachment links. * link records or attachment links.
* @param {string} appId the app in which the request is looking for enriched rows.
* @param {object} table the table from which these rows came from originally, this is used to determine * @param {object} table the table from which these rows came from originally, this is used to determine
* the schema of the rows and then enrich. * the schema of the rows and then enrich.
* @param {object[]|object} rows the rows which are to be enriched. * @param {object[]|object} rows the rows which are to be enriched.
* @param {object} opts used to set some options for the output, such as disabling relationship squashing. * @param {object} opts used to set some options for the output, such as disabling relationship squashing.
* @returns {object[]|object} the enriched rows will be returned. * @returns {object[]|object} the enriched rows will be returned.
*/ */
exports.outputProcessing = async ( exports.outputProcessing = async (table, rows, opts = { squash: true }) => {
{ appId },
table,
rows,
opts = { squash: true }
) => {
let wasArray = true let wasArray = true
if (!(rows instanceof Array)) { if (!(rows instanceof Array)) {
rows = [rows] rows = [rows]
wasArray = false wasArray = false
} }
// attach any linked row information // attach any linked row information
let enriched = await linkRows.attachFullLinkedDocs(appId, table, rows) let enriched = await linkRows.attachFullLinkedDocs(table, rows)
// process formulas // process formulas
enriched = processFormulas(table, enriched, { dynamic: true }) enriched = processFormulas(table, enriched, { dynamic: true })
@ -291,18 +285,13 @@ exports.outputProcessing = async (
} }
} }
if (opts.squash) { if (opts.squash) {
enriched = await linkRows.squashLinksToPrimaryDisplay( enriched = await linkRows.squashLinksToPrimaryDisplay(table, enriched)
appId,
table,
enriched
)
} }
return wasArray ? enriched : enriched[0] return wasArray ? enriched : enriched[0]
} }
/** /**
* Clean up any attachments that were attached to a row. * Clean up any attachments that were attached to a row.
* @param {string} appId The ID of the app from which a row is being deleted.
* @param {object} table The table from which a row is being removed. * @param {object} table The table from which a row is being removed.
* @param {any} row optional - the row being removed. * @param {any} row optional - the row being removed.
* @param {any} rows optional - if multiple rows being deleted can do this in bulk. * @param {any} rows optional - if multiple rows being deleted can do this in bulk.
@ -311,15 +300,12 @@ exports.outputProcessing = async (
* deleted attachment columns. * deleted attachment columns.
* @return {Promise<void>} When all attachments have been removed this will return. * @return {Promise<void>} When all attachments have been removed this will return.
*/ */
exports.cleanupAttachments = async ( exports.cleanupAttachments = async (table, { row, rows, oldRow, oldTable }) => {
appId, const appId = getAppId()
table,
{ row, rows, oldRow, oldTable }
) => {
if (!isProdAppID(appId)) { if (!isProdAppID(appId)) {
const prodAppId = getDeployedAppID(appId) const prodAppId = getProdAppID(appId)
// if prod exists, then don't allow deleting // if prod exists, then don't allow deleting
const exists = await dbExists(CouchDB, prodAppId) const exists = await dbExists(prodAppId)
if (exists) { if (exists) {
return return
} }

View File

@ -52,6 +52,7 @@ exports.getUsageQuotaDoc = async db => {
* Given a specified tenantId this will add to the usage object for the specified property. * Given a specified tenantId this will add to the usage object for the specified property.
* @param {string} property The property which is to be added to (within the nested usageQuota object). * @param {string} property The property which is to be added to (within the nested usageQuota object).
* @param {number} usage The amount (this can be negative) to adjust the number by. * @param {number} usage The amount (this can be negative) to adjust the number by.
* @param {object} opts optional - options such as dryRun, to check what update will do.
* @returns {Promise<void>} When this completes the API key will now be up to date - the quota period may have * @returns {Promise<void>} When this completes the API key will now be up to date - the quota period may have
* also been reset after this call. * also been reset after this call.
*/ */

View File

@ -23,6 +23,7 @@ const getAppPairs = appIds => {
} }
const getAppRows = async appId => { const getAppRows = async appId => {
// need to specify the app ID, as this is used for different apps in one call
const appDb = new CouchDB(appId) const appDb = new CouchDB(appId)
const response = await appDb.allDocs( const response = await appDb.allDocs(
getRowParams(null, null, { getRowParams(null, null, {

View File

@ -1,13 +1,13 @@
const CouchDB = require("../db")
const { InternalTables } = require("../db/utils") const { InternalTables } = require("../db/utils")
const { getGlobalUser } = require("../utilities/global") const { getGlobalUser } = require("../utilities/global")
const { getAppDB } = require("@budibase/backend-core/context")
exports.getFullUser = async (ctx, userId) => { exports.getFullUser = async (ctx, userId) => {
const global = await getGlobalUser(ctx.appId, userId) const global = await getGlobalUser(userId)
let metadata let metadata
try { try {
// this will throw an error if the db doesn't exist, or there is no appId // this will throw an error if the db doesn't exist, or there is no appId
const db = new CouchDB(ctx.appId) const db = getAppDB()
metadata = await db.get(userId) metadata = await db.get(userId)
} catch (err) { } catch (err) {
// it is fine if there is no user metadata, just remove global db info // it is fine if there is no user metadata, just remove global db info

View File

@ -1,7 +1,7 @@
const fetch = require("node-fetch") const fetch = require("node-fetch")
const env = require("../environment") const env = require("../environment")
const { checkSlashesInUrl } = require("./index") const { checkSlashesInUrl } = require("./index")
const { getDeployedAppID } = require("@budibase/backend-core/db") const { getProdAppID } = require("@budibase/backend-core/db")
const { updateAppRole } = require("./global") const { updateAppRole } = require("./global")
const { Headers } = require("@budibase/backend-core/constants") const { Headers } = require("@budibase/backend-core/constants")
const { getTenantId, isTenantIdSet } = require("@budibase/backend-core/tenancy") const { getTenantId, isTenantIdSet } = require("@budibase/backend-core/tenancy")
@ -70,15 +70,15 @@ exports.getGlobalSelf = async (ctx, appId = null) => {
} }
let json = await response.json() let json = await response.json()
if (appId) { if (appId) {
json = updateAppRole(appId, json) json = updateAppRole(json)
} }
return json return json
} }
exports.removeAppFromUserRoles = async (ctx, appId) => { exports.removeAppFromUserRoles = async (ctx, appId) => {
const deployedAppId = getDeployedAppID(appId) const prodAppId = getProdAppID(appId)
const response = await fetch( const response = await fetch(
checkSlashesInUrl(env.WORKER_URL + `/api/global/roles/${deployedAppId}`), checkSlashesInUrl(env.WORKER_URL + `/api/global/roles/${prodAppId}`),
request(ctx, { request(ctx, {
method: "DELETE", method: "DELETE",
}) })

View File

@ -11,7 +11,6 @@ const {
upload, upload,
ObjectStoreBuckets, ObjectStoreBuckets,
} = require("@budibase/backend-core/objectStore") } = require("@budibase/backend-core/objectStore")
const CouchDB = require("../../../db")
const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy") const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy")
const env = require("../../../environment") const env = require("../../../environment")
const { googleCallbackUrl, oidcCallbackUrl } = require("./auth") const { googleCallbackUrl, oidcCallbackUrl } = require("./auth")
@ -252,7 +251,7 @@ exports.configChecklist = async function (ctx) {
// TODO: Watch get started video // TODO: Watch get started video
// Apps exist // Apps exist
const apps = await getAllApps(CouchDB, { idsOnly: true }) const apps = await getAllApps({ idsOnly: true })
// They have set up SMTP // They have set up SMTP
const smtpConfig = await getScopedFullConfig(db, { const smtpConfig = await getScopedFullConfig(db, {

View File

@ -1,15 +1,15 @@
const { getAllRoles } = require("@budibase/backend-core/roles") const { getAllRoles } = require("@budibase/backend-core/roles")
const { const {
getAllApps, getAllApps,
getDeployedAppID, getProdAppID,
DocumentTypes, DocumentTypes,
} = require("@budibase/backend-core/db") } = require("@budibase/backend-core/db")
const CouchDB = require("../../../db") const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
exports.fetch = async ctx => { exports.fetch = async ctx => {
const tenantId = ctx.user.tenantId const tenantId = ctx.user.tenantId
// always use the dev apps as they'll be most up to date (true) // always use the dev apps as they'll be most up to date (true)
const apps = await getAllApps(CouchDB, { tenantId, all: true }) const apps = await getAllApps({ tenantId, all: true })
const promises = [] const promises = []
for (let app of apps) { for (let app of apps) {
// use dev app IDs // use dev app IDs
@ -18,7 +18,7 @@ exports.fetch = async ctx => {
const roles = await Promise.all(promises) const roles = await Promise.all(promises)
const response = {} const response = {}
for (let app of apps) { for (let app of apps) {
const deployedAppId = getDeployedAppID(app.appId) const deployedAppId = getProdAppID(app.appId)
response[deployedAppId] = { response[deployedAppId] = {
roles: roles.shift(), roles: roles.shift(),
name: app.name, name: app.name,
@ -31,12 +31,14 @@ exports.fetch = async ctx => {
exports.find = async ctx => { exports.find = async ctx => {
const appId = ctx.params.appId const appId = ctx.params.appId
const db = new CouchDB(appId) await doInAppContext(appId, async () => {
const app = await db.get(DocumentTypes.APP_METADATA) const db = getAppDB()
ctx.body = { const app = await db.get(DocumentTypes.APP_METADATA)
roles: await getAllRoles(appId), ctx.body = {
name: app.name, roles: await getAllRoles(),
version: app.version, name: app.name,
url: app.url, version: app.version,
} url: app.url,
}
})
} }

File diff suppressed because it is too large Load Diff