From d2fe119d902daf5079f8d90f8e368325ac432d08 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 27 Jan 2022 18:18:31 +0000 Subject: [PATCH] Main body of work, refactoring most usages. --- packages/backend-core/context.js | 15 +++ packages/backend-core/src/db/utils.js | 16 +-- .../backend-core/src/middleware/appTenancy.js | 2 + packages/backend-core/src/security/roles.js | 38 +++--- .../src/tenancy/FunctionContext.js | 20 ++-- packages/backend-core/src/tenancy/context.js | 113 ++++++++++++++++-- .../server/src/api/controllers/application.js | 55 +++++---- packages/server/src/api/controllers/cloud.js | 5 +- .../server/src/api/controllers/component.js | 7 +- .../server/src/api/controllers/datasource.js | 20 ++-- packages/server/src/api/controllers/layout.js | 6 +- .../server/src/api/controllers/permission.js | 8 +- packages/server/src/api/controllers/role.js | 10 +- .../server/src/api/controllers/routing.js | 2 +- .../api/controllers/row/ExternalRequest.ts | 82 +++++++------ .../src/api/controllers/row/external.js | 6 +- .../src/api/controllers/row/internal.js | 51 +++----- .../src/api/controllers/row/internalSearch.js | 24 ++-- .../server/src/api/controllers/row/utils.js | 13 +- packages/server/src/api/controllers/screen.js | 12 +- .../src/api/controllers/static/index.js | 8 +- .../src/api/controllers/table/external.js | 17 ++- .../server/src/api/controllers/table/index.js | 8 +- .../src/api/controllers/table/internal.js | 8 +- .../server/src/api/controllers/table/utils.js | 59 ++++----- packages/server/src/api/controllers/user.js | 31 +++-- .../server/src/api/controllers/view/index.js | 18 ++- .../server/src/api/controllers/view/utils.js | 13 +- .../server/src/api/routes/tests/misc.spec.js | 1 - .../routes/tests/utilities/TestFunctions.js | 6 +- .../server/src/automations/automationUtils.js | 5 +- .../server/src/automations/steps/createRow.js | 1 - .../server/src/automations/steps/updateRow.js | 2 +- .../src/db/linkedRows/LinkController.js | 10 +- packages/server/src/db/linkedRows/index.js | 41 ++----- .../server/src/db/linkedRows/linkUtils.js | 12 +- .../src/db/tests/linkController.spec.js | 1 - .../server/src/db/tests/linkTests.spec.js | 11 +- packages/server/src/db/views/staticViews.js | 18 ++- packages/server/src/integrations/utils.ts | 5 +- packages/server/src/middleware/authorized.js | 4 +- packages/server/src/middleware/currentapp.js | 3 +- .../src/migrations/usageQuotas/syncApps.js | 3 +- .../src/migrations/usageQuotas/syncRows.js | 3 +- .../src/tests/utilities/TestConfiguration.js | 3 + .../server/src/utilities/fileSystem/index.js | 4 +- packages/server/src/utilities/global.js | 23 ++-- .../src/utilities/rowProcessor/index.js | 17 +-- packages/server/src/utilities/users.js | 6 +- .../server/src/utilities/workerRequests.js | 2 +- .../src/api/controllers/global/configs.js | 3 +- .../src/api/controllers/global/roles.js | 2 +- 52 files changed, 453 insertions(+), 400 deletions(-) create mode 100644 packages/backend-core/context.js diff --git a/packages/backend-core/context.js b/packages/backend-core/context.js new file mode 100644 index 0000000000..b3d004b209 --- /dev/null +++ b/packages/backend-core/context.js @@ -0,0 +1,15 @@ +const { + getAppDB, + getDevAppDB, + getProdAppDB, + getAppId, + updateAppId, +} = require("./src/tenancy/context") + +module.exports = { + getAppDB, + getDevAppDB, + getProdAppDB, + getAppId, + updateAppId, +} diff --git a/packages/backend-core/src/db/utils.js b/packages/backend-core/src/db/utils.js index 2bc5462646..181467b402 100644 --- a/packages/backend-core/src/db/utils.js +++ b/packages/backend-core/src/db/utils.js @@ -250,11 +250,10 @@ exports.getAllDbs = async () => { /** * 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). - * 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} 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() if (!env.MULTI_TENANCY && !tenantId) { tenantId = DEFAULT_TENANT_ID @@ -310,8 +309,8 @@ exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => { /** * Utility function for getAllApps but filters to production apps only. */ -exports.getDeployedAppIDs = async CouchDB => { - return (await exports.getAllApps(CouchDB, { idsOnly: true })).filter( +exports.getDeployedAppIDs = async () => { + return (await exports.getAllApps({ idsOnly: true })).filter( id => !exports.isDevAppID(id) ) } @@ -319,13 +318,14 @@ exports.getDeployedAppIDs = async CouchDB => { /** * Utility function for the inverse of above. */ -exports.getDevAppIDs = async CouchDB => { - return (await exports.getAllApps(CouchDB, { idsOnly: true })).filter(id => +exports.getDevAppIDs = async () => { + return (await exports.getAllApps({ idsOnly: true })).filter(id => exports.isDevAppID(id) ) } -exports.dbExists = async (CouchDB, dbName) => { +exports.dbExists = async dbName => { + const CouchDB = getCouch() let exists = false try { const db = CouchDB(dbName, { skip_setup: true }) diff --git a/packages/backend-core/src/middleware/appTenancy.js b/packages/backend-core/src/middleware/appTenancy.js index 30fc4f7453..60d7448af2 100644 --- a/packages/backend-core/src/middleware/appTenancy.js +++ b/packages/backend-core/src/middleware/appTenancy.js @@ -3,6 +3,7 @@ const { updateTenantId, isTenantIdSet, DEFAULT_TENANT_ID, + updateAppId, } = require("../tenancy") const ContextFactory = require("../tenancy/FunctionContext") const { getTenantIDFromAppID } = require("../db/utils") @@ -21,5 +22,6 @@ module.exports = () => { const appId = ctx.appId ? ctx.appId : ctx.user ? ctx.user.appId : null const tenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID updateTenantId(tenantId) + updateAppId(appId) }) } diff --git a/packages/backend-core/src/security/roles.js b/packages/backend-core/src/security/roles.js index 8529dde6f4..2be5058cbb 100644 --- a/packages/backend-core/src/security/roles.js +++ b/packages/backend-core/src/security/roles.js @@ -1,4 +1,3 @@ -const { getDB } = require("../db") const { cloneDeep } = require("lodash/fp") const { BUILTIN_PERMISSION_IDS } = require("./permissions") const { @@ -7,6 +6,7 @@ const { DocumentTypes, SEPARATOR, } = require("../db/utils") +const { getAppDB } = require("../tenancy/context") const BUILTIN_IDS = { ADMIN: "ADMIN", @@ -111,11 +111,10 @@ exports.lowerBuiltinRoleID = (roleId1, roleId2) => { /** * 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. - * @param {string} appId The app in which to look for the role. * @param {string|null} roleId The level ID to lookup. * @returns {Promise} The role object, which may contain an "inherits" property. */ -exports.getRole = async (appId, roleId) => { +exports.getRole = async roleId => { if (!roleId) { return null } @@ -128,7 +127,7 @@ exports.getRole = async (appId, roleId) => { ) } try { - const db = getDB(appId) + const db = getAppDB() const dbRole = await db.get(exports.getDBRoleID(roleId)) role = Object.assign(role, dbRole) // finalise the ID @@ -145,11 +144,11 @@ exports.getRole = async (appId, roleId) => { /** * 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) { return [BUILTIN_IDS.BASIC] } - let currentRole = await exports.getRole(appId, userRoleId) + let currentRole = await exports.getRole(userRoleId) let roles = currentRole ? [currentRole] : [] let roleIds = [userRoleId] // get all the inherited roles @@ -159,7 +158,7 @@ async function getAllUserRoles(appId, userRoleId) { roleIds.indexOf(currentRole.inherits) === -1 ) { roleIds.push(currentRole.inherits) - currentRole = await exports.getRole(appId, currentRole.inherits) + currentRole = await exports.getRole(currentRole.inherits) roles.push(currentRole) } return roles @@ -168,29 +167,23 @@ async function getAllUserRoles(appId, userRoleId) { /** * 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. - * @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 {object} opts Various options, such as whether to only retrieve the IDs (default true). * @returns {Promise} returns an ordered array of the roles, with the first being their * highest level of access and the last being the lowest level. */ -exports.getUserRoleHierarchy = async ( - appId, - userRoleId, - opts = { idOnly: true } -) => { +exports.getUserRoleHierarchy = async (userRoleId, opts = { idOnly: true }) => { // 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 } /** * 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} An array of the role objects that were found. */ -exports.getAllRoles = async appId => { - const db = getDB(appId) +exports.getAllRoles = async () => { + const db = getAppDB() const body = await db.allDocs( getRoleParams(null, { include_docs: true, @@ -218,19 +211,17 @@ exports.getAllRoles = async appId => { } /** - * This retrieves the required role/ - * @param appId + * This retrieves the required role * @param permLevel * @param resourceId * @param subResourceId * @return {Promise<{permissions}|Object>} */ exports.getRequiredResourceRole = async ( - appId, permLevel, { resourceId, subResourceId } ) => { - const roles = await exports.getAllRoles(appId) + const roles = await exports.getAllRoles() let main = [], sub = [] for (let role of roles) { @@ -251,8 +242,7 @@ exports.getRequiredResourceRole = async ( } class AccessController { - constructor(appId) { - this.appId = appId + constructor() { this.userHierarchies = {} } @@ -270,7 +260,7 @@ class AccessController { } let roleIds = this.userHierarchies[userRoleId] if (!roleIds) { - roleIds = await exports.getUserRoleHierarchy(this.appId, userRoleId) + roleIds = await exports.getUserRoleHierarchy(userRoleId) this.userHierarchies[userRoleId] = roleIds } diff --git a/packages/backend-core/src/tenancy/FunctionContext.js b/packages/backend-core/src/tenancy/FunctionContext.js index d97a3a30b4..1a3f65056e 100644 --- a/packages/backend-core/src/tenancy/FunctionContext.js +++ b/packages/backend-core/src/tenancy/FunctionContext.js @@ -4,8 +4,8 @@ const { newid } = require("../hashing") const REQUEST_ID_KEY = "requestId" class FunctionContext { - static getMiddleware(updateCtxFn = null) { - const namespace = this.createNamespace() + static getMiddleware(updateCtxFn = null, contextName = "session") { + const namespace = this.createNamespace(contextName) return async function (ctx, next) { await new Promise( @@ -24,14 +24,14 @@ class FunctionContext { } } - static run(callback) { - const namespace = this.createNamespace() + static run(callback, contextName = "session") { + const namespace = this.createNamespace(contextName) return namespace.runAndReturn(callback) } - static setOnContext(key, value) { - const namespace = this.createNamespace() + static setOnContext(key, value, contextName = "session") { + const namespace = this.createNamespace(contextName) namespace.set(key, value) } @@ -55,16 +55,16 @@ class FunctionContext { } } - static destroyNamespace() { + static destroyNamespace(name = "session") { if (this._namespace) { - cls.destroyNamespace("session") + cls.destroyNamespace(name) this._namespace = null } } - static createNamespace() { + static createNamespace(name = "session") { if (!this._namespace) { - this._namespace = cls.createNamespace("session") + this._namespace = cls.createNamespace(name) } return this._namespace } diff --git a/packages/backend-core/src/tenancy/context.js b/packages/backend-core/src/tenancy/context.js index 01d1fdc604..ac2cfbeae9 100644 --- a/packages/backend-core/src/tenancy/context.js +++ b/packages/backend-core/src/tenancy/context.js @@ -1,6 +1,25 @@ const env = require("../environment") const { Headers } = require("../../constants") const cls = require("./FunctionContext") +const { getCouch } = require("../db") +const { getDeployedAppID, getDevelopmentAppID } = require("../db/utils") +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" @@ -12,13 +31,11 @@ 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) + cls.setOnContext(ContextKeys.TENANT_ID, tenantId) // invoke the task return task() @@ -26,7 +43,19 @@ exports.doInTenant = (tenantId, task) => { } exports.updateTenantId = tenantId => { - cls.setOnContext(TENANT_ID, tenantId) + cls.setOnContext(ContextKeys.TENANT_ID, tenantId) +} + +exports.updateAppId = appId => { + try { + cls.setOnContext(ContextKeys.APP_ID, appId) + } catch (err) { + if (env.isTest()) { + TEST_APP_ID = appId + } else { + throw err + } + } } exports.setTenantId = ( @@ -36,7 +65,7 @@ exports.setTenantId = ( let tenantId // exit early if not multi-tenant if (!exports.isMultiTenant()) { - cls.setOnContext(TENANT_ID, this.DEFAULT_TENANT_ID) + cls.setOnContext(ContextKeys.TENANT_ID, this.DEFAULT_TENANT_ID) return } @@ -63,12 +92,12 @@ exports.setTenantId = ( } // check tenant ID just incase no tenant was allowed if (tenantId) { - cls.setOnContext(TENANT_ID, tenantId) + cls.setOnContext(ContextKeys.TENANT_ID, tenantId) } } exports.isTenantIdSet = () => { - const tenantId = cls.getFromContext(TENANT_ID) + const tenantId = cls.getFromContext(ContextKeys.TENANT_ID) return !!tenantId } @@ -76,9 +105,77 @@ exports.getTenantId = () => { if (!exports.isMultiTenant()) { return exports.DEFAULT_TENANT_ID } - const tenantId = cls.getFromContext(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 = getDeployedAppID(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) +} diff --git a/packages/server/src/api/controllers/application.js b/packages/server/src/api/controllers/application.js index 7aaeebc025..9197fa30a1 100644 --- a/packages/server/src/api/controllers/application.js +++ b/packages/server/src/api/controllers/application.js @@ -1,4 +1,3 @@ -const CouchDB = require("../../db") const env = require("../../environment") const packageJson = require("../../../package.json") const { @@ -45,11 +44,13 @@ const { getTenantId, isMultiTenant } = require("@budibase/backend-core/tenancy") const { syncGlobalUsers } = require("./user") const { app: appCache } = require("@budibase/backend-core/cache") const { cleanupAutomations } = require("../../automations/utils") +const context = require("@budibase/backend-core/context") const URL_REGEX_SLASH = /\/|\\/g // utility function, need to do away with this -async function getLayouts(db) { +async function getLayouts() { + const db = context.getAppDB() return ( await db.allDocs( getLayoutParams(null, { @@ -59,7 +60,8 @@ async function getLayouts(db) { ).rows.map(row => row.doc) } -async function getScreens(db) { +async function getScreens() { + const db = context.getAppDB() return ( await db.allDocs( getScreenParams(null, { @@ -116,8 +118,9 @@ async function createInstance(template) { const tenantId = isMultiTenant() ? getTenantId() : null const baseAppId = generateAppID(tenantId) const appId = generateDevAppID(baseAppId) + context.updateAppId(appId) - const db = new CouchDB(appId) + const db = context.getAppDB() await db.put({ _id: "_design/database", // view collation information, read before writing any complex views: @@ -127,9 +130,9 @@ async function createInstance(template) { // NOTE: indexes need to be created before any tables/templates // add view for linked rows - await createLinkView(appId) - await createRoutingView(appId) - await createAllSearchIndex(appId) + await createLinkView() + await createRoutingView() + await createAllSearchIndex() // replicate the template data to the instance DB // this is currently very hard to test, downloading and importing template files @@ -155,7 +158,7 @@ async function createInstance(template) { exports.fetch = async ctx => { const dev = ctx.query && ctx.query.status === AppStatus.DEV 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 if (dev || all) { @@ -178,12 +181,11 @@ exports.fetch = async ctx => { } exports.fetchAppDefinition = async ctx => { - const db = new CouchDB(ctx.params.appId) - const layouts = await getLayouts(db) + const layouts = await getLayouts() const userRoleId = getUserRoleId(ctx) - const accessController = new AccessController(ctx.params.appId) + const accessController = new AccessController() const screens = await accessController.checkScreensAccess( - await getScreens(db), + await getScreens(), userRoleId ) ctx.body = { @@ -194,15 +196,15 @@ exports.fetchAppDefinition = async ctx => { } exports.fetchAppPackage = async ctx => { - const db = new CouchDB(ctx.params.appId) + const db = context.getAppDB() const application = await db.get(DocumentTypes.APP_METADATA) - const layouts = await getLayouts(db) - let screens = await getScreens(db) + const layouts = await getLayouts() + let screens = await getScreens() // Only filter screens if the user is not a builder if (!(ctx.user.builder && ctx.user.builder.global)) { const userRoleId = getUserRoleId(ctx) - const accessController = new AccessController(ctx.params.appId) + const accessController = new AccessController() screens = await accessController.checkScreensAccess(screens, userRoleId) } @@ -215,7 +217,7 @@ exports.fetchAppPackage = 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 checkAppName(ctx, apps, name) const url = await getAppUrl(ctx) @@ -233,7 +235,7 @@ exports.create = async ctx => { const instance = await createInstance(instanceConfig) const appId = instance._id - const db = new CouchDB(appId) + const db = context.getAppDB() let _rev try { // if template there will be an existing doc @@ -277,7 +279,7 @@ exports.create = async ctx => { } exports.update = async ctx => { - const apps = await getAllApps(CouchDB, { dev: true }) + const apps = await getAllApps({ dev: true }) // validation const name = ctx.request.body.name checkAppName(ctx, apps, name, ctx.params.appId) @@ -292,7 +294,7 @@ exports.update = async ctx => { exports.updateClient = async ctx => { // Get current app version - const db = new CouchDB(ctx.params.appId) + const db = context.getAppDB() const application = await db.get(DocumentTypes.APP_METADATA) const currentVersion = application.version @@ -314,7 +316,7 @@ exports.updateClient = async ctx => { exports.revertClient = async ctx => { // Check app can be reverted - const db = new CouchDB(ctx.params.appId) + const db = context.getAppDB() const application = await db.get(DocumentTypes.APP_METADATA) if (!application.revertableVersion) { ctx.throw(400, "There is no version to revert to") @@ -336,7 +338,7 @@ exports.revertClient = async ctx => { } exports.delete = async ctx => { - const db = new CouchDB(ctx.params.appId) + const db = context.getAppDB() const result = await db.destroy() /* istanbul ignore next */ @@ -364,7 +366,8 @@ exports.sync = async (ctx, next) => { const prodAppId = getDeployedAppID(appId) try { - const prodDb = new CouchDB(prodAppId, { skip_setup: true }) + // specific case, want to make sure setup is skipped + const prodDb = context.getProdAppDB({ skip_setup: true }) const info = await prodDb.info() if (info.error) throw info.error } catch (err) { @@ -392,7 +395,7 @@ exports.sync = async (ctx, next) => { } // sync the users - await syncGlobalUsers(appId) + await syncGlobalUsers() if (error) { ctx.throw(400, error) @@ -404,7 +407,7 @@ exports.sync = async (ctx, next) => { } const updateAppPackage = async (appPackage, appId) => { - const db = new CouchDB(appId) + const db = context.getAppDB() const application = await db.get(DocumentTypes.APP_METADATA) const newAppPackage = { ...application, ...appPackage } @@ -423,7 +426,7 @@ const updateAppPackage = async (appPackage, appId) => { } const createEmptyAppPackage = async (ctx, app) => { - const db = new CouchDB(app.appId) + const db = context.getAppDB() let screensAndLayouts = [] for (let layout of BASE_LAYOUTS) { diff --git a/packages/server/src/api/controllers/cloud.js b/packages/server/src/api/controllers/cloud.js index ea6cc9b71e..38804f4d4a 100644 --- a/packages/server/src/api/controllers/cloud.js +++ b/packages/server/src/api/controllers/cloud.js @@ -1,6 +1,5 @@ const env = require("../../environment") const { getAllApps } = require("@budibase/backend-core/db") -const CouchDB = require("../../db") const { exportDB, sendTempFile, @@ -30,7 +29,7 @@ exports.exportApps = async ctx => { if (env.SELF_HOSTED || !env.MULTI_TENANCY) { 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(), { filter: doc => !doc._id.startsWith(DocumentTypes.USER), }) @@ -63,7 +62,7 @@ async function hasBeenImported() { if (!env.SELF_HOSTED || env.MULTI_TENANCY) { return true } - const apps = await getAllApps(CouchDB, { all: true }) + const apps = await getAllApps({ all: true }) return apps.length !== 0 } diff --git a/packages/server/src/api/controllers/component.js b/packages/server/src/api/controllers/component.js index 06cb2cd211..2d0aaea23a 100644 --- a/packages/server/src/api/controllers/component.js +++ b/packages/server/src/api/controllers/component.js @@ -1,15 +1,14 @@ -const CouchDB = require("../../db") const { DocumentTypes } = require("../../db/utils") const { getComponentLibraryManifest } = require("../../utilities/fileSystem") +const { getAppDB } = require("@budibase/backend-core/context") exports.fetchAppComponentDefinitions = async function (ctx) { - const appId = ctx.params.appId || ctx.appId - const db = new CouchDB(appId) + const db = getAppDB() const app = await db.get(DocumentTypes.APP_METADATA) let componentManifests = await Promise.all( app.componentLibraries.map(async library => { - let manifest = await getComponentLibraryManifest(appId, library) + let manifest = await getComponentLibraryManifest(library) return { manifest, diff --git a/packages/server/src/api/controllers/datasource.js b/packages/server/src/api/controllers/datasource.js index 5ab3c0a865..999f322563 100644 --- a/packages/server/src/api/controllers/datasource.js +++ b/packages/server/src/api/controllers/datasource.js @@ -1,4 +1,3 @@ -const CouchDB = require("../../db") const { generateDatasourceID, getDatasourceParams, @@ -11,12 +10,11 @@ const { BuildSchemaErrors, InvalidColumns } = require("../../constants") const { integrations } = require("../../integrations") const { getDatasourceAndQuery } = require("./row/utils") const { invalidateDynamicVariables } = require("../../threads/utils") +const { getAppDB } = require("@budibase/backend-core/context") exports.fetch = async function (ctx) { - const database = new CouchDB(ctx.appId) - // Get internal tables - const db = new CouchDB(ctx.appId) + const db = getAppDB() const internalTables = await db.allDocs( getTableParams(null, { include_docs: true, @@ -31,7 +29,7 @@ exports.fetch = async function (ctx) { // Get external datasources const datasources = ( - await database.allDocs( + await db.allDocs( getDatasourceParams(null, { include_docs: true, }) @@ -49,7 +47,7 @@ exports.fetch = 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 { tables, error } = await buildSchemaHelper(datasource) @@ -98,7 +96,7 @@ const invalidateVariables = async (existingDatasource, updatedDatasource) => { } exports.update = async function (ctx) { - const db = new CouchDB(ctx.appId) + const db = getAppDB() const datasourceId = ctx.params.datasourceId let datasource = await db.get(datasourceId) const auth = datasource.config.auth @@ -126,7 +124,7 @@ exports.update = 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 fetchSchema = ctx.request.body.fetchSchema @@ -162,7 +160,7 @@ exports.save = async function (ctx) { } exports.destroy = async function (ctx) { - const db = new CouchDB(ctx.appId) + const db = getAppDB() // Delete all queries for the datasource const queries = await db.allDocs( @@ -184,7 +182,7 @@ exports.destroy = 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) } @@ -192,7 +190,7 @@ exports.find = async function (ctx) { exports.query = async function (ctx) { const queryJson = ctx.request.body try { - ctx.body = await getDatasourceAndQuery(ctx.appId, queryJson) + ctx.body = await getDatasourceAndQuery(queryJson) } catch (err) { ctx.throw(400, err) } diff --git a/packages/server/src/api/controllers/layout.js b/packages/server/src/api/controllers/layout.js index c3cae1b6a7..a92eec424a 100644 --- a/packages/server/src/api/controllers/layout.js +++ b/packages/server/src/api/controllers/layout.js @@ -2,11 +2,11 @@ const { EMPTY_LAYOUT, BASE_LAYOUT_PROP_IDS, } = require("../../constants/layouts") -const CouchDB = require("../../db") const { generateLayoutID, getScreenParams } = require("../../db/utils") +const { getAppDB } = require("@budibase/backend-core/context") exports.save = async function (ctx) { - const db = new CouchDB(ctx.appId) + const db = getAppDB() let layout = ctx.request.body if (!layout.props) { @@ -26,7 +26,7 @@ exports.save = async function (ctx) { } exports.destroy = async function (ctx) { - const db = new CouchDB(ctx.appId) + const db = getAppDB() const layoutId = ctx.params.layoutId, layoutRev = ctx.params.layoutRev diff --git a/packages/server/src/api/controllers/permission.js b/packages/server/src/api/controllers/permission.js index 5c42fe77ef..0e37a3e7d3 100644 --- a/packages/server/src/api/controllers/permission.js +++ b/packages/server/src/api/controllers/permission.js @@ -6,12 +6,12 @@ const { getBuiltinRoles, } = require("@budibase/backend-core/roles") const { getRoleParams } = require("../../db/utils") -const CouchDB = require("../../db") const { CURRENTLY_SUPPORTED_LEVELS, getBasePermissions, } = require("../../utilities/security") const { removeFromArray } = require("../../utilities") +const { getAppDB } = require("@budibase/backend-core/context") const PermissionUpdateType = { REMOVE: "remove", @@ -35,7 +35,7 @@ async function updatePermissionOnRole( { roleId, resourceId, level }, updateType ) { - const db = new CouchDB(appId) + const db = getAppDB() const remove = updateType === PermissionUpdateType.REMOVE const isABuiltin = isBuiltin(roleId) const dbRoleId = getDBRoleID(roleId) @@ -106,7 +106,7 @@ exports.fetchLevels = function (ctx) { } exports.fetch = async function (ctx) { - const db = new CouchDB(ctx.appId) + const db = getAppDB() const roles = await getAllDBRoles(db) let permissions = {} // create an object with structure role ID -> resource ID -> level @@ -133,7 +133,7 @@ exports.fetch = async function (ctx) { exports.getResourcePerms = async function (ctx) { const resourceId = ctx.params.resourceId - const db = new CouchDB(ctx.appId) + const db = getAppDB() const body = await db.allDocs( getRoleParams(null, { include_docs: true, diff --git a/packages/server/src/api/controllers/role.js b/packages/server/src/api/controllers/role.js index b79907031d..11b4b9a520 100644 --- a/packages/server/src/api/controllers/role.js +++ b/packages/server/src/api/controllers/role.js @@ -1,4 +1,3 @@ -const CouchDB = require("../../db") const { Role, getRole, @@ -10,6 +9,7 @@ const { getUserMetadataParams, InternalTables, } = require("../../db/utils") +const { getAppDB } = require("@budibase/backend-core/context") const UpdateRolesOptions = { CREATED: "created", @@ -40,15 +40,15 @@ async function updateRolesOnUserTable(db, roleId, updateOption) { } exports.fetch = async function (ctx) { - ctx.body = await getAllRoles(ctx.appId) + ctx.body = await getAllRoles() } 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) { - const db = new CouchDB(ctx.appId) + const db = getAppDB() let { _id, name, inherits, permissionId } = ctx.request.body if (!_id) { _id = generateRoleID() @@ -69,7 +69,7 @@ exports.save = async function (ctx) { } exports.destroy = async function (ctx) { - const db = new CouchDB(ctx.appId) + const db = getAppDB() const roleId = ctx.params.roleId if (isBuiltin(roleId)) { ctx.throw(400, "Cannot delete builtin role.") diff --git a/packages/server/src/api/controllers/routing.js b/packages/server/src/api/controllers/routing.js index d45d33ed07..aeb728454b 100644 --- a/packages/server/src/api/controllers/routing.js +++ b/packages/server/src/api/controllers/routing.js @@ -63,7 +63,7 @@ exports.fetch = async ctx => { exports.clientFetch = async ctx => { const routing = await getRoutingStructure(ctx.appId) 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 subpathKey of Object.keys(topLevel.subpaths)) { let found = false diff --git a/packages/server/src/api/controllers/row/ExternalRequest.ts b/packages/server/src/api/controllers/row/ExternalRequest.ts index af199561dc..317511f508 100644 --- a/packages/server/src/api/controllers/row/ExternalRequest.ts +++ b/packages/server/src/api/controllers/row/ExternalRequest.ts @@ -19,6 +19,19 @@ import { isRowId, convertRowId, } 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 { tableId?: string @@ -38,18 +51,6 @@ interface RunConfig { } 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( id: string | undefined, filters: SearchFilters, @@ -210,19 +211,16 @@ module External { } class ExternalRequest { - private readonly appId: string private operation: Operation private tableId: string private datasource: Datasource private tables: { [key: string]: Table } = {} constructor( - appId: string, operation: Operation, tableId: string, datasource: Datasource ) { - this.appId = appId this.operation = operation this.tableId = tableId this.datasource = datasource @@ -231,12 +229,14 @@ module External { } } - getTable(tableId: string | undefined): Table { + getTable(tableId: string | undefined): Table | undefined { if (!tableId) { throw "Table ID is unknown, cannot find table" } const { tableName } = breakExternalTableId(tableId) - return this.tables[tableName] + if (tableName) { + return this.tables[tableName] + } } inputProcessing(row: Row | undefined, table: Table) { @@ -272,9 +272,9 @@ module External { newRow[key] = row[key] continue } - const { tableName: linkTableName } = breakExternalTableId(field.tableId) + const { tableName: linkTableName } = breakExternalTableId(field?.tableId) // table has to exist for many to many - if (!this.tables[linkTableName]) { + if (!linkTableName || !this.tables[linkTableName]) { continue } const linkTable = this.tables[linkTableName] @@ -422,7 +422,7 @@ module External { } const { tableName: linkTableName } = breakExternalTableId(field.tableId) // no table to link to, this is not a valid relationships - if (!this.tables[linkTableName]) { + if (!linkTableName || !this.tables[linkTableName]) { continue } const linkTable = this.tables[linkTableName] @@ -460,6 +460,9 @@ module External { async lookupRelations(tableId: string, row: Row) { const related: { [key: string]: any } = {} const { tableName } = breakExternalTableId(tableId) + if (!tableName) { + return related + } const table = this.tables[tableName] // @ts-ignore const primaryKey = table.primary[0] @@ -484,7 +487,7 @@ module External { if (!lookupField || !row[lookupField]) { continue } - const response = await getDatasourceAndQuery(this.appId, { + const response = await getDatasourceAndQuery({ endpoint: getEndpoint(tableId, DataSourceOperation.READ), filters: { equal: { @@ -515,28 +518,30 @@ module External { row: Row, relationships: ManyRelationship[] ) { - const { appId } = this // if we're creating (in a through table) need to wipe the existing ones first const promises = [] const related = await this.lookupRelations(mainTableId, row) for (let relationship of relationships) { 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) // @ts-ignore - const linkPrimary = linkTable.primary[0] + const linkPrimary = linkTable?.primary[0] + if (!linkTable || !linkPrimary) { + return + } const rows = related[key].rows || [] const found = rows.find( (row: { [key: string]: any }) => row[linkPrimary] === relationship.id || - row[linkPrimary] === body[linkPrimary] + row[linkPrimary] === body?.[linkPrimary] ) const operation = isUpdate ? DataSourceOperation.UPDATE : DataSourceOperation.CREATE if (!found) { promises.push( - getDatasourceAndQuery(appId, { + getDatasourceAndQuery({ endpoint: getEndpoint(tableId, operation), // if we're doing many relationships then we're writing, only one response body, @@ -552,9 +557,9 @@ module External { for (let [colName, { isMany, rows, tableId }] of Object.entries( 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 (table.primary && table.primary.indexOf(colName) !== -1) { + if (!table || (table.primary && table.primary.indexOf(colName) !== -1)) { continue } for (let row of rows) { @@ -566,7 +571,7 @@ module External { : DataSourceOperation.UPDATE const body = isMany ? null : { [colName]: null } promises.push( - getDatasourceAndQuery(this.appId, { + getDatasourceAndQuery({ endpoint: getEndpoint(tableId, op), body, filters, @@ -605,20 +610,25 @@ module External { continue } const { tableName: linkTableName } = breakExternalTableId(field.tableId) - const linkTable = this.tables[linkTableName] - if (linkTable) { - const linkedFields = extractRealFields(linkTable, fields) - fields = fields.concat(linkedFields) + if (linkTableName) { + const linkTable = this.tables[linkTableName] + if (linkTable) { + const linkedFields = extractRealFields(linkTable, fields) + fields = fields.concat(linkedFields) + } } } return fields } async run(config: RunConfig) { - const { appId, operation, tableId } = this + const { operation, tableId } = this let { datasourceId, tableName } = breakExternalTableId(tableId) + if (!tableName) { + throw "Unable to run without a table name" + } if (!this.datasource) { - const db = new CouchDB(appId) + const db = getAppDB() this.datasource = await db.get(datasourceId) if (!this.datasource || !this.datasource.entities) { throw "No tables found, fetch tables before query." @@ -670,7 +680,7 @@ module External { }, } // 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) if ( operation !== DataSourceOperation.READ && diff --git a/packages/server/src/api/controllers/row/external.js b/packages/server/src/api/controllers/row/external.js index b8620f7bc3..4e79975893 100644 --- a/packages/server/src/api/controllers/row/external.js +++ b/packages/server/src/api/controllers/row/external.js @@ -11,7 +11,7 @@ const { const ExternalRequest = require("./ExternalRequest") const CouchDB = require("../../../db") -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 if (opts && opts.filters) { 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( - opts - ) + return new ExternalRequest(operation, tableId, opts.datasource).run(opts) } exports.handleRequest = handleRequest diff --git a/packages/server/src/api/controllers/row/internal.js b/packages/server/src/api/controllers/row/internal.js index 75caaf2fda..8449530ce3 100644 --- a/packages/server/src/api/controllers/row/internal.js +++ b/packages/server/src/api/controllers/row/internal.js @@ -1,4 +1,3 @@ -const CouchDB = require("../../../db") const linkRows = require("../../../db/linkedRows") const { getRowParams, @@ -27,6 +26,7 @@ const { getFromMemoryDoc, } = require("../view/utils") const { cloneDeep } = require("lodash/fp") +const { getAppDB } = require("@budibase/backend-core/context") const CALCULATION_TYPES = { SUM: "sum", @@ -106,8 +106,7 @@ async function getView(db, viewName) { } exports.patch = async ctx => { - const appId = ctx.appId - const db = new CouchDB(appId) + const db = getAppDB() const inputs = ctx.request.body const tableId = inputs.tableId const isUserTable = tableId === InternalTables.USER_METADATA @@ -146,14 +145,13 @@ exports.patch = async ctx => { // returned row is cleaned and prepared for writing to DB row = await linkRows.updateLinks({ - appId, eventType: linkRows.EventType.ROW_UPDATE, row, tableId: row.tableId, table, }) // check if any attachments removed - await cleanupAttachments(appId, table, { oldRow, row }) + await cleanupAttachments(table, { oldRow, row }) if (isUserTable) { // the row has been updated, need to put it into the ctx @@ -166,8 +164,7 @@ exports.patch = async ctx => { } exports.save = async function (ctx) { - const appId = ctx.appId - const db = new CouchDB(appId) + const db = getAppDB() let inputs = ctx.request.body inputs.tableId = ctx.params.tableId @@ -189,7 +186,6 @@ exports.save = async function (ctx) { // make sure link rows are up to date row = await linkRows.updateLinks({ - appId, eventType: linkRows.EventType.ROW_SAVE, row, tableId: row.tableId, @@ -200,7 +196,6 @@ exports.save = async function (ctx) { } exports.fetchView = async ctx => { - const appId = ctx.appId const viewName = ctx.params.viewName // if this is a table view being looked for just transfer to that @@ -209,7 +204,7 @@ exports.fetchView = async ctx => { return exports.fetch(ctx) } - const db = new CouchDB(appId) + const db = getAppDB() const { calculation, group, field } = ctx.query const viewInfo = await getView(db, viewName) let response @@ -263,8 +258,7 @@ exports.fetchView = async ctx => { } exports.fetch = async ctx => { - const appId = ctx.appId - const db = new CouchDB(appId) + const db = getAppDB() const tableId = ctx.params.tableId let table = await db.get(tableId) @@ -273,17 +267,15 @@ exports.fetch = async ctx => { } exports.find = async ctx => { - const appId = ctx.appId - const db = new CouchDB(appId) + const db = getAppDB() 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) return row } exports.destroy = async function (ctx) { - const appId = ctx.appId - const db = new CouchDB(appId) + const db = getAppDB() const { _id, _rev } = ctx.request.body let row = await db.get(_id) @@ -295,13 +287,12 @@ exports.destroy = async function (ctx) { row = await outputProcessing(ctx, table, row, { squash: false }) // now remove the relationships await linkRows.updateLinks({ - appId, eventType: linkRows.EventType.ROW_DELETE, row, tableId: row.tableId, }) // remove any attachments that were on the row from object storage - await cleanupAttachments(appId, table, { row }) + await cleanupAttachments(table, { row }) let response if (ctx.params.tableId === InternalTables.USER_METADATA) { @@ -317,8 +308,7 @@ exports.destroy = async function (ctx) { } exports.bulkDestroy = async ctx => { - const appId = ctx.appId - const db = new CouchDB(appId) + const db = getAppDB() const tableId = ctx.params.tableId const table = await db.get(tableId) let { rows } = ctx.request.body @@ -330,7 +320,6 @@ exports.bulkDestroy = async ctx => { // remove the relationships first let updates = rows.map(row => linkRows.updateLinks({ - appId, eventType: linkRows.EventType.ROW_DELETE, row, tableId: row.tableId, @@ -349,7 +338,7 @@ exports.bulkDestroy = async ctx => { await db.bulkDocs(rows.map(row => ({ ...row, _deleted: true }))) } // remove any attachments that were on the rows from object storage - await cleanupAttachments(appId, table, { rows }) + await cleanupAttachments(table, { rows }) await Promise.all(updates) return { response: { ok: true }, rows } } @@ -360,25 +349,24 @@ exports.search = async ctx => { return { rows: await exports.fetch(ctx) } } - const appId = ctx.appId const { tableId } = ctx.params - const db = new CouchDB(appId) + const db = getAppDB() const { paginate, query, ...params } = ctx.request.body params.version = ctx.version params.tableId = tableId let response if (paginate) { - response = await paginatedSearch(appId, query, params) + response = await paginatedSearch(query, params) } else { - response = await fullSearch(appId, query, params) + response = await fullSearch(query, params) } // Enrich search results with relationships if (response.rows && response.rows.length) { // enrich with global users if from users table if (tableId === InternalTables.USER_METADATA) { - response.rows = await getGlobalUsersFromMetadata(appId, response.rows) + response.rows = await getGlobalUsersFromMetadata(response.rows) } const table = await db.get(tableId) response.rows = await outputProcessing(ctx, table, response.rows) @@ -389,25 +377,22 @@ exports.search = async ctx => { exports.validate = async ctx => { return validate({ - appId: ctx.appId, tableId: ctx.params.tableId, row: ctx.request.body, }) } exports.fetchEnrichedRow = async ctx => { - const appId = ctx.appId - const db = new CouchDB(appId) + const db = getAppDB() const tableId = ctx.params.tableId const rowId = ctx.params.rowId // need table to work out where links go in row let [table, row] = await Promise.all([ db.get(tableId), - findRow(ctx, db, tableId, rowId), + findRow(ctx, tableId, rowId), ]) // get the link docs const linkVals = await linkRows.getLinkDocuments({ - appId, tableId, rowId, }) diff --git a/packages/server/src/api/controllers/row/internalSearch.js b/packages/server/src/api/controllers/row/internalSearch.js index 3a2586331a..21991f4de3 100644 --- a/packages/server/src/api/controllers/row/internalSearch.js +++ b/packages/server/src/api/controllers/row/internalSearch.js @@ -1,14 +1,14 @@ const { SearchIndexes } = require("../../../db/utils") const fetch = require("node-fetch") const { getCouchUrl } = require("@budibase/backend-core/db") +const { getAppId } = require("@budibase/backend-core/context") /** * Class to build lucene query URLs. * Optionally takes a base lucene query object. */ class QueryBuilder { - constructor(appId, base) { - this.appId = appId + constructor(base) { this.query = { string: {}, fuzzy: {}, @@ -233,7 +233,8 @@ class QueryBuilder { } async run() { - const url = `${getCouchUrl()}/${this.appId}/_design/database/_search/${ + const appId = getAppId() + const url = `${getCouchUrl()}/${appId}/_design/database/_search/${ SearchIndexes.ROWS }` const body = this.buildSearchBody() @@ -270,7 +271,6 @@ const runQuery = async (url, body) => { * 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 * until enough results have been found. - * @param appId {string} The app ID to search * @param query {object} The JSON query structure * @param params {object} The search params including: * tableId {string} The table ID to search @@ -283,7 +283,7 @@ const runQuery = async (url, body) => { * rows {array|null} Current results in the recursive search * @returns {Promise<*[]|*>} */ -const recursiveSearch = async (appId, query, params) => { +const recursiveSearch = async (query, params) => { const bookmark = params.bookmark const rows = params.rows || [] if (rows.length >= params.limit) { @@ -293,7 +293,7 @@ const recursiveSearch = async (appId, query, params) => { if (rows.length > params.limit - 200) { pageSize = params.limit - rows.length } - const page = await new QueryBuilder(appId, query) + const page = await new QueryBuilder(query) .setVersion(params.version) .setTable(params.tableId) .setBookmark(bookmark) @@ -313,14 +313,13 @@ const recursiveSearch = async (appId, query, params) => { bookmark: page.bookmark, 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 * page to be fetched. There is a max limit off 200 results per page in a * paginated search. - * @param appId {string} The app ID to search * @param query {object} The JSON query structure * @param params {object} The search params including: * tableId {string} The table ID to search @@ -332,13 +331,13 @@ const recursiveSearch = async (appId, query, params) => { * bookmark {string} The bookmark to resume from * @returns {Promise<{hasNextPage: boolean, rows: *[]}>} */ -exports.paginatedSearch = async (appId, query, params) => { +exports.paginatedSearch = async (query, params) => { let limit = params.limit if (limit == null || isNaN(limit) || limit < 0) { limit = 50 } limit = Math.min(limit, 200) - const search = new QueryBuilder(appId, query) + const search = new QueryBuilder(query) .setVersion(params.version) .setTable(params.tableId) .setSort(params.sort) @@ -367,7 +366,6 @@ exports.paginatedSearch = async (appId, query, params) => { * desired amount of results. There is a limit of 1000 results to avoid * heavy performance hits, and to avoid client components breaking from * handling too much data. - * @param appId {string} The app ID to search * @param query {object} The JSON query structure * @param params {object} The search params including: * tableId {string} The table ID to search @@ -378,12 +376,12 @@ exports.paginatedSearch = async (appId, query, params) => { * limit {number} The desired number of results * @returns {Promise<{rows: *}>} */ -exports.fullSearch = async (appId, query, params) => { +exports.fullSearch = async (query, params) => { let limit = params.limit if (limit == null || isNaN(limit) || limit < 0) { limit = 1000 } params.limit = Math.min(limit, 1000) - const rows = await recursiveSearch(appId, query, params) + const rows = await recursiveSearch(query, params) return { rows } } diff --git a/packages/server/src/api/controllers/row/utils.js b/packages/server/src/api/controllers/row/utils.js index 51bc03eba4..4235e70127 100644 --- a/packages/server/src/api/controllers/row/utils.js +++ b/packages/server/src/api/controllers/row/utils.js @@ -1,11 +1,11 @@ const validateJs = require("validate.js") const { cloneDeep } = require("lodash/fp") -const CouchDB = require("../../../db") const { InternalTables } = require("../../../db/utils") const userController = require("../user") const { FieldTypes } = require("../../../constants") const { processStringSync } = require("@budibase/string-templates") const { makeExternalQuery } = require("../../../integrations/base/utils") +const { getAppDB } = require("@budibase/backend-core/context") validateJs.extend(validateJs.validators.datetime, { 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 db = new CouchDB(appId) + const db = getAppDB() const datasource = await db.get(datasourceId) return makeExternalQuery(datasource, json) } -exports.findRow = async (ctx, db, tableId, rowId) => { +exports.findRow = async (ctx, tableId, rowId) => { + const db = getAppDB() let row // TODO remove special user case in future if (tableId === InternalTables.USER_METADATA) { @@ -42,9 +43,9 @@ exports.findRow = async (ctx, db, tableId, rowId) => { return row } -exports.validate = async ({ appId, tableId, row, table }) => { +exports.validate = async ({ tableId, row, table }) => { if (!table) { - const db = new CouchDB(appId) + const db = getAppDB() table = await db.get(tableId) } const errors = {} diff --git a/packages/server/src/api/controllers/screen.js b/packages/server/src/api/controllers/screen.js index 5e0eeb5176..e166ab3eb8 100644 --- a/packages/server/src/api/controllers/screen.js +++ b/packages/server/src/api/controllers/screen.js @@ -1,10 +1,9 @@ -const CouchDB = require("../../db") const { getScreenParams, generateScreenID } = require("../../db/utils") const { AccessController } = require("@budibase/backend-core/roles") +const { getAppDB } = require("@budibase/backend-core/context") exports.fetch = async ctx => { - const appId = ctx.appId - const db = new CouchDB(appId) + const db = getAppDB() const screens = ( await db.allDocs( @@ -14,15 +13,14 @@ exports.fetch = async ctx => { ) ).rows.map(element => element.doc) - ctx.body = await new AccessController(appId).checkScreensAccess( + ctx.body = await new AccessController().checkScreensAccess( screens, ctx.user.role._id ) } exports.save = async ctx => { - const appId = ctx.appId - const db = new CouchDB(appId) + const db = getAppDB() let screen = ctx.request.body if (!screen._id) { @@ -39,7 +37,7 @@ exports.save = async ctx => { } exports.destroy = async ctx => { - const db = new CouchDB(ctx.appId) + const db = getAppDB() await db.remove(ctx.params.screenId, ctx.params.screenRev) ctx.body = { message: "Screen deleted successfully", diff --git a/packages/server/src/api/controllers/static/index.js b/packages/server/src/api/controllers/static/index.js index 11bb14e282..cafe999150 100644 --- a/packages/server/src/api/controllers/static/index.js +++ b/packages/server/src/api/controllers/static/index.js @@ -6,7 +6,6 @@ const uuid = require("uuid") const { ObjectStoreBuckets } = require("../../../constants") const { processString } = require("@budibase/string-templates") const { getAllApps } = require("@budibase/backend-core/db") -const CouchDB = require("../../../db") const { loadHandlebarsFile, NODE_MODULES_PATH, @@ -17,6 +16,7 @@ const { clientLibraryPath } = require("../../../utilities") const { upload } = require("../../../utilities/fileSystem") const { attachmentsRelativeURL } = require("../../../utilities") const { DocumentTypes } = require("../../../db/utils") +const { getAppDB } = require("@budibase/backend-core/context") const AWS = require("aws-sdk") 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()}` // 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( a => a.url && a.url.toLowerCase() === possibleAppUrl )[0] @@ -85,7 +85,7 @@ exports.uploadFile = async function (ctx) { exports.serveApp = async function (ctx) { let appId = await getAppIdFromUrl(ctx) 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 { head, html, css } = App.render({ @@ -111,7 +111,7 @@ exports.serveClientLibrary = async function (ctx) { } exports.getSignedUploadURL = async function (ctx) { - const database = new CouchDB(ctx.appId) + const database = getAppDB() // Ensure datasource is valid let datasource diff --git a/packages/server/src/api/controllers/table/external.js b/packages/server/src/api/controllers/table/external.js index 2453ca7a37..b27eebb0c4 100644 --- a/packages/server/src/api/controllers/table/external.js +++ b/packages/server/src/api/controllers/table/external.js @@ -1,4 +1,3 @@ -const CouchDB = require("../../../db") const { buildExternalTableId, breakExternalTableId, @@ -19,6 +18,7 @@ const { makeExternalQuery } = require("../../../integrations/base/utils") const { cloneDeep } = require("lodash/fp") const csvParser = require("../../../utilities/csvParser") const { handleRequest } = require("../row/external") +const { getAppDB } = require("@budibase/backend-core/context") async function makeTableRequest( datasource, @@ -159,7 +159,6 @@ function isRelationshipSetup(column) { } exports.save = async function (ctx) { - const appId = ctx.appId const table = ctx.request.body // can't do this right now delete table.dataImport @@ -176,14 +175,14 @@ exports.save = async function (ctx) { let oldTable 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)) { ctx.throw(400, "A column type has changed.") } - const db = new CouchDB(appId) + const db = getAppDB() const datasource = await db.get(datasourceId) const oldTables = cloneDeep(datasource.entities) const tables = datasource.entities @@ -267,14 +266,13 @@ exports.save = async function (ctx) { } exports.destroy = async function (ctx) { - const appId = ctx.appId - const tableToDelete = await getTable(appId, ctx.params.tableId) + const tableToDelete = await getTable(ctx.params.tableId) if (!tableToDelete || !tableToDelete.created) { ctx.throw(400, "Cannot delete tables which weren't created in Budibase.") } const datasourceId = getDatasourceId(tableToDelete) - const db = new CouchDB(appId) + const db = getAppDB() const datasource = await db.get(datasourceId) const tables = datasource.entities @@ -290,8 +288,7 @@ exports.destroy = async function (ctx) { } exports.bulkImport = async function (ctx) { - const appId = ctx.appId - const table = await getTable(appId, ctx.params.tableId) + const table = await getTable(ctx.params.tableId) const { dataImport } = ctx.request.body if (!dataImport || !dataImport.schema || !dataImport.csvString) { ctx.throw(400, "Provided data import information is invalid.") @@ -300,7 +297,7 @@ exports.bulkImport = async function (ctx) { ...dataImport, existingTable: table, }) - await handleRequest(appId, DataSourceOperation.BULK_CREATE, table._id, { + await handleRequest(DataSourceOperation.BULK_CREATE, table._id, { rows, }) return table diff --git a/packages/server/src/api/controllers/table/index.js b/packages/server/src/api/controllers/table/index.js index 20dc10017d..f3493edb3b 100644 --- a/packages/server/src/api/controllers/table/index.js +++ b/packages/server/src/api/controllers/table/index.js @@ -1,4 +1,3 @@ -const CouchDB = require("../../../db") const internal = require("./internal") const external = require("./external") const csvParser = require("../../../utilities/csvParser") @@ -9,6 +8,7 @@ const { BudibaseInternalDB, } = require("../../../db/utils") const { getTable } = require("./utils") +const { getAppDB } = require("@budibase/backend-core/context") function pickApi({ tableId, table }) { if (table && !tableId) { @@ -24,7 +24,7 @@ function pickApi({ tableId, table }) { // covers both internal and external exports.fetch = async function (ctx) { - const db = new CouchDB(ctx.appId) + const db = getAppDB() const internalTables = await db.allDocs( getTableParams(null, { @@ -63,7 +63,7 @@ exports.fetch = async function (ctx) { exports.find = async function (ctx) { const tableId = ctx.params.id - ctx.body = await getTable(ctx.appId, tableId) + ctx.body = await getTable(tableId) } exports.save = async function (ctx) { @@ -102,7 +102,7 @@ exports.validateCSVSchema = async function (ctx) { const { csvString, schema = {}, tableId } = ctx.request.body let existingTable if (tableId) { - existingTable = await getTable(ctx.appId, tableId) + existingTable = await getTable(tableId) } let result = await csvParser.parse(csvString, schema) if (existingTable) { diff --git a/packages/server/src/api/controllers/table/internal.js b/packages/server/src/api/controllers/table/internal.js index 9f09e78219..7f7fe1cb3c 100644 --- a/packages/server/src/api/controllers/table/internal.js +++ b/packages/server/src/api/controllers/table/internal.js @@ -34,8 +34,7 @@ exports.save = async function (ctx) { // 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 const tableSaveFunctions = new TableSaveFunctions({ - db, - ctx, + user: ctx.user, oldTable, dataImport, }) @@ -145,9 +144,8 @@ exports.destroy = async function (ctx) { } exports.bulkImport = async function (ctx) { - const appId = ctx.appId - const table = await getTable(appId, ctx.params.tableId) + const table = await getTable(ctx.params.tableId) const { dataImport } = ctx.request.body - await handleDataImport(appId, ctx.user, table, dataImport) + await handleDataImport(ctx.user, table, dataImport) return table } diff --git a/packages/server/src/api/controllers/table/utils.js b/packages/server/src/api/controllers/table/utils.js index 86e2837e15..76dbada59a 100644 --- a/packages/server/src/api/controllers/table/utils.js +++ b/packages/server/src/api/controllers/table/utils.js @@ -1,4 +1,3 @@ -const CouchDB = require("../../../db") const csvParser = require("../../../utilities/csvParser") const { getRowParams, @@ -17,8 +16,10 @@ const { const { getViews, saveView } = require("../view/utils") const viewTemplate = require("../view/viewBuilder") const usageQuota = require("../../../utilities/usageQuota") +const { getAppDB } = require("@budibase/backend-core/context") -exports.checkForColumnUpdates = async (db, oldTable, updatedTable) => { +exports.checkForColumnUpdates = async (oldTable, updatedTable) => { + const db = getAppDB() let updatedRows = [] const rename = updatedTable._rename let deletedColumns = [] @@ -46,7 +47,7 @@ exports.checkForColumnUpdates = async (db, oldTable, updatedTable) => { }) // Update views - await exports.checkForViewUpdates(db, updatedTable, rename, deletedColumns) + await exports.checkForViewUpdates(updatedTable, rename, deletedColumns) delete updatedTable._rename } return { rows: updatedRows, table: updatedTable } @@ -73,12 +74,12 @@ exports.makeSureTableUpToDate = (table, tableToSave) => { return tableToSave } -exports.handleDataImport = async (appId, user, table, dataImport) => { +exports.handleDataImport = async (user, table, dataImport) => { if (!dataImport || !dataImport.csvString) { return table } - const db = new CouchDB(appId) + const db = getAppDB() // Populate the table with rows imported from CSV in a bulk update const data = await csvParser.transform({ ...dataImport, @@ -123,8 +124,8 @@ exports.handleDataImport = async (appId, user, table, dataImport) => { return table } -exports.handleSearchIndexes = async (appId, table) => { - const db = new CouchDB(appId) +exports.handleSearchIndexes = async table => { + const db = getAppDB() // create relevant search indexes if (table.indexes && table.indexes.length > 0) { const currentIndexes = await db.getIndexes() @@ -181,12 +182,9 @@ exports.checkStaticTables = table => { } class TableSaveFunctions { - constructor({ db, ctx, oldTable, dataImport }) { - this.db = db - this.ctx = ctx - if (this.ctx && this.ctx.user) { - this.appId = this.ctx.appId - } + constructor({ user, oldTable, dataImport }) { + this.db = getAppDB() + this.user = user this.oldTable = oldTable this.dataImport = dataImport // any rows that need updated @@ -204,24 +202,15 @@ class TableSaveFunctions { // when confirmed valid async mid(table) { - let response = await exports.checkForColumnUpdates( - this.db, - this.oldTable, - table - ) + let response = await exports.checkForColumnUpdates(this.oldTable, table) this.rows = this.rows.concat(response.rows) return table } // after saving async after(table) { - table = await exports.handleSearchIndexes(this.appId, table) - table = await exports.handleDataImport( - this.appId, - this.ctx.user, - table, - this.dataImport - ) + table = await exports.handleSearchIndexes(table) + table = await exports.handleDataImport(this.user, table, this.dataImport) return table } @@ -230,8 +219,8 @@ class TableSaveFunctions { } } -exports.getAllExternalTables = async (appId, datasourceId) => { - const db = new CouchDB(appId) +exports.getAllExternalTables = async datasourceId => { + const db = getAppDB() const datasource = await db.get(datasourceId) if (!datasource || !datasource.entities) { throw "Datasource is not configured fully." @@ -239,25 +228,25 @@ exports.getAllExternalTables = async (appId, datasourceId) => { return datasource.entities } -exports.getExternalTable = async (appId, datasourceId, tableName) => { - const entities = await exports.getAllExternalTables(appId, datasourceId) +exports.getExternalTable = async (datasourceId, tableName) => { + const entities = await exports.getAllExternalTables(datasourceId) return entities[tableName] } -exports.getTable = async (appId, tableId) => { - const db = new CouchDB(appId) +exports.getTable = async tableId => { + const db = getAppDB() if (isExternalTable(tableId)) { let { datasourceId, tableName } = breakExternalTableId(tableId) 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) } } else { return db.get(tableId) } } -exports.checkForViewUpdates = async (db, table, rename, deletedColumns) => { - const views = await getViews(db) +exports.checkForViewUpdates = async (table, rename, deletedColumns) => { + const views = await getViews() const tableViews = views.filter(view => view.meta.tableId === table._id) // Check each table view to see if impacted by this table action @@ -319,7 +308,7 @@ exports.checkForViewUpdates = async (db, table, rename, deletedColumns) => { // Update view if required if (needsUpdated) { const newViewTemplate = viewTemplate(view.meta) - await saveView(db, null, view.name, newViewTemplate) + await saveView(null, view.name, newViewTemplate) if (!newViewTemplate.meta.schema) { newViewTemplate.meta.schema = table.schema } diff --git a/packages/server/src/api/controllers/user.js b/packages/server/src/api/controllers/user.js index d87afc4309..31194c3e96 100644 --- a/packages/server/src/api/controllers/user.js +++ b/packages/server/src/api/controllers/user.js @@ -1,4 +1,3 @@ -const CouchDB = require("../../db") const { generateUserMetadataID, getUserMetadataParams, @@ -15,8 +14,10 @@ const { } = require("@budibase/backend-core/db") const { doesDatabaseExist } = require("../../utilities") 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 ( await db.allDocs( getUserMetadataParams(null, { @@ -54,13 +55,10 @@ function combineMetadataAndUser(user, metadata) { return null } -exports.syncGlobalUsers = async appId => { +exports.syncGlobalUsers = async () => { // sync user metadata - const db = new CouchDB(appId) - const [users, metadata] = await Promise.all([ - getGlobalUsers(appId), - rawMetadata(db), - ]) + const db = getAppDB() + const [users, metadata] = await Promise.all([getGlobalUsers(), rawMetadata()]) const toWrite = [] for (let user of users) { const combined = await combineMetadataAndUser(user, metadata) @@ -94,7 +92,7 @@ exports.syncUser = async function (ctx) { let prodAppIds // if they are a builder then get all production app IDs if ((user.builder && user.builder.global) || deleting) { - prodAppIds = await getDeployedAppIDs(CouchDB) + prodAppIds = await getDeployedAppIDs() } else { prodAppIds = Object.entries(roles) .filter(entry => entry[1] !== BUILTIN_ROLE_IDS.PUBLIC) @@ -107,7 +105,7 @@ exports.syncUser = async function (ctx) { if (!(await doesDatabaseExist(appId))) { continue } - const db = new CouchDB(appId) + const db = getAppDB() const metadataId = generateUserMetadataID(userId) let metadata try { @@ -143,8 +141,8 @@ exports.syncUser = async function (ctx) { } exports.fetchMetadata = async function (ctx) { - const database = new CouchDB(ctx.appId) - const global = await getGlobalUsers(ctx.appId) + const database = getAppDB() + const global = await getGlobalUsers() const metadata = await rawMetadata(database) const users = [] for (let user of global) { @@ -171,8 +169,7 @@ exports.updateSelfMetadata = async function (ctx) { } exports.updateMetadata = async function (ctx) { - const appId = ctx.appId - const db = new CouchDB(appId) + const db = getAppDB() const user = ctx.request.body // this isn't applicable to the user delete user.roles @@ -184,7 +181,7 @@ exports.updateMetadata = async function (ctx) { } exports.destroyMetadata = async function (ctx) { - const db = new CouchDB(ctx.appId) + const db = getAppDB() try { const dbUser = await db.get(ctx.params.id) await db.remove(dbUser._id, dbUser._rev) @@ -207,7 +204,7 @@ exports.setFlag = async function (ctx) { ctx.throw(400, "Must supply a 'flag' field in request body.") } const flagDocId = generateUserFlagID(userId) - const db = new CouchDB(ctx.appId) + const db = getAppDB() let doc try { doc = await db.get(flagDocId) @@ -222,7 +219,7 @@ exports.setFlag = async function (ctx) { exports.getFlags = async function (ctx) { const userId = ctx.user._id const docId = generateUserFlagID(userId) - const db = new CouchDB(ctx.appId) + const db = getAppDB() let doc try { doc = await db.get(docId) diff --git a/packages/server/src/api/controllers/view/index.js b/packages/server/src/api/controllers/view/index.js index e3232323bf..fd6b32f3d6 100644 --- a/packages/server/src/api/controllers/view/index.js +++ b/packages/server/src/api/controllers/view/index.js @@ -1,4 +1,3 @@ -const CouchDB = require("../../../db") const viewTemplate = require("./viewBuilder") const { apiFileReturn } = require("../../../utilities/fileSystem") const exporters = require("./exporters") @@ -6,14 +5,14 @@ const { saveView, getView, getViews, deleteView } = require("./utils") const { fetchView } = require("../row") const { getTable } = require("../table/utils") const { FieldTypes } = require("../../../constants") +const { getAppDB } = require("@budibase/backend-core/context") exports.fetch = async ctx => { - const db = new CouchDB(ctx.appId) - ctx.body = await getViews(db) + ctx.body = await getViews() } exports.save = async ctx => { - const db = new CouchDB(ctx.appId) + const db = getAppDB() const { originalName, ...viewToSave } = ctx.request.body const view = viewTemplate(viewToSave) @@ -21,7 +20,7 @@ exports.save = async ctx => { 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 const table = await db.get(ctx.request.body.tableId) @@ -42,9 +41,9 @@ exports.save = async ctx => { } exports.destroy = async ctx => { - const db = new CouchDB(ctx.appId) + const db = getAppDB() 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) delete table.views[viewName] await db.put(table) @@ -53,9 +52,8 @@ exports.destroy = async ctx => { } exports.exportView = async ctx => { - const db = new CouchDB(ctx.appId) const viewName = decodeURI(ctx.query.view) - const view = await getView(db, viewName) + const view = await getView(viewName) const format = ctx.query.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 if (!schema) { const tableId = ctx.params.tableId || view.meta.tableId - const table = await getTable(ctx.appId, tableId) + const table = await getTable(tableId) schema = table.schema } diff --git a/packages/server/src/api/controllers/view/utils.js b/packages/server/src/api/controllers/view/utils.js index 27fccaf47f..59d169ef7f 100644 --- a/packages/server/src/api/controllers/view/utils.js +++ b/packages/server/src/api/controllers/view/utils.js @@ -6,8 +6,10 @@ const { SEPARATOR, } = require("../../../db/utils") 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) { const designDoc = await db.get("_design/database") 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 = [] if (env.SELF_HOSTED) { const designDoc = await db.get("_design/database") @@ -54,7 +57,8 @@ exports.getViews = async db => { return response } -exports.saveView = async (db, originalName, viewName, viewTemplate) => { +exports.saveView = async (originalName, viewName, viewTemplate) => { + const db = getAppDB() if (env.SELF_HOSTED) { const designDoc = await db.get("_design/database") 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) { const designDoc = await db.get("_design/database") const view = designDoc.views[viewName] diff --git a/packages/server/src/api/routes/tests/misc.spec.js b/packages/server/src/api/routes/tests/misc.spec.js index ae5c0cca60..e5b87543d2 100644 --- a/packages/server/src/api/routes/tests/misc.spec.js +++ b/packages/server/src/api/routes/tests/misc.spec.js @@ -82,7 +82,6 @@ describe("run misc tests", () => { dataImport.schema[col] = { type: "string" } } await tableUtils.handleDataImport( - config.getAppId(), { userId: "test" }, table, dataImport diff --git a/packages/server/src/api/routes/tests/utilities/TestFunctions.js b/packages/server/src/api/routes/tests/utilities/TestFunctions.js index 9bd54f0d75..e9e15b7619 100644 --- a/packages/server/src/api/routes/tests/utilities/TestFunctions.js +++ b/packages/server/src/api/routes/tests/utilities/TestFunctions.js @@ -1,9 +1,9 @@ const rowController = require("../../../controllers/row") const appController = require("../../../controllers/application") -const CouchDB = require("../../../../db") const { AppStatus } = require("../../../../db/utils") const { BUILTIN_ROLE_IDS } = require("@budibase/backend-core/roles") const { TENANT_ID } = require("../../../../tests/utilities/structures") +const { getAppDB } = require("@budibase/backend-core/context") function Request(appId, params) { this.appId = appId @@ -96,8 +96,8 @@ exports.checkPermissionsEndpoint = async ({ .expect(403) } -exports.getDB = config => { - return new CouchDB(config.getAppId()) +exports.getDB = () => { + return getAppDB() } exports.testAutomation = async (config, automation) => { diff --git a/packages/server/src/automations/automationUtils.js b/packages/server/src/automations/automationUtils.js index aab341a1f8..9360840efd 100644 --- a/packages/server/src/automations/automationUtils.js +++ b/packages/server/src/automations/automationUtils.js @@ -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 * 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 {object} row The input row structure which requires clean-up after having been through template statements. * @returns {Promise} The cleaned up rows object, will should now have all the required primitive types. */ -exports.cleanUpRow = async (appId, tableId, row) => { - let table = await getTable(appId, tableId) +exports.cleanUpRow = async (tableId, row) => { + let table = await getTable(tableId) return exports.cleanInputValues(row, { properties: table.schema }) } diff --git a/packages/server/src/automations/steps/createRow.js b/packages/server/src/automations/steps/createRow.js index 1937121062..a16521d25d 100644 --- a/packages/server/src/automations/steps/createRow.js +++ b/packages/server/src/automations/steps/createRow.js @@ -78,7 +78,6 @@ exports.run = async function ({ inputs, appId, emitter }) { try { inputs.row = await automationUtils.cleanUpRow( - appId, inputs.row.tableId, inputs.row ) diff --git a/packages/server/src/automations/steps/updateRow.js b/packages/server/src/automations/steps/updateRow.js index a9569932fa..f66fcf9432 100644 --- a/packages/server/src/automations/steps/updateRow.js +++ b/packages/server/src/automations/steps/updateRow.js @@ -87,7 +87,7 @@ exports.run = async function ({ inputs, appId, emitter }) { try { if (tableId) { - inputs.row = await automationUtils.cleanUpRow(appId, tableId, inputs.row) + inputs.row = await automationUtils.cleanUpRow(tableId, inputs.row) } await rowController.patch(ctx) return { diff --git a/packages/server/src/db/linkedRows/LinkController.js b/packages/server/src/db/linkedRows/LinkController.js index b66e2debb5..86c32bf94f 100644 --- a/packages/server/src/db/linkedRows/LinkController.js +++ b/packages/server/src/db/linkedRows/LinkController.js @@ -1,4 +1,3 @@ -const CouchDB = require("../index") const { IncludeDocs, getLinkDocuments } = require("./linkUtils") const { generateLinkID, @@ -7,6 +6,7 @@ const { } = require("../utils") const Sentry = require("@sentry/node") 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 @@ -52,9 +52,8 @@ function LinkDocument( } class LinkController { - constructor({ appId, tableId, row, table, oldTable }) { - this._appId = appId - this._db = new CouchDB(appId) + constructor({ tableId, row, table, oldTable }) { + this._db = getAppDB() this._tableId = tableId this._row = row this._table = table @@ -99,7 +98,6 @@ class LinkController { */ getRowLinkDocs(rowId) { return getLinkDocuments({ - appId: this._appId, tableId: this._tableId, rowId, includeDocs: IncludeDocs.INCLUDE, @@ -111,7 +109,6 @@ class LinkController { */ getTableLinkDocs() { return getLinkDocuments({ - appId: this._appId, tableId: this._tableId, includeDocs: IncludeDocs.INCLUDE, }) @@ -230,7 +227,6 @@ class LinkController { if (linkedSchema.relationshipType === RelationshipTypes.ONE_TO_MANY) { let links = ( await getLinkDocuments({ - appId: this._appId, tableId: field.tableId, rowId: linkId, includeDocs: IncludeDocs.EXCLUDE, diff --git a/packages/server/src/db/linkedRows/index.js b/packages/server/src/db/linkedRows/index.js index 6835719e5f..f2872d808a 100644 --- a/packages/server/src/db/linkedRows/index.js +++ b/packages/server/src/db/linkedRows/index.js @@ -9,12 +9,12 @@ const { getLinkedTable, } = require("./linkUtils") const { flatten } = require("lodash") -const CouchDB = require("../../db") const { FieldTypes } = require("../../constants") const { getMultiIDParams, USER_METDATA_PREFIX } = require("../../db/utils") const { partition } = require("lodash") const { getGlobalUsersFromMetadata } = require("../../utilities/global") 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 @@ -48,14 +48,13 @@ function clearRelationshipFields(table, rows) { return rows } -async function getLinksForRows(appId, rows) { +async function getLinksForRows(rows) { const tableIds = [...new Set(rows.map(el => el.tableId))] // start by getting all the link values for performance reasons const responses = flatten( await Promise.all( tableIds.map(tableId => getLinkDocuments({ - appId, tableId: tableId, includeDocs: IncludeDocs.EXCLUDE, }) @@ -72,9 +71,9 @@ async function getLinksForRows(appId, rows) { ) } -async function getFullLinkedDocs(ctx, appId, links) { +async function getFullLinkedDocs(links) { // create DBs - const db = new CouchDB(appId) + const db = getAppDB() const linkedRowIds = links.map(link => link.id) const uniqueRowIds = [...new Set(linkedRowIds)] let dbRows = (await db.allDocs(getMultiIDParams(uniqueRowIds))).rows.map( @@ -88,7 +87,7 @@ async function getFullLinkedDocs(ctx, appId, links) { let [users, other] = partition(linked, linkRow => linkRow._id.startsWith(USER_METDATA_PREFIX) ) - users = await getGlobalUsersFromMetadata(appId, users) + users = await getGlobalUsersFromMetadata(users) return [...other, ...users] } @@ -96,7 +95,6 @@ async function getFullLinkedDocs(ctx, appId, links) { * 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 * 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 {object|null} 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. @@ -105,11 +103,8 @@ async function getFullLinkedDocs(ctx, appId, links) { * row operations and the table for table operations. */ 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 - if (appId == null) { - throw "Cannot operate without an instance ID." - } // make sure table ID is set if (tableId == null && table != null) { args.tableId = table._id @@ -146,27 +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. * This is required for formula fields, this may only be utilised internally (for now). - * @param {object} ctx The request which is looking for rows. * @param {object} table The table from which the rows originated. * @param {array} rows The rows which are to be enriched. * @return {Promise<*>} returns the rows with all of the enriched relationships on it. */ -exports.attachFullLinkedDocs = async (ctx, table, rows) => { - const appId = ctx.appId +exports.attachFullLinkedDocs = async (table, rows) => { const linkedTableIds = getLinkedTableIDs(table) if (linkedTableIds.length === 0) { return rows } - // create DBs - const db = new CouchDB(appId) // 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) ) // clear any existing links that could be dupe'd rows = clearRelationshipFields(table, rows) // now get the docs and combine into the rows - let linked = await getFullLinkedDocs(ctx, appId, links) + let linked = await getFullLinkedDocs(links) const linkedTables = [] for (let row of rows) { for (let link of links.filter(link => link.thisId === row._id)) { @@ -177,11 +168,7 @@ exports.attachFullLinkedDocs = async (ctx, table, rows) => { if (linkedRow) { const linkedTableId = linkedRow.tableId || getRelatedTableForField(table, link.fieldName) - const linkedTable = await getLinkedTable( - db, - linkedTableId, - linkedTables - ) + const linkedTable = await getLinkedTable(linkedTableId, linkedTables) if (linkedTable) { row[link.fieldName].push(processFormulas(linkedTable, linkedRow)) } @@ -193,18 +180,16 @@ exports.attachFullLinkedDocs = async (ctx, table, rows) => { /** * 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 {array} enriched The pre-enriched rows (full docs) which are to be squashed. * @returns {Promise} The rows after having their links squashed to only contain the ID and primary display. */ -exports.squashLinksToPrimaryDisplay = async (appId, table, enriched) => { - const db = new CouchDB(appId) +exports.squashLinksToPrimaryDisplay = async (table, enriched) => { // will populate this as we find them const linkedTables = [table] for (let row of enriched) { // this only fetches the table if its not already in array - const rowTable = await getLinkedTable(db, row.tableId, linkedTables) + const rowTable = await getLinkedTable(row.tableId, linkedTables) for (let [column, schema] of Object.entries(rowTable.schema)) { if (schema.type !== FieldTypes.LINK || !Array.isArray(row[column])) { continue @@ -212,7 +197,7 @@ exports.squashLinksToPrimaryDisplay = async (appId, table, enriched) => { const newLinks = [] for (let link of row[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 } if (link[linkedTable.primaryDisplay]) { obj.primaryDisplay = link[linkedTable.primaryDisplay] diff --git a/packages/server/src/db/linkedRows/linkUtils.js b/packages/server/src/db/linkedRows/linkUtils.js index 12e72af78d..5af4aa919a 100644 --- a/packages/server/src/db/linkedRows/linkUtils.js +++ b/packages/server/src/db/linkedRows/linkUtils.js @@ -1,8 +1,8 @@ -const CouchDB = require("../index") const Sentry = require("@sentry/node") const { ViewNames, getQueryIndex } = require("../utils") const { FieldTypes } = require("../../constants") const { createLinkView } = require("../views/staticViews") +const { getAppDB } = require("@budibase/backend-core/context") /** * 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. - * @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|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. @@ -30,8 +29,8 @@ exports.createLinkView = createLinkView * (if any). */ exports.getLinkDocuments = async function (args) { - const { appId, tableId, rowId, includeDocs } = args - const db = new CouchDB(appId) + const { tableId, rowId, includeDocs } = args + const db = getAppDB() let params if (rowId != null) { params = { key: [tableId, rowId] } @@ -68,7 +67,7 @@ exports.getLinkDocuments = async function (args) { } catch (err) { // check if the view doesn't exist, it should for all new instances if (err != null && err.name === "not_found") { - await exports.createLinkView(appId) + await exports.createLinkView() return exports.getLinkDocuments(arguments[0]) } else { /* istanbul ignore next */ @@ -89,7 +88,8 @@ exports.getLinkedTableIDs = table => { .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) if (linkedTable) { return linkedTable diff --git a/packages/server/src/db/tests/linkController.spec.js b/packages/server/src/db/tests/linkController.spec.js index d45bd99ea2..180cc2b3a0 100644 --- a/packages/server/src/db/tests/linkController.spec.js +++ b/packages/server/src/db/tests/linkController.spec.js @@ -20,7 +20,6 @@ describe("test the link controller", () => { function createLinkController(table, row = null, oldTable = null) { const linkConfig = { - appId: config.getAppId(), tableId: table._id, table, } diff --git a/packages/server/src/db/tests/linkTests.spec.js b/packages/server/src/db/tests/linkTests.spec.js index 8dad7be049..9a309df70a 100644 --- a/packages/server/src/db/tests/linkTests.spec.js +++ b/packages/server/src/db/tests/linkTests.spec.js @@ -1,8 +1,8 @@ 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 links = require("../linkedRows") const CouchDB = require("../index") +const { getAppDB } = require("@budibase/backend-core/context") describe("test link functionality", () => { const config = new TestConfig(false) @@ -11,18 +11,18 @@ describe("test link functionality", () => { let db, table beforeEach(async () => { await config.init() - db = new CouchDB(config.getAppId()) + db = getAppDB() table = await config.createTable() }) 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) }) it("should be able to retrieve a table from DB and update list", async () => { 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(tables[0]).toBeDefined() }) @@ -51,7 +51,6 @@ describe("test link functionality", () => { const db = new CouchDB("test") await db.put({ _id: "_design/database", views: {} }) const output = await linkUtils.getLinkDocuments({ - appId: "test", tableId: "test", rowId: "test", includeDocs: false, diff --git a/packages/server/src/db/views/staticViews.js b/packages/server/src/db/views/staticViews.js index 8e7b101ef5..50b7c305d3 100644 --- a/packages/server/src/db/views/staticViews.js +++ b/packages/server/src/db/views/staticViews.js @@ -1,4 +1,4 @@ -const CouchDB = require("../index") +const { getAppDB } = require("@budibase/backend-core/context") const { DocumentTypes, 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 * 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} The view now exists, please note that the next view of this query will actually build it, * so it may be slow. */ -exports.createLinkView = async appId => { - const db = new CouchDB(appId) +exports.createLinkView = async () => { + const db = getAppDB() const designDoc = await db.get("_design/database") const view = { map: function (doc) { @@ -57,8 +56,8 @@ exports.createLinkView = async appId => { await db.put(designDoc) } -exports.createRoutingView = async appId => { - const db = new CouchDB(appId) +exports.createRoutingView = async () => { + const db = getAppDB() const designDoc = await db.get("_design/database") const view = { // 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) } -async function searchIndex(appId, indexName, fnString) { - const db = new CouchDB(appId) +async function searchIndex(indexName, fnString) { + const db = getAppDB() const designDoc = await db.get("_design/database") designDoc.indexes = { [indexName]: { @@ -90,9 +89,8 @@ async function searchIndex(appId, indexName, fnString) { await db.put(designDoc) } -exports.createAllSearchIndex = async appId => { +exports.createAllSearchIndex = async () => { await searchIndex( - appId, SearchIndexes.ROWS, function (doc) { function idx(input, prev) { diff --git a/packages/server/src/integrations/utils.ts b/packages/server/src/integrations/utils.ts index b9e643e26a..398f7fe56a 100644 --- a/packages/server/src/integrations/utils.ts +++ b/packages/server/src/integrations/utils.ts @@ -52,7 +52,10 @@ export function buildExternalTableId(datasourceId: string, tableName: string) { 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) let tableName = parts.pop() // if they need joined diff --git a/packages/server/src/middleware/authorized.js b/packages/server/src/middleware/authorized.js index b463895a80..e3414192af 100644 --- a/packages/server/src/middleware/authorized.js +++ b/packages/server/src/middleware/authorized.js @@ -43,13 +43,13 @@ module.exports = // need to check this first, in-case public access, don't check authed until last const roleId = ctx.roleId || BUILTIN_ROLE_IDS.PUBLIC - const hierarchy = await getUserRoleHierarchy(ctx.appId, roleId, { + const hierarchy = await getUserRoleHierarchy(roleId, { idOnly: false, }) const permError = "User does not have permission" let possibleRoleIds = [] if (hasResource(ctx)) { - possibleRoleIds = await getRequiredResourceRole(ctx.appId, permLevel, ctx) + possibleRoleIds = await getRequiredResourceRole(permLevel, ctx) } // check if we found a role, if not fallback to base permissions if (possibleRoleIds.length > 0) { diff --git a/packages/server/src/middleware/currentapp.js b/packages/server/src/middleware/currentapp.js index 69f80c895b..43f5ed9d46 100644 --- a/packages/server/src/middleware/currentapp.js +++ b/packages/server/src/middleware/currentapp.js @@ -11,7 +11,6 @@ const { generateUserMetadataID, isDevAppID } = require("../db/utils") const { dbExists } = require("@budibase/backend-core/db") const { isUserInAppTenant } = require("@budibase/backend-core/tenancy") const { getCachedSelf } = require("../utilities/global") -const CouchDB = require("../db") const env = require("../environment") const { isWebhookEndpoint } = require("./utils") @@ -31,7 +30,7 @@ module.exports = async (ctx, next) => { // check the app exists referenced in cookie if (appCookie) { const appId = appCookie.appId - const exists = await dbExists(CouchDB, appId) + const exists = await dbExists(appId) if (!exists) { clearCookie(ctx, Cookies.CurrentApp) return next() diff --git a/packages/server/src/migrations/usageQuotas/syncApps.js b/packages/server/src/migrations/usageQuotas/syncApps.js index ee106129e6..e373c397ac 100644 --- a/packages/server/src/migrations/usageQuotas/syncApps.js +++ b/packages/server/src/migrations/usageQuotas/syncApps.js @@ -1,12 +1,11 @@ const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy") const { getAllApps } = require("@budibase/backend-core/db") -const CouchDB = require("../../db") const { getUsageQuotaDoc } = require("../../utilities/usageQuota") exports.run = async () => { const db = getGlobalDB() // get app count - const devApps = await getAllApps(CouchDB, { dev: true }) + const devApps = await getAllApps({ dev: true }) const appCount = devApps ? devApps.length : 0 // sync app count diff --git a/packages/server/src/migrations/usageQuotas/syncRows.js b/packages/server/src/migrations/usageQuotas/syncRows.js index 7990f405de..5bdda08d8e 100644 --- a/packages/server/src/migrations/usageQuotas/syncRows.js +++ b/packages/server/src/migrations/usageQuotas/syncRows.js @@ -1,13 +1,12 @@ const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy") const { getAllApps } = require("@budibase/backend-core/db") -const CouchDB = require("../../db") const { getUsageQuotaDoc } = require("../../utilities/usageQuota") const { getUniqueRows } = require("../../utilities/usageQuota/rows") exports.run = async () => { const db = getGlobalDB() // 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 => app.appId) : [] const rows = await getUniqueRows(appIds) const rowCount = rows ? rows.length : 0 diff --git a/packages/server/src/tests/utilities/TestConfiguration.js b/packages/server/src/tests/utilities/TestConfiguration.js index 7aefe4fb78..d10ccdd230 100644 --- a/packages/server/src/tests/utilities/TestConfiguration.js +++ b/packages/server/src/tests/utilities/TestConfiguration.js @@ -23,6 +23,7 @@ const { createASession } = require("@budibase/backend-core/sessions") const { user: userCache } = require("@budibase/backend-core/cache") const CouchDB = require("../../db") const newid = require("../../db/newid") +const context = require("@budibase/backend-core/context") core.init(CouchDB) const GLOBAL_USER_ID = "us_uuid1" @@ -50,6 +51,7 @@ class TestConfiguration { } async _req(config, params, controlFunc) { + context.updateAppId(this.appId) const request = {} // fake cookies, we don't need them request.cookies = { set: () => {}, get: () => {} } @@ -165,6 +167,7 @@ class TestConfiguration { // create dev app this.app = await this._req({ name: appName }, null, controllers.app.create) this.appId = this.app.appId + context.updateAppId(this.appId) // create production app this.prodApp = await this.deploy() diff --git a/packages/server/src/utilities/fileSystem/index.js b/packages/server/src/utilities/fileSystem/index.js index b8ddb1a356..7a9c2f350c 100644 --- a/packages/server/src/utilities/fileSystem/index.js +++ b/packages/server/src/utilities/fileSystem/index.js @@ -20,6 +20,7 @@ const { LINK_USER_METADATA_PREFIX, } = require("../../db/utils") const MemoryStream = require("memorystream") +const { getAppId } = require("@budibase/backend-core/context") const TOP_LEVEL_PATH = join(__dirname, "..", "..", "..") const NODE_MODULES_PATH = join(TOP_LEVEL_PATH, "node_modules") @@ -251,7 +252,8 @@ exports.downloadTemplate = async (type, name) => { /** * 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" /* istanbul ignore next */ // when testing in cypress and so on we need to get the package diff --git a/packages/server/src/utilities/global.js b/packages/server/src/utilities/global.js index 7ef1c09405..959eb59932 100644 --- a/packages/server/src/utilities/global.js +++ b/packages/server/src/utilities/global.js @@ -11,8 +11,10 @@ const { isUserInAppTenant, } = require("@budibase/backend-core/tenancy") 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) { return user } @@ -35,18 +37,18 @@ exports.updateAppRole = (appId, user) => { return user } -function processUser(appId, user) { +function processUser(user, { appId } = {}) { if (user) { delete user.password } - return exports.updateAppRole(appId, user) + return exports.updateAppRole(user, { appId }) } exports.getCachedSelf = async (ctx, appId) => { // 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 const user = await userCache.getUser(ctx.user._id) - return processUser(appId, user) + return processUser(user, appId) } exports.getRawGlobalUser = async userId => { @@ -54,12 +56,13 @@ exports.getRawGlobalUser = async userId => { return db.get(getGlobalIDFromUserMetadataID(userId)) } -exports.getGlobalUser = async (appId, userId) => { +exports.getGlobalUser = async 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() let globalUsers if (users) { @@ -86,11 +89,11 @@ exports.getGlobalUsers = async (appId = null, users = null) => { if (!appId) { return globalUsers } - return globalUsers.map(user => exports.updateAppRole(appId, user)) + return globalUsers.map(user => exports.updateAppRole(user)) } -exports.getGlobalUsersFromMetadata = async (appId, users) => { - const globalUsers = await exports.getGlobalUsers(appId, users) +exports.getGlobalUsersFromMetadata = async users => { + const globalUsers = await exports.getGlobalUsers(users) return users.map(user => { const globalUser = globalUsers.find( globalUser => globalUser && user._id.includes(globalUser._id) diff --git a/packages/server/src/utilities/rowProcessor/index.js b/packages/server/src/utilities/rowProcessor/index.js index 4f5d72c179..55c7494928 100644 --- a/packages/server/src/utilities/rowProcessor/index.js +++ b/packages/server/src/utilities/rowProcessor/index.js @@ -10,7 +10,7 @@ const { getDeployedAppID, dbExists, } = require("@budibase/backend-core/db") -const CouchDB = require("../../db") +const { getAppId } = require("@budibase/backend-core/context") const BASE_AUTO_ID = 1 @@ -263,14 +263,13 @@ exports.outputProcessing = async ( rows, opts = { squash: true } ) => { - const appId = ctx.appId let wasArray = true if (!(rows instanceof Array)) { rows = [rows] wasArray = false } // attach any linked row information - let enriched = await linkRows.attachFullLinkedDocs(ctx, table, rows) + let enriched = await linkRows.attachFullLinkedDocs(table, rows) // process formulas enriched = processFormulas(table, enriched) @@ -289,29 +288,25 @@ exports.outputProcessing = async ( } } if (opts.squash) { - enriched = await linkRows.squashLinksToPrimaryDisplay( - appId, - table, - enriched - ) + enriched = await linkRows.squashLinksToPrimaryDisplay(table, enriched) } return wasArray ? enriched : enriched[0] } /** * 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 {any} row optional - the row being removed. * @param {any} rows optional - if multiple rows being deleted can do this in bulk. * @param {any} oldRow optional - if updating a row this will determine the difference. * @return {Promise} When all attachments have been removed this will return. */ -exports.cleanupAttachments = async (appId, table, { row, rows, oldRow }) => { +exports.cleanupAttachments = async (table, { row, rows, oldRow }) => { + const appId = getAppId() if (!isProdAppID(appId)) { const prodAppId = getDeployedAppID(appId) // if prod exists, then don't allow deleting - const exists = await dbExists(CouchDB, prodAppId) + const exists = await dbExists(prodAppId) if (exists) { return } diff --git a/packages/server/src/utilities/users.js b/packages/server/src/utilities/users.js index 6144397bf1..b3601986d8 100644 --- a/packages/server/src/utilities/users.js +++ b/packages/server/src/utilities/users.js @@ -1,13 +1,13 @@ -const CouchDB = require("../db") const { InternalTables } = require("../db/utils") const { getGlobalUser } = require("../utilities/global") +const { getAppDB } = require("@budibase/backend-core/context") exports.getFullUser = async (ctx, userId) => { - const global = await getGlobalUser(ctx.appId, userId) + const global = await getGlobalUser(userId) let metadata try { // 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) } catch (err) { // it is fine if there is no user metadata, just remove global db info diff --git a/packages/server/src/utilities/workerRequests.js b/packages/server/src/utilities/workerRequests.js index 5e46f1678f..a7fa92b295 100644 --- a/packages/server/src/utilities/workerRequests.js +++ b/packages/server/src/utilities/workerRequests.js @@ -70,7 +70,7 @@ exports.getGlobalSelf = async (ctx, appId = null) => { } let json = await response.json() if (appId) { - json = updateAppRole(appId, json) + json = updateAppRole(json) } return json } diff --git a/packages/worker/src/api/controllers/global/configs.js b/packages/worker/src/api/controllers/global/configs.js index fc0aa868a3..604e7d0e93 100644 --- a/packages/worker/src/api/controllers/global/configs.js +++ b/packages/worker/src/api/controllers/global/configs.js @@ -11,7 +11,6 @@ const { upload, ObjectStoreBuckets, } = require("@budibase/backend-core/objectStore") -const CouchDB = require("../../../db") const { getGlobalDB, getTenantId } = require("@budibase/backend-core/tenancy") const env = require("../../../environment") const { googleCallbackUrl, oidcCallbackUrl } = require("./auth") @@ -252,7 +251,7 @@ exports.configChecklist = async function (ctx) { // TODO: Watch get started video // Apps exist - const apps = await getAllApps(CouchDB, { idsOnly: true }) + const apps = await getAllApps({ idsOnly: true }) // They have set up SMTP const smtpConfig = await getScopedFullConfig(db, { diff --git a/packages/worker/src/api/controllers/global/roles.js b/packages/worker/src/api/controllers/global/roles.js index 3c977a6290..ee55256f35 100644 --- a/packages/worker/src/api/controllers/global/roles.js +++ b/packages/worker/src/api/controllers/global/roles.js @@ -9,7 +9,7 @@ const CouchDB = require("../../../db") exports.fetch = async ctx => { const tenantId = ctx.user.tenantId // 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 = [] for (let app of apps) { // use dev app IDs