Main body of work, refactoring most usages.

This commit is contained in:
mike12345567 2022-01-27 18:18:31 +00:00
parent d8ab715267
commit d2fe119d90
52 changed files with 453 additions and 400 deletions

View File

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

View File

@ -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<object[]>} returns the app information document stored in each app database.
*/
exports.getAllApps = async (CouchDB, { dev, all, idsOnly } = {}) => {
exports.getAllApps = async ({ dev, all, idsOnly } = {}) => {
const CouchDB = getCouch()
let tenantId = getTenantId()
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 })

View File

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

View File

@ -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<Role|object|null>} 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<string[]>} 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<object[]>} 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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,13 +229,15 @@ 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)
if (tableName) {
return this.tables[tableName]
}
}
inputProcessing(row: Row | undefined, table: Table) {
if (!row) {
@ -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)
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 &&

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -53,13 +53,12 @@ exports.cleanInputValues = (inputs, schema) => {
* the automation but is instead part of the Table/Table. This function will get the table schema and use it to instead
* 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<Object>} 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 })
}

View File

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

View File

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

View File

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

View File

@ -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<object>} 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<object>} enriched The pre-enriched rows (full docs) which are to be squashed.
* @returns {Promise<Array>} The rows after having their links squashed to only contain the ID and primary display.
*/
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]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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