2022-07-13 14:22:21 +02:00
|
|
|
import env from "../environment"
|
|
|
|
import { SEPARATOR, DocumentTypes } from "../db/constants"
|
|
|
|
import cls from "./FunctionContext"
|
|
|
|
import { dangerousGetDB, closeDB } from "../db"
|
|
|
|
import { getProdAppID, getDevelopmentAppID } from "../db/conversions"
|
|
|
|
import { baseGlobalDBName } from "../tenancy/utils"
|
|
|
|
import { IdentityContext } from "@budibase/types"
|
|
|
|
import { isEqual } from "lodash"
|
|
|
|
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
|
|
|
|
|
|
|
|
export const DEFAULT_TENANT_ID = _DEFAULT_TENANT_ID
|
2022-01-27 19:18:31 +01:00
|
|
|
|
|
|
|
// some test cases call functions directly, need to
|
|
|
|
// store an app ID to pretend there is a context
|
2022-07-13 14:22:21 +02:00
|
|
|
let TEST_APP_ID: string | null = null
|
2022-01-27 19:18:31 +01:00
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
enum ContextKeys {
|
|
|
|
TENANT_ID = "tenantId",
|
|
|
|
GLOBAL_DB = "globalDb",
|
|
|
|
APP_ID = "appId",
|
|
|
|
IDENTITY = "identity",
|
2022-01-27 19:18:31 +01:00
|
|
|
// whatever the request app DB was
|
2022-07-13 14:22:21 +02:00
|
|
|
CURRENT_DB = "currentDb",
|
2022-01-27 19:18:31 +01:00
|
|
|
// get the prod app DB from the request
|
2022-07-13 14:22:21 +02:00
|
|
|
PROD_DB = "prodDb",
|
2022-01-27 19:18:31 +01:00
|
|
|
// get the dev app DB from the request
|
2022-07-13 14:22:21 +02:00
|
|
|
DEV_DB = "devDb",
|
|
|
|
DB_OPTS = "dbOpts",
|
2022-04-21 00:10:39 +02:00
|
|
|
// check if something else is using the context, don't close DB
|
2022-07-13 14:22:21 +02:00
|
|
|
TENANCY_IN_USE = "tenancyInUse",
|
|
|
|
APP_IN_USE = "appInUse",
|
|
|
|
IDENTITY_IN_USE = "identityInUse",
|
2022-01-27 19:18:31 +01:00
|
|
|
}
|
2021-08-05 10:59:08 +02:00
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
let openAppCount = 0
|
|
|
|
let closeAppCount = 0
|
|
|
|
let openTenancyCount = 0
|
|
|
|
let closeTenancyCount = 0
|
|
|
|
|
|
|
|
setInterval(function () {
|
|
|
|
console.log("openAppCount: " + openAppCount)
|
|
|
|
console.log("closeAppCount: " + closeAppCount)
|
|
|
|
console.log("openTenancyCount: " + openTenancyCount)
|
|
|
|
console.log("closeTenancyCount: " + closeTenancyCount)
|
|
|
|
console.log("------------------ ")
|
|
|
|
}, 5000)
|
2022-04-19 20:42:52 +02:00
|
|
|
|
|
|
|
// this function makes sure the PouchDB objects are closed and
|
|
|
|
// fully deleted when finished - this protects against memory leaks
|
|
|
|
async function closeAppDBs() {
|
|
|
|
const dbKeys = [
|
|
|
|
ContextKeys.CURRENT_DB,
|
|
|
|
ContextKeys.PROD_DB,
|
|
|
|
ContextKeys.DEV_DB,
|
|
|
|
]
|
|
|
|
for (let dbKey of dbKeys) {
|
|
|
|
const db = cls.getFromContext(dbKey)
|
|
|
|
if (!db) {
|
|
|
|
continue
|
|
|
|
}
|
2022-07-13 14:22:21 +02:00
|
|
|
closeAppCount++
|
2022-04-20 18:33:42 +02:00
|
|
|
await closeDB(db)
|
2022-04-21 00:10:39 +02:00
|
|
|
// clear the DB from context, incase someone tries to use it again
|
|
|
|
cls.setOnContext(dbKey, null)
|
|
|
|
}
|
|
|
|
// clear the app ID now that the databases are closed
|
|
|
|
if (cls.getFromContext(ContextKeys.APP_ID)) {
|
|
|
|
cls.setOnContext(ContextKeys.APP_ID, null)
|
|
|
|
}
|
|
|
|
if (cls.getFromContext(ContextKeys.DB_OPTS)) {
|
|
|
|
cls.setOnContext(ContextKeys.DB_OPTS, null)
|
2022-04-19 20:42:52 +02:00
|
|
|
}
|
|
|
|
}
|
2021-08-05 10:59:08 +02:00
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const closeTenancy = async () => {
|
|
|
|
closeTenancyCount++
|
2022-05-10 17:37:24 +02:00
|
|
|
if (env.USE_COUCH) {
|
2022-07-13 14:22:21 +02:00
|
|
|
await closeDB(getGlobalDB())
|
2022-05-10 17:37:24 +02:00
|
|
|
}
|
|
|
|
// clear from context now that database is closed/task is finished
|
|
|
|
cls.setOnContext(ContextKeys.TENANT_ID, null)
|
|
|
|
cls.setOnContext(ContextKeys.GLOBAL_DB, null)
|
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
// export const isDefaultTenant = () => {
|
|
|
|
// return getTenantId() === DEFAULT_TENANT_ID
|
|
|
|
// }
|
2021-08-05 10:59:08 +02:00
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const isMultiTenant = () => {
|
2021-08-05 10:59:08 +02:00
|
|
|
return env.MULTI_TENANCY
|
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
/**
|
|
|
|
* Given an app ID this will attempt to retrieve the tenant ID from it.
|
|
|
|
* @return {null|string} The tenant ID found within the app ID.
|
|
|
|
*/
|
|
|
|
export const getTenantIDFromAppID = (appId: string) => {
|
|
|
|
if (!appId) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
const split = appId.split(SEPARATOR)
|
|
|
|
const hasDev = split[1] === DocumentTypes.DEV
|
|
|
|
if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
if (hasDev) {
|
|
|
|
return split[2]
|
|
|
|
} else {
|
|
|
|
return split[1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const setAppTenantId = (appId: string) => {
|
|
|
|
const appTenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID
|
|
|
|
updateTenantId(appTenantId)
|
|
|
|
}
|
|
|
|
|
2021-08-05 10:59:08 +02:00
|
|
|
// used for automations, API endpoints should always be in context already
|
2022-07-13 14:22:21 +02:00
|
|
|
export const doInTenant = (tenantId: string | null, task: any) => {
|
2022-04-19 20:42:52 +02:00
|
|
|
// the internal function is so that we can re-use an existing
|
|
|
|
// context - don't want to close DB on a parent context
|
2022-05-24 21:01:13 +02:00
|
|
|
async function internal(opts = { existing: false }) {
|
2022-07-13 14:22:21 +02:00
|
|
|
// set the tenant id + global db if this is a new context
|
2022-04-19 20:42:52 +02:00
|
|
|
if (!opts.existing) {
|
2022-07-13 14:22:21 +02:00
|
|
|
updateTenantId(tenantId)
|
2022-04-19 20:42:52 +02:00
|
|
|
}
|
2021-08-05 10:59:08 +02:00
|
|
|
|
2022-04-20 18:33:42 +02:00
|
|
|
try {
|
|
|
|
// invoke the task
|
|
|
|
return await task()
|
|
|
|
} finally {
|
2022-07-13 14:22:21 +02:00
|
|
|
const using = cls.getFromContext(ContextKeys.TENANCY_IN_USE)
|
2022-04-21 00:10:39 +02:00
|
|
|
if (!using || using <= 1) {
|
2022-07-13 14:22:21 +02:00
|
|
|
await closeTenancy()
|
2022-04-21 00:10:39 +02:00
|
|
|
} else {
|
|
|
|
cls.setOnContext(using - 1)
|
2022-04-20 18:33:42 +02:00
|
|
|
}
|
2022-04-19 20:42:52 +02:00
|
|
|
}
|
|
|
|
}
|
2022-05-24 10:54:36 +02:00
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
const using = cls.getFromContext(ContextKeys.TENANCY_IN_USE)
|
|
|
|
if (using && cls.getFromContext(ContextKeys.TENANT_ID) === tenantId) {
|
|
|
|
// the tenant id of the current context matches the one we want to use
|
|
|
|
// don't create a new context, just use the existing one
|
|
|
|
cls.setOnContext(ContextKeys.TENANCY_IN_USE, using + 1)
|
2022-05-24 21:01:13 +02:00
|
|
|
return internal({ existing: true })
|
2022-04-19 20:42:52 +02:00
|
|
|
} else {
|
|
|
|
return cls.run(async () => {
|
2022-07-13 14:22:21 +02:00
|
|
|
cls.setOnContext(ContextKeys.TENANCY_IN_USE, 1)
|
2022-05-24 21:01:13 +02:00
|
|
|
return internal()
|
2022-04-19 20:42:52 +02:00
|
|
|
})
|
|
|
|
}
|
2021-08-05 10:59:08 +02:00
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const doInAppContext = (appId: string, task: any) => {
|
2022-03-24 14:04:49 +01:00
|
|
|
if (!appId) {
|
|
|
|
throw new Error("appId is required")
|
|
|
|
}
|
2022-04-21 00:10:39 +02:00
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
const identity = getIdentity()
|
2022-05-24 21:01:13 +02:00
|
|
|
|
2022-04-19 20:42:52 +02:00
|
|
|
// the internal function is so that we can re-use an existing
|
|
|
|
// context - don't want to close DB on a parent context
|
2022-05-28 22:38:22 +02:00
|
|
|
async function internal(opts = { existing: false }) {
|
2022-03-24 14:04:49 +01:00
|
|
|
// set the app tenant id
|
2022-04-19 20:42:52 +02:00
|
|
|
if (!opts.existing) {
|
|
|
|
setAppTenantId(appId)
|
|
|
|
}
|
2022-01-28 01:05:39 +01:00
|
|
|
// set the app ID
|
|
|
|
cls.setOnContext(ContextKeys.APP_ID, appId)
|
2022-07-13 14:22:21 +02:00
|
|
|
setAppTenantId(appId)
|
|
|
|
|
2022-05-28 22:38:22 +02:00
|
|
|
// preserve the identity
|
2022-07-13 14:22:21 +02:00
|
|
|
if (identity) {
|
|
|
|
setIdentity(identity)
|
|
|
|
}
|
2022-04-20 18:33:42 +02:00
|
|
|
try {
|
|
|
|
// invoke the task
|
|
|
|
return await task()
|
|
|
|
} finally {
|
2022-07-13 14:22:21 +02:00
|
|
|
const using = cls.getFromContext(ContextKeys.APP_IN_USE)
|
2022-04-21 00:10:39 +02:00
|
|
|
if (!using || using <= 1) {
|
2022-04-20 18:33:42 +02:00
|
|
|
await closeAppDBs()
|
2022-07-13 14:22:21 +02:00
|
|
|
await closeTenancy()
|
2022-04-21 00:10:39 +02:00
|
|
|
} else {
|
|
|
|
cls.setOnContext(using - 1)
|
2022-04-20 18:33:42 +02:00
|
|
|
}
|
2022-04-19 20:42:52 +02:00
|
|
|
}
|
|
|
|
}
|
2022-07-13 14:22:21 +02:00
|
|
|
const using = cls.getFromContext(ContextKeys.APP_IN_USE)
|
|
|
|
if (using && cls.getFromContext(ContextKeys.APP_ID) === appId) {
|
|
|
|
cls.setOnContext(ContextKeys.APP_IN_USE, using + 1)
|
2022-05-24 21:01:13 +02:00
|
|
|
return internal({ existing: true })
|
2022-04-19 20:42:52 +02:00
|
|
|
} else {
|
|
|
|
return cls.run(async () => {
|
2022-07-13 14:22:21 +02:00
|
|
|
cls.setOnContext(ContextKeys.APP_IN_USE, 1)
|
2022-05-24 21:01:13 +02:00
|
|
|
return internal()
|
2022-04-19 20:42:52 +02:00
|
|
|
})
|
|
|
|
}
|
2022-01-28 01:05:39 +01:00
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const doInIdentityContext = (identity: IdentityContext, task: any) => {
|
2022-05-28 22:38:22 +02:00
|
|
|
if (!identity) {
|
|
|
|
throw new Error("identity is required")
|
2022-05-24 21:01:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
async function internal(opts = { existing: false }) {
|
|
|
|
if (!opts.existing) {
|
2022-05-28 22:38:22 +02:00
|
|
|
cls.setOnContext(ContextKeys.IDENTITY, identity)
|
|
|
|
// set the tenant so that doInTenant will preserve identity
|
|
|
|
if (identity.tenantId) {
|
2022-07-13 14:22:21 +02:00
|
|
|
updateTenantId(identity.tenantId)
|
2022-05-24 21:01:13 +02:00
|
|
|
}
|
2022-05-24 10:54:36 +02:00
|
|
|
}
|
2022-05-24 21:01:13 +02:00
|
|
|
|
|
|
|
try {
|
|
|
|
// invoke the task
|
|
|
|
return await task()
|
|
|
|
} finally {
|
2022-07-13 14:22:21 +02:00
|
|
|
const using = cls.getFromContext(ContextKeys.IDENTITY_IN_USE)
|
2022-05-24 21:01:13 +02:00
|
|
|
if (!using || using <= 1) {
|
2022-07-13 14:22:21 +02:00
|
|
|
setIdentity(null)
|
|
|
|
await closeTenancy()
|
2022-05-24 21:01:13 +02:00
|
|
|
} else {
|
|
|
|
cls.setOnContext(using - 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-28 22:38:22 +02:00
|
|
|
const existing = cls.getFromContext(ContextKeys.IDENTITY)
|
2022-07-13 14:22:21 +02:00
|
|
|
const using = cls.getFromContext(ContextKeys.IDENTITY_IN_USE)
|
2022-05-28 22:38:22 +02:00
|
|
|
if (using && existing && existing._id === identity._id) {
|
2022-07-13 14:22:21 +02:00
|
|
|
cls.setOnContext(ContextKeys.IDENTITY_IN_USE, using + 1)
|
2022-05-24 21:01:13 +02:00
|
|
|
return internal({ existing: true })
|
|
|
|
} else {
|
|
|
|
return cls.run(async () => {
|
2022-07-13 14:22:21 +02:00
|
|
|
cls.setOnContext(ContextKeys.IDENTITY_IN_USE, 1)
|
2022-05-24 21:01:13 +02:00
|
|
|
return internal({ existing: false })
|
|
|
|
})
|
|
|
|
}
|
2022-05-23 23:14:44 +02:00
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
const setIdentity = (identity: IdentityContext | null) => {
|
2022-05-28 22:38:22 +02:00
|
|
|
cls.setOnContext(ContextKeys.IDENTITY, identity)
|
2022-05-24 10:54:36 +02:00
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const getIdentity = (): IdentityContext | undefined => {
|
2022-05-23 23:14:44 +02:00
|
|
|
try {
|
2022-05-28 22:38:22 +02:00
|
|
|
return cls.getFromContext(ContextKeys.IDENTITY)
|
2022-05-23 23:14:44 +02:00
|
|
|
} catch (e) {
|
2022-05-28 22:38:22 +02:00
|
|
|
// do nothing - identity is not in context
|
2022-05-23 23:14:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const updateTenantId = (tenantId: string | null) => {
|
2022-01-27 19:18:31 +01:00
|
|
|
cls.setOnContext(ContextKeys.TENANT_ID, tenantId)
|
2022-05-24 21:01:13 +02:00
|
|
|
if (env.USE_COUCH) {
|
2022-07-13 14:22:21 +02:00
|
|
|
setGlobalDB(tenantId)
|
2022-05-24 21:01:13 +02:00
|
|
|
}
|
2022-01-27 19:18:31 +01:00
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const updateAppId = async (appId: string) => {
|
2022-01-27 19:18:31 +01:00
|
|
|
try {
|
2022-04-20 18:33:42 +02:00
|
|
|
// have to close first, before removing the databases from context
|
2022-04-21 15:56:14 +02:00
|
|
|
await closeAppDBs()
|
2022-01-27 19:18:31 +01:00
|
|
|
cls.setOnContext(ContextKeys.APP_ID, appId)
|
|
|
|
} catch (err) {
|
|
|
|
if (env.isTest()) {
|
|
|
|
TEST_APP_ID = appId
|
|
|
|
} else {
|
|
|
|
throw err
|
|
|
|
}
|
|
|
|
}
|
2021-08-05 10:59:08 +02:00
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const setGlobalDB = (tenantId: string | null) => {
|
2022-04-19 20:42:52 +02:00
|
|
|
const dbName = baseGlobalDBName(tenantId)
|
2022-07-13 14:22:21 +02:00
|
|
|
openTenancyCount++
|
2022-04-19 20:42:52 +02:00
|
|
|
const db = dangerousGetDB(dbName)
|
|
|
|
cls.setOnContext(ContextKeys.GLOBAL_DB, db)
|
|
|
|
return db
|
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const getGlobalDB = () => {
|
2022-04-19 20:42:52 +02:00
|
|
|
const db = cls.getFromContext(ContextKeys.GLOBAL_DB)
|
|
|
|
if (!db) {
|
|
|
|
throw new Error("Global DB not found")
|
|
|
|
}
|
|
|
|
return db
|
2021-08-05 10:59:08 +02:00
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const isTenantIdSet = () => {
|
2022-01-27 19:18:31 +01:00
|
|
|
const tenantId = cls.getFromContext(ContextKeys.TENANT_ID)
|
2021-08-05 10:59:08 +02:00
|
|
|
return !!tenantId
|
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const getTenantId = () => {
|
|
|
|
if (!isMultiTenant()) {
|
|
|
|
return DEFAULT_TENANT_ID
|
2021-08-05 10:59:08 +02:00
|
|
|
}
|
2022-01-27 19:18:31 +01:00
|
|
|
const tenantId = cls.getFromContext(ContextKeys.TENANT_ID)
|
2021-08-05 10:59:08 +02:00
|
|
|
if (!tenantId) {
|
2022-02-18 12:18:59 +01:00
|
|
|
throw new Error("Tenant id not found")
|
2021-08-05 10:59:08 +02:00
|
|
|
}
|
|
|
|
return tenantId
|
|
|
|
}
|
2022-01-27 19:18:31 +01:00
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
export const getAppId = () => {
|
2022-01-27 19:18:31 +01:00
|
|
|
const foundId = cls.getFromContext(ContextKeys.APP_ID)
|
|
|
|
if (!foundId && env.isTest() && TEST_APP_ID) {
|
|
|
|
return TEST_APP_ID
|
|
|
|
} else {
|
|
|
|
return foundId
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
function getContextDB(key: string, opts: any) {
|
2022-01-27 19:18:31 +01:00
|
|
|
const dbOptsKey = `${key}${ContextKeys.DB_OPTS}`
|
|
|
|
let storedOpts = cls.getFromContext(dbOptsKey)
|
|
|
|
let db = cls.getFromContext(key)
|
|
|
|
if (db && isEqual(opts, storedOpts)) {
|
|
|
|
return db
|
|
|
|
}
|
2022-04-19 15:38:09 +02:00
|
|
|
|
2022-07-13 14:22:21 +02:00
|
|
|
const appId = getAppId()
|
2022-01-27 19:18:31 +01:00
|
|
|
let toUseAppId
|
2022-04-19 15:56:56 +02:00
|
|
|
|
2022-01-27 19:18:31 +01:00
|
|
|
switch (key) {
|
|
|
|
case ContextKeys.CURRENT_DB:
|
|
|
|
toUseAppId = appId
|
|
|
|
break
|
|
|
|
case ContextKeys.PROD_DB:
|
2022-01-31 18:42:51 +01:00
|
|
|
toUseAppId = getProdAppID(appId)
|
2022-01-27 19:18:31 +01:00
|
|
|
break
|
|
|
|
case ContextKeys.DEV_DB:
|
|
|
|
toUseAppId = getDevelopmentAppID(appId)
|
|
|
|
break
|
|
|
|
}
|
2022-07-13 14:22:21 +02:00
|
|
|
openAppCount++
|
2022-04-19 20:42:52 +02:00
|
|
|
db = dangerousGetDB(toUseAppId, opts)
|
2022-01-27 19:18:31 +01:00
|
|
|
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.
|
|
|
|
*/
|
2022-07-13 14:22:21 +02:00
|
|
|
export const getAppDB = (opts?: any) => {
|
2022-03-29 17:03:44 +02:00
|
|
|
return getContextDB(ContextKeys.CURRENT_DB, opts)
|
2022-01-27 19:18:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This specifically gets the prod app ID, if the request
|
|
|
|
* contained a development app ID, this will open the prod one.
|
|
|
|
*/
|
2022-07-13 14:22:21 +02:00
|
|
|
export const getProdAppDB = (opts?: any) => {
|
2022-03-29 17:03:44 +02:00
|
|
|
return getContextDB(ContextKeys.PROD_DB, opts)
|
2022-01-27 19:18:31 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This specifically gets the dev app ID, if the request
|
|
|
|
* contained a prod app ID, this will open the dev one.
|
|
|
|
*/
|
2022-07-13 14:22:21 +02:00
|
|
|
export const getDevAppDB = (opts?: any) => {
|
2022-03-29 17:03:44 +02:00
|
|
|
return getContextDB(ContextKeys.DEV_DB, opts)
|
2022-01-27 19:18:31 +01:00
|
|
|
}
|