Merge remote-tracking branch 'origin/develop' into feature/auto-select-dataprovider-source
This commit is contained in:
commit
764c2bea67
|
@ -124,12 +124,21 @@ http {
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/backups/ {
|
location /api/backups/ {
|
||||||
|
# calls to export apps are limited
|
||||||
|
limit_req zone=ratelimit burst=20 nodelay;
|
||||||
|
|
||||||
|
# 1800s timeout for app export requests
|
||||||
proxy_read_timeout 1800s;
|
proxy_read_timeout 1800s;
|
||||||
proxy_connect_timeout 1800s;
|
proxy_connect_timeout 1800s;
|
||||||
proxy_send_timeout 1800s;
|
proxy_send_timeout 1800s;
|
||||||
proxy_pass http://app-service;
|
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
proxy_pass http://$apps:4002;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
|
|
|
@ -43,6 +43,24 @@ server {
|
||||||
rewrite ^/worker/(.*)$ /$1 break;
|
rewrite ^/worker/(.*)$ /$1 break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /api/backups/ {
|
||||||
|
# calls to export apps are limited
|
||||||
|
limit_req zone=ratelimit burst=20 nodelay;
|
||||||
|
|
||||||
|
# 1800s timeout for app export requests
|
||||||
|
proxy_read_timeout 1800s;
|
||||||
|
proxy_connect_timeout 1800s;
|
||||||
|
proxy_send_timeout 1800s;
|
||||||
|
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
|
||||||
|
proxy_pass http://127.0.0.1:4001;
|
||||||
|
}
|
||||||
|
|
||||||
location /api/ {
|
location /api/ {
|
||||||
# calls to the API are rate limited with bursting
|
# calls to the API are rate limited with bursting
|
||||||
limit_req zone=ratelimit burst=20 nodelay;
|
limit_req zone=ratelimit burst=20 nodelay;
|
||||||
|
|
|
@ -27,12 +27,14 @@ if [[ "${TARGETBUILD}" = "aas" ]]; then
|
||||||
else
|
else
|
||||||
DATA_DIR=${DATA_DIR:-/data}
|
DATA_DIR=${DATA_DIR:-/data}
|
||||||
fi
|
fi
|
||||||
|
mkdir -p ${DATA_DIR}
|
||||||
# Mount NFS or GCP Filestore if env vars exist for it
|
# Mount NFS or GCP Filestore if env vars exist for it
|
||||||
if [[ -z ${FILESHARE_IP} && -z ${FILESHARE_NAME} ]]; then
|
if [[ ! -z ${FILESHARE_IP} && ! -z ${FILESHARE_NAME} ]]; then
|
||||||
|
echo "Mounting NFS share"
|
||||||
|
apt update && apt install -y nfs-common nfs-kernel-server
|
||||||
echo "Mount file share ${FILESHARE_IP}:/${FILESHARE_NAME} to ${DATA_DIR}"
|
echo "Mount file share ${FILESHARE_IP}:/${FILESHARE_NAME} to ${DATA_DIR}"
|
||||||
mount -o nolock ${FILESHARE_IP}:/${FILESHARE_NAME} ${DATA_DIR}
|
mount -o nolock ${FILESHARE_IP}:/${FILESHARE_NAME} ${DATA_DIR}
|
||||||
echo "Mounting completed."
|
echo "Mounting result: $?"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f "${DATA_DIR}/.env" ]; then
|
if [ -f "${DATA_DIR}/.env" ]; then
|
||||||
|
@ -74,9 +76,9 @@ mkdir -p ${DATA_DIR}/couch/{dbs,views}
|
||||||
mkdir -p ${DATA_DIR}/minio
|
mkdir -p ${DATA_DIR}/minio
|
||||||
mkdir -p ${DATA_DIR}/search
|
mkdir -p ${DATA_DIR}/search
|
||||||
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
chown -R couchdb:couchdb ${DATA_DIR}/couch
|
||||||
redis-server --requirepass $REDIS_PASSWORD &
|
redis-server --requirepass $REDIS_PASSWORD > /dev/stdout 2>&1 &
|
||||||
/opt/clouseau/bin/clouseau &
|
/opt/clouseau/bin/clouseau > /dev/stdout 2>&1 &
|
||||||
/minio/minio server ${DATA_DIR}/minio &
|
/minio/minio server ${DATA_DIR}/minio > /dev/stdout 2>&1 &
|
||||||
/docker-entrypoint.sh /opt/couchdb/bin/couchdb &
|
/docker-entrypoint.sh /opt/couchdb/bin/couchdb &
|
||||||
/etc/init.d/nginx restart
|
/etc/init.d/nginx restart
|
||||||
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
||||||
|
@ -85,16 +87,18 @@ if [[ ! -z "${CUSTOM_DOMAIN}" ]]; then
|
||||||
chmod +x /etc/cron.d/certificate-renew
|
chmod +x /etc/cron.d/certificate-renew
|
||||||
# Request the certbot certificate
|
# Request the certbot certificate
|
||||||
/app/letsencrypt/certificate-request.sh ${CUSTOM_DOMAIN}
|
/app/letsencrypt/certificate-request.sh ${CUSTOM_DOMAIN}
|
||||||
|
/etc/init.d/nginx restart
|
||||||
fi
|
fi
|
||||||
|
|
||||||
/etc/init.d/nginx restart
|
|
||||||
pushd app
|
pushd app
|
||||||
pm2 start --name app "yarn run:docker"
|
pm2 start -l /dev/stdout --name app "yarn run:docker"
|
||||||
popd
|
popd
|
||||||
pushd worker
|
pushd worker
|
||||||
pm2 start --name worker "yarn run:docker"
|
pm2 start -l /dev/stdout --name worker "yarn run:docker"
|
||||||
popd
|
popd
|
||||||
sleep 10
|
sleep 10
|
||||||
|
echo "curl to couchdb endpoints"
|
||||||
curl -X PUT ${COUCH_DB_URL}/_users
|
curl -X PUT ${COUCH_DB_URL}/_users
|
||||||
curl -X PUT ${COUCH_DB_URL}/_replicator
|
curl -X PUT ${COUCH_DB_URL}/_replicator
|
||||||
|
echo "end of runner.sh, sleeping ..."
|
||||||
sleep infinity
|
sleep infinity
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "2.1.22-alpha.6",
|
"version": "2.1.32-alpha.1",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"packages": [
|
"packages": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|
|
@ -1,7 +1 @@
|
||||||
module.exports = {
|
module.exports = require("./src/db")
|
||||||
...require("./src/db/utils"),
|
|
||||||
...require("./src/db/constants"),
|
|
||||||
...require("./src/db"),
|
|
||||||
...require("./src/db/views"),
|
|
||||||
...require("./src/db/pouch"),
|
|
||||||
}
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ if (!process.env.CI) {
|
||||||
// use sources when not in CI
|
// use sources when not in CI
|
||||||
config.moduleNameMapper = {
|
config.moduleNameMapper = {
|
||||||
"@budibase/types": "<rootDir>/../types/src",
|
"@budibase/types": "<rootDir>/../types/src",
|
||||||
|
"^axios.*$": "<rootDir>/node_modules/axios/lib/axios.js",
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log("Running tests with compiled dependency sources")
|
console.log("Running tests with compiled dependency sources")
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/backend-core",
|
"name": "@budibase/backend-core",
|
||||||
"version": "2.1.22-alpha.6",
|
"version": "2.1.32-alpha.1",
|
||||||
"description": "Budibase backend core libraries used in server and worker",
|
"description": "Budibase backend core libraries used in server and worker",
|
||||||
"main": "dist/src/index.js",
|
"main": "dist/src/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
"test:watch": "jest --watchAll"
|
"test:watch": "jest --watchAll"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/types": "2.1.22-alpha.6",
|
"@budibase/types": "2.1.32-alpha.1",
|
||||||
"@shopify/jest-koa-mocks": "5.0.1",
|
"@shopify/jest-koa-mocks": "5.0.1",
|
||||||
"@techpass/passport-openidconnect": "0.3.2",
|
"@techpass/passport-openidconnect": "0.3.2",
|
||||||
"aws-sdk": "2.1030.0",
|
"aws-sdk": "2.1030.0",
|
||||||
|
@ -35,6 +35,7 @@
|
||||||
"koa-passport": "4.1.4",
|
"koa-passport": "4.1.4",
|
||||||
"lodash": "4.17.21",
|
"lodash": "4.17.21",
|
||||||
"lodash.isarguments": "3.1.0",
|
"lodash.isarguments": "3.1.0",
|
||||||
|
"nano": "^10.1.0",
|
||||||
"node-fetch": "2.6.7",
|
"node-fetch": "2.6.7",
|
||||||
"passport-google-auth": "1.0.2",
|
"passport-google-auth": "1.0.2",
|
||||||
"passport-google-oauth": "2.0.0",
|
"passport-google-oauth": "2.0.0",
|
||||||
|
|
|
@ -3,7 +3,7 @@ const LocalStrategy = require("passport-local").Strategy
|
||||||
const JwtStrategy = require("passport-jwt").Strategy
|
const JwtStrategy = require("passport-jwt").Strategy
|
||||||
import { getGlobalDB } from "./tenancy"
|
import { getGlobalDB } from "./tenancy"
|
||||||
const refresh = require("passport-oauth2-refresh")
|
const refresh = require("passport-oauth2-refresh")
|
||||||
import { Configs } from "./constants"
|
import { Config } from "./constants"
|
||||||
import { getScopedConfig } from "./db/utils"
|
import { getScopedConfig } from "./db/utils"
|
||||||
import {
|
import {
|
||||||
jwt,
|
jwt,
|
||||||
|
@ -76,7 +76,7 @@ async function refreshOIDCAccessToken(
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
refresh.requestNewAccessToken(
|
refresh.requestNewAccessToken(
|
||||||
Configs.OIDC,
|
Config.OIDC,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
(err: any, accessToken: string, refreshToken: any, params: any) => {
|
(err: any, accessToken: string, refreshToken: any, params: any) => {
|
||||||
resolve({ err, accessToken, refreshToken, params })
|
resolve({ err, accessToken, refreshToken, params })
|
||||||
|
@ -106,7 +106,7 @@ async function refreshGoogleAccessToken(
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
refresh.requestNewAccessToken(
|
refresh.requestNewAccessToken(
|
||||||
Configs.GOOGLE,
|
Config.GOOGLE,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
(err: any, accessToken: string, refreshToken: string, params: any) => {
|
(err: any, accessToken: string, refreshToken: string, params: any) => {
|
||||||
resolve({ err, accessToken, refreshToken, params })
|
resolve({ err, accessToken, refreshToken, params })
|
||||||
|
@ -129,7 +129,7 @@ async function refreshOAuthToken(
|
||||||
|
|
||||||
let chosenConfig = {}
|
let chosenConfig = {}
|
||||||
let refreshResponse
|
let refreshResponse
|
||||||
if (configType === Configs.OIDC) {
|
if (configType === Config.OIDC) {
|
||||||
// configId - retrieved from cookie.
|
// configId - retrieved from cookie.
|
||||||
chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
|
chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
|
||||||
if (!chosenConfig) {
|
if (!chosenConfig) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require("../../../tests")
|
require("../../../tests")
|
||||||
const { Writethrough } = require("../writethrough")
|
const { Writethrough } = require("../writethrough")
|
||||||
const { dangerousGetDB } = require("../../db")
|
const { getDB } = require("../../db")
|
||||||
const tk = require("timekeeper")
|
const tk = require("timekeeper")
|
||||||
|
|
||||||
const START_DATE = Date.now()
|
const START_DATE = Date.now()
|
||||||
|
@ -8,8 +8,8 @@ tk.freeze(START_DATE)
|
||||||
|
|
||||||
const DELAY = 5000
|
const DELAY = 5000
|
||||||
|
|
||||||
const db = dangerousGetDB("test")
|
const db = getDB("test")
|
||||||
const db2 = dangerousGetDB("test2")
|
const db2 = getDB("test2")
|
||||||
const writethrough = new Writethrough(db, DELAY), writethrough2 = new Writethrough(db2, DELAY)
|
const writethrough = new Writethrough(db, DELAY), writethrough2 = new Writethrough(db2, DELAY)
|
||||||
|
|
||||||
describe("writethrough", () => {
|
describe("writethrough", () => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import BaseCache from "./base"
|
import BaseCache from "./base"
|
||||||
import { getWritethroughClient } from "../redis/init"
|
import { getWritethroughClient } from "../redis/init"
|
||||||
import { logWarn } from "../logging"
|
import { logWarn } from "../logging"
|
||||||
import PouchDB from "pouchdb"
|
import { Database } from "@budibase/types"
|
||||||
|
|
||||||
const DEFAULT_WRITE_RATE_MS = 10000
|
const DEFAULT_WRITE_RATE_MS = 10000
|
||||||
let CACHE: BaseCache | null = null
|
let CACHE: BaseCache | null = null
|
||||||
|
@ -19,7 +19,7 @@ async function getCache() {
|
||||||
return CACHE
|
return CACHE
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeCacheKey(db: PouchDB.Database, key: string) {
|
function makeCacheKey(db: Database, key: string) {
|
||||||
return db.name + key
|
return db.name + key
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ function makeCacheItem(doc: any, lastWrite: number | null = null): CacheItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function put(
|
export async function put(
|
||||||
db: PouchDB.Database,
|
db: Database,
|
||||||
doc: any,
|
doc: any,
|
||||||
writeRateMs: number = DEFAULT_WRITE_RATE_MS
|
writeRateMs: number = DEFAULT_WRITE_RATE_MS
|
||||||
) {
|
) {
|
||||||
|
@ -64,7 +64,7 @@ export async function put(
|
||||||
return { ok: true, id: output._id, rev: output._rev }
|
return { ok: true, id: output._id, rev: output._rev }
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function get(db: PouchDB.Database, id: string): Promise<any> {
|
export async function get(db: Database, id: string): Promise<any> {
|
||||||
const cache = await getCache()
|
const cache = await getCache()
|
||||||
const cacheKey = makeCacheKey(db, id)
|
const cacheKey = makeCacheKey(db, id)
|
||||||
let cacheItem: CacheItem = await cache.get(cacheKey)
|
let cacheItem: CacheItem = await cache.get(cacheKey)
|
||||||
|
@ -77,7 +77,7 @@ export async function get(db: PouchDB.Database, id: string): Promise<any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function remove(
|
export async function remove(
|
||||||
db: PouchDB.Database,
|
db: Database,
|
||||||
docOrId: any,
|
docOrId: any,
|
||||||
rev?: any
|
rev?: any
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
@ -95,13 +95,10 @@ export async function remove(
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Writethrough {
|
export class Writethrough {
|
||||||
db: PouchDB.Database
|
db: Database
|
||||||
writeRateMs: number
|
writeRateMs: number
|
||||||
|
|
||||||
constructor(
|
constructor(db: Database, writeRateMs: number = DEFAULT_WRITE_RATE_MS) {
|
||||||
db: PouchDB.Database,
|
|
||||||
writeRateMs: number = DEFAULT_WRITE_RATE_MS
|
|
||||||
) {
|
|
||||||
this.db = db
|
this.db = db
|
||||||
this.writeRateMs = writeRateMs
|
this.writeRateMs = writeRateMs
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import API from "./api"
|
import API from "./api"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { Headers } from "../constants"
|
import { Header } from "../constants"
|
||||||
import { CloudAccount } from "@budibase/types"
|
import { CloudAccount } from "@budibase/types"
|
||||||
|
|
||||||
const api = new API(env.ACCOUNT_PORTAL_URL)
|
const api = new API(env.ACCOUNT_PORTAL_URL)
|
||||||
|
@ -14,7 +14,7 @@ export const getAccount = async (
|
||||||
const response = await api.post(`/api/accounts/search`, {
|
const response = await api.post(`/api/accounts/search`, {
|
||||||
body: payload,
|
body: payload,
|
||||||
headers: {
|
headers: {
|
||||||
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
[Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export const getAccountByTenantId = async (
|
||||||
const response = await api.post(`/api/accounts/search`, {
|
const response = await api.post(`/api/accounts/search`, {
|
||||||
body: payload,
|
body: payload,
|
||||||
headers: {
|
headers: {
|
||||||
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
[Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ export const getAccountByTenantId = async (
|
||||||
export const getStatus = async () => {
|
export const getStatus = async () => {
|
||||||
const response = await api.get(`/api/status`, {
|
const response = await api.get(`/api/status`, {
|
||||||
headers: {
|
headers: {
|
||||||
[Headers.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
[Header.API_KEY]: env.ACCOUNT_PORTAL_API_KEY,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const json = await response.json()
|
const json = await response.json()
|
||||||
|
|
|
@ -1,650 +0,0 @@
|
||||||
const util = require("util")
|
|
||||||
const assert = require("assert")
|
|
||||||
const wrapEmitter = require("emitter-listener")
|
|
||||||
const async_hooks = require("async_hooks")
|
|
||||||
|
|
||||||
const CONTEXTS_SYMBOL = "cls@contexts"
|
|
||||||
const ERROR_SYMBOL = "error@context"
|
|
||||||
|
|
||||||
const DEBUG_CLS_HOOKED = process.env.DEBUG_CLS_HOOKED
|
|
||||||
|
|
||||||
let currentUid = -1
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getNamespace: getNamespace,
|
|
||||||
createNamespace: createNamespace,
|
|
||||||
destroyNamespace: destroyNamespace,
|
|
||||||
reset: reset,
|
|
||||||
ERROR_SYMBOL: ERROR_SYMBOL,
|
|
||||||
}
|
|
||||||
|
|
||||||
function Namespace(name) {
|
|
||||||
this.name = name
|
|
||||||
// changed in 2.7: no default context
|
|
||||||
this.active = null
|
|
||||||
this._set = []
|
|
||||||
this.id = null
|
|
||||||
this._contexts = new Map()
|
|
||||||
this._indent = 0
|
|
||||||
this._hook = null
|
|
||||||
}
|
|
||||||
|
|
||||||
Namespace.prototype.set = function set(key, value) {
|
|
||||||
if (!this.active) {
|
|
||||||
throw new Error(
|
|
||||||
"No context available. ns.run() or ns.bind() must be called first."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.active[key] = value
|
|
||||||
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
|
||||||
debug2(
|
|
||||||
indentStr +
|
|
||||||
"CONTEXT-SET KEY:" +
|
|
||||||
key +
|
|
||||||
"=" +
|
|
||||||
value +
|
|
||||||
" in ns:" +
|
|
||||||
this.name +
|
|
||||||
" currentUid:" +
|
|
||||||
currentUid +
|
|
||||||
" active:" +
|
|
||||||
util.inspect(this.active, { showHidden: true, depth: 2, colors: true })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
Namespace.prototype.get = function get(key) {
|
|
||||||
if (!this.active) {
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const asyncHooksCurrentId = async_hooks.currentId()
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}CONTEXT-GETTING KEY NO ACTIVE NS: (${this.name}) ${key}=undefined currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${this._set.length}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
|
||||||
debug2(
|
|
||||||
indentStr +
|
|
||||||
"CONTEXT-GETTING KEY:" +
|
|
||||||
key +
|
|
||||||
"=" +
|
|
||||||
this.active[key] +
|
|
||||||
" (" +
|
|
||||||
this.name +
|
|
||||||
") currentUid:" +
|
|
||||||
currentUid +
|
|
||||||
" active:" +
|
|
||||||
util.inspect(this.active, { showHidden: true, depth: 2, colors: true })
|
|
||||||
)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}CONTEXT-GETTING KEY: (${this.name}) ${key}=${
|
|
||||||
this.active[key]
|
|
||||||
} currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${
|
|
||||||
this._set.length
|
|
||||||
} active:${util.inspect(this.active)}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return this.active[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
Namespace.prototype.createContext = function createContext() {
|
|
||||||
// Prototype inherit existing context if created a new child context within existing context.
|
|
||||||
let context = Object.create(this.active ? this.active : Object.prototype)
|
|
||||||
context._ns_name = this.name
|
|
||||||
context.id = currentUid
|
|
||||||
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}CONTEXT-CREATED Context: (${
|
|
||||||
this.name
|
|
||||||
}) currentUid:${currentUid} asyncHooksCurrentId:${asyncHooksCurrentId} triggerId:${triggerId} len:${
|
|
||||||
this._set.length
|
|
||||||
} context:${util.inspect(context, {
|
|
||||||
showHidden: true,
|
|
||||||
depth: 2,
|
|
||||||
colors: true,
|
|
||||||
})}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
|
|
||||||
Namespace.prototype.run = function run(fn) {
|
|
||||||
let context = this.createContext()
|
|
||||||
this.enter(context)
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
|
||||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}CONTEXT-RUN BEGIN: (${
|
|
||||||
this.name
|
|
||||||
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
|
||||||
this._set.length
|
|
||||||
} context:${util.inspect(context)}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fn(context)
|
|
||||||
return context
|
|
||||||
} catch (exception) {
|
|
||||||
if (exception) {
|
|
||||||
exception[ERROR_SYMBOL] = context
|
|
||||||
}
|
|
||||||
throw exception
|
|
||||||
} finally {
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
|
||||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}CONTEXT-RUN END: (${
|
|
||||||
this.name
|
|
||||||
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
|
||||||
this._set.length
|
|
||||||
} ${util.inspect(context)}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
this.exit(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Namespace.prototype.runAndReturn = function runAndReturn(fn) {
|
|
||||||
let value
|
|
||||||
this.run(function (context) {
|
|
||||||
value = fn(context)
|
|
||||||
})
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses global Promise and assumes Promise is cls friendly or wrapped already.
|
|
||||||
* @param {function} fn
|
|
||||||
* @returns {*}
|
|
||||||
*/
|
|
||||||
Namespace.prototype.runPromise = function runPromise(fn) {
|
|
||||||
let context = this.createContext()
|
|
||||||
this.enter(context)
|
|
||||||
|
|
||||||
let promise = fn(context)
|
|
||||||
if (!promise || !promise.then || !promise.catch) {
|
|
||||||
throw new Error("fn must return a promise.")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
debug2(
|
|
||||||
"CONTEXT-runPromise BEFORE: (" +
|
|
||||||
this.name +
|
|
||||||
") currentUid:" +
|
|
||||||
currentUid +
|
|
||||||
" len:" +
|
|
||||||
this._set.length +
|
|
||||||
" " +
|
|
||||||
util.inspect(context)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise
|
|
||||||
.then(result => {
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
debug2(
|
|
||||||
"CONTEXT-runPromise AFTER then: (" +
|
|
||||||
this.name +
|
|
||||||
") currentUid:" +
|
|
||||||
currentUid +
|
|
||||||
" len:" +
|
|
||||||
this._set.length +
|
|
||||||
" " +
|
|
||||||
util.inspect(context)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
this.exit(context)
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
err[ERROR_SYMBOL] = context
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
debug2(
|
|
||||||
"CONTEXT-runPromise AFTER catch: (" +
|
|
||||||
this.name +
|
|
||||||
") currentUid:" +
|
|
||||||
currentUid +
|
|
||||||
" len:" +
|
|
||||||
this._set.length +
|
|
||||||
" " +
|
|
||||||
util.inspect(context)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
this.exit(context)
|
|
||||||
throw err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Namespace.prototype.bind = function bindFactory(fn, context) {
|
|
||||||
if (!context) {
|
|
||||||
if (!this.active) {
|
|
||||||
context = this.createContext()
|
|
||||||
} else {
|
|
||||||
context = this.active
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let self = this
|
|
||||||
return function clsBind() {
|
|
||||||
self.enter(context)
|
|
||||||
try {
|
|
||||||
return fn.apply(this, arguments)
|
|
||||||
} catch (exception) {
|
|
||||||
if (exception) {
|
|
||||||
exception[ERROR_SYMBOL] = context
|
|
||||||
}
|
|
||||||
throw exception
|
|
||||||
} finally {
|
|
||||||
self.exit(context)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Namespace.prototype.enter = function enter(context) {
|
|
||||||
assert.ok(context, "context must be provided for entering")
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}CONTEXT-ENTER: (${
|
|
||||||
this.name
|
|
||||||
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
|
||||||
this._set.length
|
|
||||||
} ${util.inspect(context)}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this._set.push(this.active)
|
|
||||||
this.active = context
|
|
||||||
}
|
|
||||||
|
|
||||||
Namespace.prototype.exit = function exit(context) {
|
|
||||||
assert.ok(context, "context must be provided for exiting")
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const asyncHooksCurrentId = async_hooks.executionAsyncId()
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
const indentStr = " ".repeat(this._indent < 0 ? 0 : this._indent)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}CONTEXT-EXIT: (${
|
|
||||||
this.name
|
|
||||||
}) currentUid:${currentUid} triggerId:${triggerId} asyncHooksCurrentId:${asyncHooksCurrentId} len:${
|
|
||||||
this._set.length
|
|
||||||
} ${util.inspect(context)}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fast path for most exits that are at the top of the stack
|
|
||||||
if (this.active === context) {
|
|
||||||
assert.ok(this._set.length, "can't remove top context")
|
|
||||||
this.active = this._set.pop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fast search in the stack using lastIndexOf
|
|
||||||
let index = this._set.lastIndexOf(context)
|
|
||||||
|
|
||||||
if (index < 0) {
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
debug2(
|
|
||||||
"??ERROR?? context exiting but not entered - ignoring: " +
|
|
||||||
util.inspect(context)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
assert.ok(
|
|
||||||
index >= 0,
|
|
||||||
"context not currently entered; can't exit. \n" +
|
|
||||||
util.inspect(this) +
|
|
||||||
"\n" +
|
|
||||||
util.inspect(context)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
assert.ok(index, "can't remove top context")
|
|
||||||
this._set.splice(index, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Namespace.prototype.bindEmitter = function bindEmitter(emitter) {
|
|
||||||
assert.ok(
|
|
||||||
emitter.on && emitter.addListener && emitter.emit,
|
|
||||||
"can only bind real EEs"
|
|
||||||
)
|
|
||||||
|
|
||||||
let namespace = this
|
|
||||||
let thisSymbol = "context@" + this.name
|
|
||||||
|
|
||||||
// Capture the context active at the time the emitter is bound.
|
|
||||||
function attach(listener) {
|
|
||||||
if (!listener) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!listener[CONTEXTS_SYMBOL]) {
|
|
||||||
listener[CONTEXTS_SYMBOL] = Object.create(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
listener[CONTEXTS_SYMBOL][thisSymbol] = {
|
|
||||||
namespace: namespace,
|
|
||||||
context: namespace.active,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// At emit time, bind the listener within the correct context.
|
|
||||||
function bind(unwrapped) {
|
|
||||||
if (!(unwrapped && unwrapped[CONTEXTS_SYMBOL])) {
|
|
||||||
return unwrapped
|
|
||||||
}
|
|
||||||
|
|
||||||
let wrapped = unwrapped
|
|
||||||
let unwrappedContexts = unwrapped[CONTEXTS_SYMBOL]
|
|
||||||
Object.keys(unwrappedContexts).forEach(function (name) {
|
|
||||||
let thunk = unwrappedContexts[name]
|
|
||||||
wrapped = thunk.namespace.bind(wrapped, thunk.context)
|
|
||||||
})
|
|
||||||
return wrapped
|
|
||||||
}
|
|
||||||
|
|
||||||
wrapEmitter(emitter, attach, bind)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If an error comes out of a namespace, it will have a context attached to it.
|
|
||||||
* This function knows how to find it.
|
|
||||||
*
|
|
||||||
* @param {Error} exception Possibly annotated error.
|
|
||||||
*/
|
|
||||||
Namespace.prototype.fromException = function fromException(exception) {
|
|
||||||
return exception[ERROR_SYMBOL]
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNamespace(name) {
|
|
||||||
return process.namespaces[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
function createNamespace(name) {
|
|
||||||
assert.ok(name, "namespace must be given a name.")
|
|
||||||
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
debug2(`NS-CREATING NAMESPACE (${name})`)
|
|
||||||
}
|
|
||||||
let namespace = new Namespace(name)
|
|
||||||
namespace.id = currentUid
|
|
||||||
|
|
||||||
const hook = async_hooks.createHook({
|
|
||||||
init(asyncId, type, triggerId, resource) {
|
|
||||||
currentUid = async_hooks.executionAsyncId()
|
|
||||||
|
|
||||||
//CHAIN Parent's Context onto child if none exists. This is needed to pass net-events.spec
|
|
||||||
// let initContext = namespace.active;
|
|
||||||
// if(!initContext && triggerId) {
|
|
||||||
// let parentContext = namespace._contexts.get(triggerId);
|
|
||||||
// if (parentContext) {
|
|
||||||
// namespace.active = parentContext;
|
|
||||||
// namespace._contexts.set(currentUid, parentContext);
|
|
||||||
// if (DEBUG_CLS_HOOKED) {
|
|
||||||
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
|
||||||
// debug2(`${indentStr}INIT [${type}] (${name}) WITH PARENT CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
|
||||||
// }
|
|
||||||
// } else if (DEBUG_CLS_HOOKED) {
|
|
||||||
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
|
||||||
// debug2(`${indentStr}INIT [${type}] (${name}) MISSING CONTEXT asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
|
||||||
// }
|
|
||||||
// }else {
|
|
||||||
// namespace._contexts.set(currentUid, namespace.active);
|
|
||||||
// if (DEBUG_CLS_HOOKED) {
|
|
||||||
// const indentStr = ' '.repeat(namespace._indent < 0 ? 0 : namespace._indent);
|
|
||||||
// debug2(`${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(namespace.active, true)} resource:${resource}`);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
if (namespace.active) {
|
|
||||||
namespace._contexts.set(asyncId, namespace.active)
|
|
||||||
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const indentStr = " ".repeat(
|
|
||||||
namespace._indent < 0 ? 0 : namespace._indent
|
|
||||||
)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}INIT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
|
||||||
namespace.active,
|
|
||||||
{ showHidden: true, depth: 2, colors: true }
|
|
||||||
)} resource:${resource}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (currentUid === 0) {
|
|
||||||
// CurrentId will be 0 when triggered from C++. Promise events
|
|
||||||
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
const triggerIdContext = namespace._contexts.get(triggerId)
|
|
||||||
if (triggerIdContext) {
|
|
||||||
namespace._contexts.set(asyncId, triggerIdContext)
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const indentStr = " ".repeat(
|
|
||||||
namespace._indent < 0 ? 0 : namespace._indent
|
|
||||||
)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}INIT USING CONTEXT FROM TRIGGERID [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
|
||||||
namespace.active,
|
|
||||||
{ showHidden: true, depth: 2, colors: true }
|
|
||||||
)} resource:${resource}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (DEBUG_CLS_HOOKED) {
|
|
||||||
const indentStr = " ".repeat(
|
|
||||||
namespace._indent < 0 ? 0 : namespace._indent
|
|
||||||
)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}INIT MISSING CONTEXT [${type}] (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
|
||||||
namespace.active,
|
|
||||||
{ showHidden: true, depth: 2, colors: true }
|
|
||||||
)} resource:${resource}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (DEBUG_CLS_HOOKED && type === "PROMISE") {
|
|
||||||
debug2(util.inspect(resource, { showHidden: true }))
|
|
||||||
const parentId = resource.parentId
|
|
||||||
const indentStr = " ".repeat(
|
|
||||||
namespace._indent < 0 ? 0 : namespace._indent
|
|
||||||
)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}INIT RESOURCE-PROMISE [${type}] (${name}) parentId:${parentId} asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
|
||||||
namespace.active,
|
|
||||||
{ showHidden: true, depth: 2, colors: true }
|
|
||||||
)} resource:${resource}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
before(asyncId) {
|
|
||||||
currentUid = async_hooks.executionAsyncId()
|
|
||||||
let context
|
|
||||||
|
|
||||||
/*
|
|
||||||
if(currentUid === 0){
|
|
||||||
// CurrentId will be 0 when triggered from C++. Promise events
|
|
||||||
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
|
||||||
//const triggerId = async_hooks.triggerAsyncId();
|
|
||||||
context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId);
|
|
||||||
}else{
|
|
||||||
context = namespace._contexts.get(currentUid);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
//HACK to work with promises until they are fixed in node > 8.1.1
|
|
||||||
context =
|
|
||||||
namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid)
|
|
||||||
|
|
||||||
if (context) {
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
const indentStr = " ".repeat(
|
|
||||||
namespace._indent < 0 ? 0 : namespace._indent
|
|
||||||
)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}BEFORE (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
|
||||||
namespace.active,
|
|
||||||
{ showHidden: true, depth: 2, colors: true }
|
|
||||||
)} context:${util.inspect(context)}`
|
|
||||||
)
|
|
||||||
namespace._indent += 2
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace.enter(context)
|
|
||||||
} else if (DEBUG_CLS_HOOKED) {
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
const indentStr = " ".repeat(
|
|
||||||
namespace._indent < 0 ? 0 : namespace._indent
|
|
||||||
)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}BEFORE MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
|
||||||
namespace.active,
|
|
||||||
{ showHidden: true, depth: 2, colors: true }
|
|
||||||
)} namespace._contexts:${util.inspect(namespace._contexts, {
|
|
||||||
showHidden: true,
|
|
||||||
depth: 2,
|
|
||||||
colors: true,
|
|
||||||
})}`
|
|
||||||
)
|
|
||||||
namespace._indent += 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
after(asyncId) {
|
|
||||||
currentUid = async_hooks.executionAsyncId()
|
|
||||||
let context // = namespace._contexts.get(currentUid);
|
|
||||||
/*
|
|
||||||
if(currentUid === 0){
|
|
||||||
// CurrentId will be 0 when triggered from C++. Promise events
|
|
||||||
// https://github.com/nodejs/node/blob/master/doc/api/async_hooks.md#triggerid
|
|
||||||
//const triggerId = async_hooks.triggerAsyncId();
|
|
||||||
context = namespace._contexts.get(asyncId); // || namespace._contexts.get(triggerId);
|
|
||||||
}else{
|
|
||||||
context = namespace._contexts.get(currentUid);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
//HACK to work with promises until they are fixed in node > 8.1.1
|
|
||||||
context =
|
|
||||||
namespace._contexts.get(asyncId) || namespace._contexts.get(currentUid)
|
|
||||||
|
|
||||||
if (context) {
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
namespace._indent -= 2
|
|
||||||
const indentStr = " ".repeat(
|
|
||||||
namespace._indent < 0 ? 0 : namespace._indent
|
|
||||||
)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}AFTER (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
|
||||||
namespace.active,
|
|
||||||
{ showHidden: true, depth: 2, colors: true }
|
|
||||||
)} context:${util.inspect(context)}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace.exit(context)
|
|
||||||
} else if (DEBUG_CLS_HOOKED) {
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
namespace._indent -= 2
|
|
||||||
const indentStr = " ".repeat(
|
|
||||||
namespace._indent < 0 ? 0 : namespace._indent
|
|
||||||
)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}AFTER MISSING CONTEXT (${name}) asyncId:${asyncId} currentUid:${currentUid} triggerId:${triggerId} active:${util.inspect(
|
|
||||||
namespace.active,
|
|
||||||
{ showHidden: true, depth: 2, colors: true }
|
|
||||||
)} context:${util.inspect(context)}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
destroy(asyncId) {
|
|
||||||
currentUid = async_hooks.executionAsyncId()
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
const triggerId = async_hooks.triggerAsyncId()
|
|
||||||
const indentStr = " ".repeat(
|
|
||||||
namespace._indent < 0 ? 0 : namespace._indent
|
|
||||||
)
|
|
||||||
debug2(
|
|
||||||
`${indentStr}DESTROY (${name}) currentUid:${currentUid} asyncId:${asyncId} triggerId:${triggerId} active:${util.inspect(
|
|
||||||
namespace.active,
|
|
||||||
{ showHidden: true, depth: 2, colors: true }
|
|
||||||
)} context:${util.inspect(namespace._contexts.get(currentUid))}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace._contexts.delete(asyncId)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
hook.enable()
|
|
||||||
namespace._hook = hook
|
|
||||||
|
|
||||||
process.namespaces[name] = namespace
|
|
||||||
return namespace
|
|
||||||
}
|
|
||||||
|
|
||||||
function destroyNamespace(name) {
|
|
||||||
let namespace = getNamespace(name)
|
|
||||||
|
|
||||||
assert.ok(namespace, "can't delete nonexistent namespace! \"" + name + '"')
|
|
||||||
assert.ok(
|
|
||||||
namespace.id,
|
|
||||||
"don't assign to process.namespaces directly! " + util.inspect(namespace)
|
|
||||||
)
|
|
||||||
|
|
||||||
namespace._hook.disable()
|
|
||||||
namespace._contexts = null
|
|
||||||
process.namespaces[name] = null
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
// must unregister async listeners
|
|
||||||
if (process.namespaces) {
|
|
||||||
Object.keys(process.namespaces).forEach(function (name) {
|
|
||||||
destroyNamespace(name)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
process.namespaces = Object.create(null)
|
|
||||||
}
|
|
||||||
|
|
||||||
process.namespaces = process.namespaces || {}
|
|
||||||
|
|
||||||
//const fs = require('fs');
|
|
||||||
function debug2(...args) {
|
|
||||||
if (DEBUG_CLS_HOOKED) {
|
|
||||||
//fs.writeSync(1, `${util.format(...args)}\n`);
|
|
||||||
process._rawDebug(`${util.format(...args)}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*function getFunctionName(fn) {
|
|
||||||
if (!fn) {
|
|
||||||
return fn;
|
|
||||||
}
|
|
||||||
if (typeof fn === 'function') {
|
|
||||||
if (fn.name) {
|
|
||||||
return fn.name;
|
|
||||||
}
|
|
||||||
return (fn.toString().trim().match(/^function\s*([^\s(]+)/) || [])[1];
|
|
||||||
} else if (fn.constructor && fn.constructor.name) {
|
|
||||||
return fn.constructor.name;
|
|
||||||
}
|
|
||||||
}*/
|
|
|
@ -1,44 +0,0 @@
|
||||||
exports.UserStatus = {
|
|
||||||
ACTIVE: "active",
|
|
||||||
INACTIVE: "inactive",
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.Cookies = {
|
|
||||||
CurrentApp: "budibase:currentapp",
|
|
||||||
Auth: "budibase:auth",
|
|
||||||
Init: "budibase:init",
|
|
||||||
ACCOUNT_RETURN_URL: "budibase:account:returnurl",
|
|
||||||
DatasourceAuth: "budibase:datasourceauth",
|
|
||||||
OIDC_CONFIG: "budibase:oidc:config",
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.Headers = {
|
|
||||||
API_KEY: "x-budibase-api-key",
|
|
||||||
LICENSE_KEY: "x-budibase-license-key",
|
|
||||||
API_VER: "x-budibase-api-version",
|
|
||||||
APP_ID: "x-budibase-app-id",
|
|
||||||
TYPE: "x-budibase-type",
|
|
||||||
PREVIEW_ROLE: "x-budibase-role",
|
|
||||||
TENANT_ID: "x-budibase-tenant-id",
|
|
||||||
TOKEN: "x-budibase-token",
|
|
||||||
CSRF_TOKEN: "x-csrf-token",
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.GlobalRoles = {
|
|
||||||
OWNER: "owner",
|
|
||||||
ADMIN: "admin",
|
|
||||||
BUILDER: "builder",
|
|
||||||
WORKSPACE_MANAGER: "workspace_manager",
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.Configs = {
|
|
||||||
SETTINGS: "settings",
|
|
||||||
ACCOUNT: "account",
|
|
||||||
SMTP: "smtp",
|
|
||||||
GOOGLE: "google",
|
|
||||||
OIDC: "oidc",
|
|
||||||
OIDC_LOGOS: "logos_oidc",
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.MAX_VALID_DATE = new Date(2147483647000)
|
|
||||||
exports.DEFAULT_TENANT_ID = "default"
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
export enum UserStatus {
|
||||||
|
ACTIVE = "active",
|
||||||
|
INACTIVE = "inactive",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Cookie {
|
||||||
|
CurrentApp = "budibase:currentapp",
|
||||||
|
Auth = "budibase:auth",
|
||||||
|
Init = "budibase:init",
|
||||||
|
ACCOUNT_RETURN_URL = "budibase:account:returnurl",
|
||||||
|
DatasourceAuth = "budibase:datasourceauth",
|
||||||
|
OIDC_CONFIG = "budibase:oidc:config",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Header {
|
||||||
|
API_KEY = "x-budibase-api-key",
|
||||||
|
LICENSE_KEY = "x-budibase-license-key",
|
||||||
|
API_VER = "x-budibase-api-version",
|
||||||
|
APP_ID = "x-budibase-app-id",
|
||||||
|
TYPE = "x-budibase-type",
|
||||||
|
PREVIEW_ROLE = "x-budibase-role",
|
||||||
|
TENANT_ID = "x-budibase-tenant-id",
|
||||||
|
TOKEN = "x-budibase-token",
|
||||||
|
CSRF_TOKEN = "x-csrf-token",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum GlobalRole {
|
||||||
|
OWNER = "owner",
|
||||||
|
ADMIN = "admin",
|
||||||
|
BUILDER = "builder",
|
||||||
|
WORKSPACE_MANAGER = "workspace_manager",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Config {
|
||||||
|
SETTINGS = "settings",
|
||||||
|
ACCOUNT = "account",
|
||||||
|
SMTP = "smtp",
|
||||||
|
GOOGLE = "google",
|
||||||
|
OIDC = "oidc",
|
||||||
|
OIDC_LOGOS = "logos_oidc",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MAX_VALID_DATE = new Date(2147483647000)
|
||||||
|
export const DEFAULT_TENANT_ID = "default"
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { AsyncLocalStorage } from "async_hooks"
|
||||||
|
import { ContextMap } from "./constants"
|
||||||
|
|
||||||
|
export default class Context {
|
||||||
|
static storage = new AsyncLocalStorage<ContextMap>()
|
||||||
|
|
||||||
|
static run(context: ContextMap, func: any) {
|
||||||
|
return Context.storage.run(context, () => func())
|
||||||
|
}
|
||||||
|
|
||||||
|
static get(): ContextMap {
|
||||||
|
return Context.storage.getStore() as ContextMap
|
||||||
|
}
|
||||||
|
|
||||||
|
static set(context: ContextMap) {
|
||||||
|
Context.storage.enterWith(context)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,47 +0,0 @@
|
||||||
const cls = require("../clshooked")
|
|
||||||
const { newid } = require("../hashing")
|
|
||||||
|
|
||||||
const REQUEST_ID_KEY = "requestId"
|
|
||||||
const MAIN_CTX = cls.createNamespace("main")
|
|
||||||
|
|
||||||
function getContextStorage(namespace) {
|
|
||||||
if (namespace && namespace.active) {
|
|
||||||
let contextData = namespace.active
|
|
||||||
delete contextData.id
|
|
||||||
delete contextData._ns_name
|
|
||||||
return contextData
|
|
||||||
}
|
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FunctionContext {
|
|
||||||
static run(callback) {
|
|
||||||
return MAIN_CTX.runAndReturn(async () => {
|
|
||||||
const namespaceId = newid()
|
|
||||||
MAIN_CTX.set(REQUEST_ID_KEY, namespaceId)
|
|
||||||
const namespace = cls.createNamespace(namespaceId)
|
|
||||||
let response = await namespace.runAndReturn(callback)
|
|
||||||
cls.destroyNamespace(namespaceId)
|
|
||||||
return response
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
static setOnContext(key, value) {
|
|
||||||
const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY)
|
|
||||||
const namespace = cls.getNamespace(namespaceId)
|
|
||||||
namespace.set(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
static getFromContext(key) {
|
|
||||||
const namespaceId = MAIN_CTX.get(REQUEST_ID_KEY)
|
|
||||||
const namespace = cls.getNamespace(namespaceId)
|
|
||||||
const context = getContextStorage(namespace)
|
|
||||||
if (context) {
|
|
||||||
return context[key]
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = FunctionContext
|
|
|
@ -1,17 +1,7 @@
|
||||||
export enum ContextKey {
|
import { IdentityContext } from "@budibase/types"
|
||||||
TENANT_ID = "tenantId",
|
|
||||||
GLOBAL_DB = "globalDb",
|
export type ContextMap = {
|
||||||
APP_ID = "appId",
|
tenantId?: string
|
||||||
IDENTITY = "identity",
|
appId?: string
|
||||||
// whatever the request app DB was
|
identity?: IdentityContext
|
||||||
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",
|
|
||||||
// check if something else is using the context, don't close DB
|
|
||||||
TENANCY_IN_USE = "tenancyInUse",
|
|
||||||
APP_IN_USE = "appInUse",
|
|
||||||
IDENTITY_IN_USE = "identityInUse",
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
import { getGlobalUserParams, getAllApps } from "../db/utils"
|
import {
|
||||||
import { doWithDB } from "../db"
|
getGlobalUserParams,
|
||||||
|
getAllApps,
|
||||||
|
doWithDB,
|
||||||
|
StaticDatabases,
|
||||||
|
} from "../db"
|
||||||
import { doWithGlobalDB } from "../tenancy"
|
import { doWithGlobalDB } from "../tenancy"
|
||||||
import { StaticDatabases } from "../db/constants"
|
import { App, Tenants, User, Database } from "@budibase/types"
|
||||||
import { App, Tenants, User } from "@budibase/types"
|
|
||||||
|
|
||||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||||
|
|
||||||
const removeTenantFromInfoDB = async (tenantId: string) => {
|
async function removeTenantFromInfoDB(tenantId: string) {
|
||||||
try {
|
try {
|
||||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => {
|
await doWithDB(PLATFORM_INFO_DB, async (infoDb: Database) => {
|
||||||
const tenants = (await infoDb.get(TENANT_DOC)) as Tenants
|
const tenants = (await infoDb.get(TENANT_DOC)) as Tenants
|
||||||
tenants.tenantIds = tenants.tenantIds.filter(id => id !== tenantId)
|
tenants.tenantIds = tenants.tenantIds.filter(id => id !== tenantId)
|
||||||
|
|
||||||
|
@ -21,9 +24,9 @@ const removeTenantFromInfoDB = async (tenantId: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const removeUserFromInfoDB = async (dbUser: User) => {
|
export async function removeUserFromInfoDB(dbUser: User) {
|
||||||
await doWithDB(PLATFORM_INFO_DB, async (infoDb: any) => {
|
await doWithDB(PLATFORM_INFO_DB, async (infoDb: Database) => {
|
||||||
const keys = [dbUser._id, dbUser.email]
|
const keys = [dbUser._id!, dbUser.email]
|
||||||
const userDocs = await infoDb.allDocs({
|
const userDocs = await infoDb.allDocs({
|
||||||
keys,
|
keys,
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
|
@ -38,7 +41,7 @@ export const removeUserFromInfoDB = async (dbUser: User) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeUsersFromInfoDB = async (tenantId: string) => {
|
async function removeUsersFromInfoDB(tenantId: string) {
|
||||||
return doWithGlobalDB(tenantId, async (db: any) => {
|
return doWithGlobalDB(tenantId, async (db: any) => {
|
||||||
try {
|
try {
|
||||||
const allUsers = await db.allDocs(
|
const allUsers = await db.allDocs(
|
||||||
|
@ -72,8 +75,8 @@ const removeUsersFromInfoDB = async (tenantId: string) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeGlobalDB = async (tenantId: string) => {
|
async function removeGlobalDB(tenantId: string) {
|
||||||
return doWithGlobalDB(tenantId, async (db: any) => {
|
return doWithGlobalDB(tenantId, async (db: Database) => {
|
||||||
try {
|
try {
|
||||||
await db.destroy()
|
await db.destroy()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -83,11 +86,11 @@ const removeGlobalDB = async (tenantId: string) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeTenantApps = async (tenantId: string) => {
|
async function removeTenantApps(tenantId: string) {
|
||||||
try {
|
try {
|
||||||
const apps = (await getAllApps({ all: true })) as App[]
|
const apps = (await getAllApps({ all: true })) as App[]
|
||||||
const destroyPromises = apps.map(app =>
|
const destroyPromises = apps.map(app =>
|
||||||
doWithDB(app.appId, (db: any) => db.destroy())
|
doWithDB(app.appId, (db: Database) => db.destroy())
|
||||||
)
|
)
|
||||||
await Promise.allSettled(destroyPromises)
|
await Promise.allSettled(destroyPromises)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -97,7 +100,7 @@ const removeTenantApps = async (tenantId: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// can't live in tenancy package due to circular dependency on db/utils
|
// can't live in tenancy package due to circular dependency on db/utils
|
||||||
export const deleteTenant = async (tenantId: string) => {
|
export async function deleteTenant(tenantId: string) {
|
||||||
await removeTenantFromInfoDB(tenantId)
|
await removeTenantFromInfoDB(tenantId)
|
||||||
await removeUsersFromInfoDB(tenantId)
|
await removeUsersFromInfoDB(tenantId)
|
||||||
await removeGlobalDB(tenantId)
|
await removeGlobalDB(tenantId)
|
||||||
|
|
|
@ -1,47 +1,32 @@
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { SEPARATOR, DocumentType } from "../db/constants"
|
|
||||||
import cls from "./FunctionContext"
|
|
||||||
import { dangerousGetDB, closeDB } from "../db"
|
|
||||||
import { baseGlobalDBName } from "../db/tenancy"
|
|
||||||
import { IdentityContext } from "@budibase/types"
|
|
||||||
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
|
|
||||||
import { ContextKey } from "./constants"
|
|
||||||
import PouchDB from "pouchdb"
|
|
||||||
import {
|
import {
|
||||||
updateUsing,
|
SEPARATOR,
|
||||||
closeWithUsing,
|
DocumentType,
|
||||||
setAppTenantId,
|
getDevelopmentAppID,
|
||||||
setIdentity,
|
getProdAppID,
|
||||||
closeAppDBs,
|
baseGlobalDBName,
|
||||||
getContextDB,
|
getDB,
|
||||||
} from "./utils"
|
} from "../db"
|
||||||
|
import Context from "./Context"
|
||||||
|
import { IdentityContext, Database } from "@budibase/types"
|
||||||
|
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
|
||||||
|
import { ContextMap } from "./constants"
|
||||||
export const DEFAULT_TENANT_ID = _DEFAULT_TENANT_ID
|
export const DEFAULT_TENANT_ID = _DEFAULT_TENANT_ID
|
||||||
|
|
||||||
// some test cases call functions directly, need to
|
// some test cases call functions directly, need to
|
||||||
// store an app ID to pretend there is a context
|
// store an app ID to pretend there is a context
|
||||||
let TEST_APP_ID: string | null = null
|
let TEST_APP_ID: string | null = null
|
||||||
|
|
||||||
export const closeTenancy = async () => {
|
export function isMultiTenant() {
|
||||||
try {
|
return env.MULTI_TENANCY
|
||||||
if (env.USE_COUCH) {
|
|
||||||
const db = getGlobalDB()
|
|
||||||
await closeDB(db)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// no DB found - skip closing
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// clear from context now that database is closed/task is finished
|
|
||||||
cls.setOnContext(ContextKey.TENANT_ID, null)
|
|
||||||
cls.setOnContext(ContextKey.GLOBAL_DB, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// export const isDefaultTenant = () => {
|
export function isTenantIdSet() {
|
||||||
// return getTenantId() === DEFAULT_TENANT_ID
|
const context = Context.get()
|
||||||
// }
|
return !!context?.tenantId
|
||||||
|
}
|
||||||
|
|
||||||
export const isMultiTenant = () => {
|
export function isTenancyEnabled() {
|
||||||
return env.MULTI_TENANCY
|
return env.MULTI_TENANCY
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,9 +34,9 @@ export const isMultiTenant = () => {
|
||||||
* Given an app ID this will attempt to retrieve the tenant ID from it.
|
* 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.
|
* @return {null|string} The tenant ID found within the app ID.
|
||||||
*/
|
*/
|
||||||
export const getTenantIDFromAppID = (appId: string) => {
|
export function getTenantIDFromAppID(appId: string) {
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
return null
|
return undefined
|
||||||
}
|
}
|
||||||
if (!isMultiTenant()) {
|
if (!isMultiTenant()) {
|
||||||
return DEFAULT_TENANT_ID
|
return DEFAULT_TENANT_ID
|
||||||
|
@ -59,7 +44,7 @@ export const getTenantIDFromAppID = (appId: string) => {
|
||||||
const split = appId.split(SEPARATOR)
|
const split = appId.split(SEPARATOR)
|
||||||
const hasDev = split[1] === DocumentType.DEV
|
const hasDev = split[1] === DocumentType.DEV
|
||||||
if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) {
|
if ((hasDev && split.length === 3) || (!hasDev && split.length === 2)) {
|
||||||
return null
|
return undefined
|
||||||
}
|
}
|
||||||
if (hasDev) {
|
if (hasDev) {
|
||||||
return split[2]
|
return split[2]
|
||||||
|
@ -68,127 +53,125 @@ export const getTenantIDFromAppID = (appId: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const doInContext = async (appId: string, task: any) => {
|
function updateContext(updates: ContextMap) {
|
||||||
// gets the tenant ID from the app ID
|
let context: ContextMap
|
||||||
const tenantId = getTenantIDFromAppID(appId)
|
try {
|
||||||
return doInTenant(tenantId, async () => {
|
context = Context.get()
|
||||||
return doInAppContext(appId, async () => {
|
} catch (err) {
|
||||||
return task()
|
// no context, start empty
|
||||||
})
|
context = {}
|
||||||
})
|
}
|
||||||
|
context = {
|
||||||
|
...context,
|
||||||
|
...updates,
|
||||||
|
}
|
||||||
|
return context
|
||||||
}
|
}
|
||||||
|
|
||||||
export const doInTenant = (tenantId: string | null, task: any) => {
|
async function newContext(updates: ContextMap, task: any) {
|
||||||
|
// see if there already is a context setup
|
||||||
|
let context: ContextMap = updateContext(updates)
|
||||||
|
return Context.run(context, task)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function doInContext(appId: string, task: any): Promise<any> {
|
||||||
|
const tenantId = getTenantIDFromAppID(appId)
|
||||||
|
return newContext(
|
||||||
|
{
|
||||||
|
tenantId,
|
||||||
|
appId,
|
||||||
|
},
|
||||||
|
task
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function doInTenant(
|
||||||
|
tenantId: string | null,
|
||||||
|
task: any
|
||||||
|
): Promise<any> {
|
||||||
// make sure default always selected in single tenancy
|
// make sure default always selected in single tenancy
|
||||||
if (!env.MULTI_TENANCY) {
|
if (!env.MULTI_TENANCY) {
|
||||||
tenantId = tenantId || DEFAULT_TENANT_ID
|
tenantId = tenantId || DEFAULT_TENANT_ID
|
||||||
}
|
}
|
||||||
// the internal function is so that we can re-use an existing
|
|
||||||
// context - don't want to close DB on a parent context
|
|
||||||
async function internal(opts = { existing: false }) {
|
|
||||||
// set the tenant id + global db if this is a new context
|
|
||||||
if (!opts.existing) {
|
|
||||||
updateTenantId(tenantId)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
const updates = tenantId ? { tenantId } : {}
|
||||||
// invoke the task
|
return newContext(updates, task)
|
||||||
return await task()
|
|
||||||
} finally {
|
|
||||||
await closeWithUsing(ContextKey.TENANCY_IN_USE, () => {
|
|
||||||
return closeTenancy()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = cls.getFromContext(ContextKey.TENANT_ID) === tenantId
|
|
||||||
return updateUsing(ContextKey.TENANCY_IN_USE, existing, internal)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const doInAppContext = (appId: string, task: any) => {
|
export async function doInAppContext(appId: string, task: any): Promise<any> {
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
throw new Error("appId is required")
|
throw new Error("appId is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
const identity = getIdentity()
|
const tenantId = getTenantIDFromAppID(appId)
|
||||||
|
const updates: ContextMap = { appId }
|
||||||
// the internal function is so that we can re-use an existing
|
if (tenantId) {
|
||||||
// context - don't want to close DB on a parent context
|
updates.tenantId = tenantId
|
||||||
async function internal(opts = { existing: false }) {
|
|
||||||
// set the app tenant id
|
|
||||||
if (!opts.existing) {
|
|
||||||
setAppTenantId(appId)
|
|
||||||
}
|
}
|
||||||
// set the app ID
|
return newContext(updates, task)
|
||||||
cls.setOnContext(ContextKey.APP_ID, appId)
|
|
||||||
|
|
||||||
// preserve the identity
|
|
||||||
if (identity) {
|
|
||||||
setIdentity(identity)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// invoke the task
|
|
||||||
return await task()
|
|
||||||
} finally {
|
|
||||||
await closeWithUsing(ContextKey.APP_IN_USE, async () => {
|
|
||||||
await closeAppDBs()
|
|
||||||
await closeTenancy()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const existing = cls.getFromContext(ContextKey.APP_ID) === appId
|
|
||||||
return updateUsing(ContextKey.APP_IN_USE, existing, internal)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const doInIdentityContext = (identity: IdentityContext, task: any) => {
|
export async function doInIdentityContext(
|
||||||
|
identity: IdentityContext,
|
||||||
|
task: any
|
||||||
|
): Promise<any> {
|
||||||
if (!identity) {
|
if (!identity) {
|
||||||
throw new Error("identity is required")
|
throw new Error("identity is required")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function internal(opts = { existing: false }) {
|
const context: ContextMap = {
|
||||||
if (!opts.existing) {
|
identity,
|
||||||
cls.setOnContext(ContextKey.IDENTITY, identity)
|
}
|
||||||
// set the tenant so that doInTenant will preserve identity
|
|
||||||
if (identity.tenantId) {
|
if (identity.tenantId) {
|
||||||
updateTenantId(identity.tenantId)
|
context.tenantId = identity.tenantId
|
||||||
}
|
}
|
||||||
}
|
return newContext(context, task)
|
||||||
|
|
||||||
try {
|
|
||||||
// invoke the task
|
|
||||||
return await task()
|
|
||||||
} finally {
|
|
||||||
await closeWithUsing(ContextKey.IDENTITY_IN_USE, async () => {
|
|
||||||
setIdentity(null)
|
|
||||||
await closeTenancy()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const existing = cls.getFromContext(ContextKey.IDENTITY)
|
|
||||||
return updateUsing(ContextKey.IDENTITY_IN_USE, existing, internal)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getIdentity = (): IdentityContext | undefined => {
|
export function getIdentity(): IdentityContext | undefined {
|
||||||
try {
|
try {
|
||||||
return cls.getFromContext(ContextKey.IDENTITY)
|
const context = Context.get()
|
||||||
|
return context?.identity
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// do nothing - identity is not in context
|
// do nothing - identity is not in context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateTenantId = (tenantId: string | null) => {
|
export function getTenantId(): string {
|
||||||
cls.setOnContext(ContextKey.TENANT_ID, tenantId)
|
if (!isMultiTenant()) {
|
||||||
if (env.USE_COUCH) {
|
return DEFAULT_TENANT_ID
|
||||||
setGlobalDB(tenantId)
|
}
|
||||||
|
const context = Context.get()
|
||||||
|
const tenantId = context?.tenantId
|
||||||
|
if (!tenantId) {
|
||||||
|
throw new Error("Tenant id not found")
|
||||||
|
}
|
||||||
|
return tenantId
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAppId(): string | undefined {
|
||||||
|
const context = Context.get()
|
||||||
|
const foundId = context?.appId
|
||||||
|
if (!foundId && env.isTest() && TEST_APP_ID) {
|
||||||
|
return TEST_APP_ID
|
||||||
|
} else {
|
||||||
|
return foundId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateAppId = async (appId: string) => {
|
export function updateTenantId(tenantId?: string) {
|
||||||
|
let context: ContextMap = updateContext({
|
||||||
|
tenantId,
|
||||||
|
})
|
||||||
|
Context.set(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateAppId(appId: string) {
|
||||||
|
let context: ContextMap = updateContext({
|
||||||
|
appId,
|
||||||
|
})
|
||||||
try {
|
try {
|
||||||
// have to close first, before removing the databases from context
|
Context.set(context)
|
||||||
await closeAppDBs()
|
|
||||||
cls.setOnContext(ContextKey.APP_ID, appId)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (env.isTest()) {
|
if (env.isTest()) {
|
||||||
TEST_APP_ID = appId
|
TEST_APP_ID = appId
|
||||||
|
@ -198,70 +181,43 @@ export const updateAppId = async (appId: string) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setGlobalDB = (tenantId: string | null) => {
|
export function getGlobalDB(): Database {
|
||||||
const dbName = baseGlobalDBName(tenantId)
|
const context = Context.get()
|
||||||
const db = dangerousGetDB(dbName)
|
if (!context || (env.MULTI_TENANCY && !context.tenantId)) {
|
||||||
cls.setOnContext(ContextKey.GLOBAL_DB, db)
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getGlobalDB = () => {
|
|
||||||
const db = cls.getFromContext(ContextKey.GLOBAL_DB)
|
|
||||||
if (!db) {
|
|
||||||
throw new Error("Global DB not found")
|
throw new Error("Global DB not found")
|
||||||
}
|
}
|
||||||
return db
|
return getDB(baseGlobalDBName(context?.tenantId))
|
||||||
}
|
|
||||||
|
|
||||||
export const isTenantIdSet = () => {
|
|
||||||
const tenantId = cls.getFromContext(ContextKey.TENANT_ID)
|
|
||||||
return !!tenantId
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getTenantId = () => {
|
|
||||||
if (!isMultiTenant()) {
|
|
||||||
return DEFAULT_TENANT_ID
|
|
||||||
}
|
|
||||||
const tenantId = cls.getFromContext(ContextKey.TENANT_ID)
|
|
||||||
if (!tenantId) {
|
|
||||||
throw new Error("Tenant id not found")
|
|
||||||
}
|
|
||||||
return tenantId
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getAppId = () => {
|
|
||||||
const foundId = cls.getFromContext(ContextKey.APP_ID)
|
|
||||||
if (!foundId && env.isTest() && TEST_APP_ID) {
|
|
||||||
return TEST_APP_ID
|
|
||||||
} else {
|
|
||||||
return foundId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isTenancyEnabled = () => {
|
|
||||||
return env.MULTI_TENANCY
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the app database based on whatever the request
|
* Gets the app database based on whatever the request
|
||||||
* contained, dev or prod.
|
* contained, dev or prod.
|
||||||
*/
|
*/
|
||||||
export const getAppDB = (opts?: any) => {
|
export function getAppDB(opts?: any): Database {
|
||||||
return getContextDB(ContextKey.CURRENT_DB, opts)
|
const appId = getAppId()
|
||||||
|
return getDB(appId, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This specifically gets the prod app ID, if the request
|
* This specifically gets the prod app ID, if the request
|
||||||
* contained a development app ID, this will open the prod one.
|
* contained a development app ID, this will get the prod one.
|
||||||
*/
|
*/
|
||||||
export const getProdAppDB = (opts?: any) => {
|
export function getProdAppDB(opts?: any): Database {
|
||||||
return getContextDB(ContextKey.PROD_DB, opts)
|
const appId = getAppId()
|
||||||
|
if (!appId) {
|
||||||
|
throw new Error("Unable to retrieve prod DB - no app ID.")
|
||||||
|
}
|
||||||
|
return getDB(getProdAppID(appId), opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This specifically gets the dev app ID, if the request
|
* This specifically gets the dev app ID, if the request
|
||||||
* contained a prod app ID, this will open the dev one.
|
* contained a prod app ID, this will get the dev one.
|
||||||
*/
|
*/
|
||||||
export const getDevAppDB = (opts?: any) => {
|
export function getDevAppDB(opts?: any): Database {
|
||||||
return getContextDB(ContextKey.DEV_DB, opts)
|
const appId = getAppId()
|
||||||
|
if (!appId) {
|
||||||
|
throw new Error("Unable to retrieve dev DB - no app ID.")
|
||||||
|
}
|
||||||
|
return getDB(getDevelopmentAppID(appId), opts)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,9 @@
|
||||||
import "../../../tests"
|
require("../../../tests")
|
||||||
import * as context from ".."
|
const context = require("../")
|
||||||
import { DEFAULT_TENANT_ID } from "../../constants"
|
const { DEFAULT_TENANT_ID } = require("../../constants")
|
||||||
import env from "../../environment"
|
const env = require("../../environment")
|
||||||
|
|
||||||
// must use require to spy index file exports due to known issue in jest
|
|
||||||
const dbUtils = require("../../db")
|
|
||||||
jest.spyOn(dbUtils, "closeDB")
|
|
||||||
jest.spyOn(dbUtils, "dangerousGetDB")
|
|
||||||
|
|
||||||
describe("context", () => {
|
describe("context", () => {
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("doInTenant", () => {
|
describe("doInTenant", () => {
|
||||||
describe("single-tenancy", () => {
|
describe("single-tenancy", () => {
|
||||||
it("defaults to the default tenant", () => {
|
it("defaults to the default tenant", () => {
|
||||||
|
@ -25,8 +16,6 @@ describe("context", () => {
|
||||||
const db = context.getGlobalDB()
|
const db = context.getGlobalDB()
|
||||||
expect(db.name).toBe("global-db")
|
expect(db.name).toBe("global-db")
|
||||||
})
|
})
|
||||||
expect(dbUtils.dangerousGetDB).toHaveBeenCalledTimes(1)
|
|
||||||
expect(dbUtils.closeDB).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -40,7 +29,7 @@ describe("context", () => {
|
||||||
let error
|
let error
|
||||||
try {
|
try {
|
||||||
context.getTenantId()
|
context.getTenantId()
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
error = e
|
error = e
|
||||||
}
|
}
|
||||||
expect(error.message).toBe("Tenant id not found")
|
expect(error.message).toBe("Tenant id not found")
|
||||||
|
@ -59,7 +48,7 @@ describe("context", () => {
|
||||||
let error
|
let error
|
||||||
try {
|
try {
|
||||||
context.getGlobalDB()
|
context.getGlobalDB()
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
error = e
|
error = e
|
||||||
}
|
}
|
||||||
expect(error.message).toBe("Global DB not found")
|
expect(error.message).toBe("Global DB not found")
|
||||||
|
@ -85,8 +74,6 @@ describe("context", () => {
|
||||||
const db = context.getGlobalDB()
|
const db = context.getGlobalDB()
|
||||||
expect(db.name).toBe("test_global-db")
|
expect(db.name).toBe("test_global-db")
|
||||||
})
|
})
|
||||||
expect(dbUtils.dangerousGetDB).toHaveBeenCalledTimes(1)
|
|
||||||
expect(dbUtils.closeDB).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("sets the tenant id when nested with same tenant id", async () => {
|
it("sets the tenant id when nested with same tenant id", async () => {
|
||||||
|
@ -121,10 +108,6 @@ describe("context", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// only 1 db is opened and closed
|
|
||||||
expect(dbUtils.dangerousGetDB).toHaveBeenCalledTimes(1)
|
|
||||||
expect(dbUtils.closeDB).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("sets different tenant id inside another context", () => {
|
it("sets different tenant id inside another context", () => {
|
|
@ -1,109 +0,0 @@
|
||||||
import {
|
|
||||||
DEFAULT_TENANT_ID,
|
|
||||||
getAppId,
|
|
||||||
getTenantIDFromAppID,
|
|
||||||
updateTenantId,
|
|
||||||
} from "./index"
|
|
||||||
import cls from "./FunctionContext"
|
|
||||||
import { IdentityContext } from "@budibase/types"
|
|
||||||
import { ContextKey } from "./constants"
|
|
||||||
import { dangerousGetDB, closeDB } from "../db"
|
|
||||||
import { isEqual } from "lodash"
|
|
||||||
import { getDevelopmentAppID, getProdAppID } from "../db/conversions"
|
|
||||||
import env from "../environment"
|
|
||||||
|
|
||||||
export async function updateUsing(
|
|
||||||
usingKey: string,
|
|
||||||
existing: boolean,
|
|
||||||
internal: (opts: { existing: boolean }) => Promise<any>
|
|
||||||
) {
|
|
||||||
const using = cls.getFromContext(usingKey)
|
|
||||||
if (using && existing) {
|
|
||||||
cls.setOnContext(usingKey, using + 1)
|
|
||||||
return internal({ existing: true })
|
|
||||||
} else {
|
|
||||||
return cls.run(async () => {
|
|
||||||
cls.setOnContext(usingKey, 1)
|
|
||||||
return internal({ existing: false })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function closeWithUsing(
|
|
||||||
usingKey: string,
|
|
||||||
closeFn: () => Promise<any>
|
|
||||||
) {
|
|
||||||
const using = cls.getFromContext(usingKey)
|
|
||||||
if (!using || using <= 1) {
|
|
||||||
await closeFn()
|
|
||||||
} else {
|
|
||||||
cls.setOnContext(usingKey, using - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setAppTenantId = (appId: string) => {
|
|
||||||
const appTenantId = getTenantIDFromAppID(appId) || DEFAULT_TENANT_ID
|
|
||||||
updateTenantId(appTenantId)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const setIdentity = (identity: IdentityContext | null) => {
|
|
||||||
cls.setOnContext(ContextKey.IDENTITY, identity)
|
|
||||||
}
|
|
||||||
|
|
||||||
// this function makes sure the PouchDB objects are closed and
|
|
||||||
// fully deleted when finished - this protects against memory leaks
|
|
||||||
export async function closeAppDBs() {
|
|
||||||
const dbKeys = [ContextKey.CURRENT_DB, ContextKey.PROD_DB, ContextKey.DEV_DB]
|
|
||||||
for (let dbKey of dbKeys) {
|
|
||||||
const db = cls.getFromContext(dbKey)
|
|
||||||
if (!db) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
await closeDB(db)
|
|
||||||
// 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(ContextKey.APP_ID)) {
|
|
||||||
cls.setOnContext(ContextKey.APP_ID, null)
|
|
||||||
}
|
|
||||||
if (cls.getFromContext(ContextKey.DB_OPTS)) {
|
|
||||||
cls.setOnContext(ContextKey.DB_OPTS, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getContextDB(key: string, opts: any) {
|
|
||||||
const dbOptsKey = `${key}${ContextKey.DB_OPTS}`
|
|
||||||
let storedOpts = cls.getFromContext(dbOptsKey)
|
|
||||||
let db = cls.getFromContext(key)
|
|
||||||
if (db && isEqual(opts, storedOpts)) {
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
const appId = getAppId()
|
|
||||||
let toUseAppId
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case ContextKey.CURRENT_DB:
|
|
||||||
toUseAppId = appId
|
|
||||||
break
|
|
||||||
case ContextKey.PROD_DB:
|
|
||||||
toUseAppId = getProdAppID(appId)
|
|
||||||
break
|
|
||||||
case ContextKey.DEV_DB:
|
|
||||||
toUseAppId = getDevelopmentAppID(appId)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
db = dangerousGetDB(toUseAppId, opts)
|
|
||||||
try {
|
|
||||||
cls.setOnContext(key, db)
|
|
||||||
if (opts) {
|
|
||||||
cls.setOnContext(dbOptsKey, opts)
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (!env.isTest()) {
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return db
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { dangerousGetDB, closeDB } from "."
|
import { getPouchDB, closePouchDB } from "./couch/pouchDB"
|
||||||
import { DocumentType } from "./constants"
|
import { DocumentType } from "./constants"
|
||||||
|
|
||||||
class Replication {
|
class Replication {
|
||||||
|
@ -12,12 +12,12 @@ class Replication {
|
||||||
* @param {String} target - the DB you want to replicate to, or rollback from
|
* @param {String} target - the DB you want to replicate to, or rollback from
|
||||||
*/
|
*/
|
||||||
constructor({ source, target }: any) {
|
constructor({ source, target }: any) {
|
||||||
this.source = dangerousGetDB(source)
|
this.source = getPouchDB(source)
|
||||||
this.target = dangerousGetDB(target)
|
this.target = getPouchDB(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
return Promise.all([closeDB(this.source), closeDB(this.target)])
|
return Promise.all([closePouchDB(this.source), closePouchDB(this.target)])
|
||||||
}
|
}
|
||||||
|
|
||||||
promisify(operation: any, opts = {}) {
|
promisify(operation: any, opts = {}) {
|
||||||
|
@ -68,7 +68,7 @@ class Replication {
|
||||||
async rollback() {
|
async rollback() {
|
||||||
await this.target.destroy()
|
await this.target.destroy()
|
||||||
// Recreate the DB again
|
// Recreate the DB again
|
||||||
this.target = dangerousGetDB(this.target.name)
|
this.target = getPouchDB(this.target.name)
|
||||||
// take the opportunity to remove deleted tombstones
|
// take the opportunity to remove deleted tombstones
|
||||||
await this.replicate()
|
await this.replicate()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,32 +1,33 @@
|
||||||
|
import { APP_DEV_PREFIX, APP_PREFIX } from "./constants"
|
||||||
|
import { App } from "@budibase/types"
|
||||||
const NO_APP_ERROR = "No app provided"
|
const NO_APP_ERROR = "No app provided"
|
||||||
const { APP_DEV_PREFIX, APP_PREFIX } = require("./constants")
|
|
||||||
|
|
||||||
exports.isDevAppID = appId => {
|
export function isDevAppID(appId?: string) {
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
throw NO_APP_ERROR
|
throw NO_APP_ERROR
|
||||||
}
|
}
|
||||||
return appId.startsWith(APP_DEV_PREFIX)
|
return appId.startsWith(APP_DEV_PREFIX)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.isProdAppID = appId => {
|
export function isProdAppID(appId?: string) {
|
||||||
if (!appId) {
|
if (!appId) {
|
||||||
throw NO_APP_ERROR
|
throw NO_APP_ERROR
|
||||||
}
|
}
|
||||||
return appId.startsWith(APP_PREFIX) && !exports.isDevAppID(appId)
|
return appId.startsWith(APP_PREFIX) && !isDevAppID(appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.isDevApp = app => {
|
export function isDevApp(app: App) {
|
||||||
if (!app) {
|
if (!app) {
|
||||||
throw NO_APP_ERROR
|
throw NO_APP_ERROR
|
||||||
}
|
}
|
||||||
return exports.isDevAppID(app.appId)
|
return isDevAppID(app.appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a development app ID from a real app ID.
|
* Generates a development app ID from a real app ID.
|
||||||
* @returns {string} the dev app ID which can be used for dev database.
|
* @returns {string} the dev app ID which can be used for dev database.
|
||||||
*/
|
*/
|
||||||
exports.getDevelopmentAppID = appId => {
|
export function getDevelopmentAppID(appId: string) {
|
||||||
if (!appId || appId.startsWith(APP_DEV_PREFIX)) {
|
if (!appId || appId.startsWith(APP_DEV_PREFIX)) {
|
||||||
return appId
|
return appId
|
||||||
}
|
}
|
||||||
|
@ -36,12 +37,12 @@ exports.getDevelopmentAppID = appId => {
|
||||||
const rest = split.join(APP_PREFIX)
|
const rest = split.join(APP_PREFIX)
|
||||||
return `${APP_DEV_PREFIX}${rest}`
|
return `${APP_DEV_PREFIX}${rest}`
|
||||||
}
|
}
|
||||||
exports.getDevAppID = exports.getDevelopmentAppID
|
export const getDevAppID = getDevelopmentAppID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a development app ID to a deployed app ID.
|
* Convert a development app ID to a deployed app ID.
|
||||||
*/
|
*/
|
||||||
exports.getProdAppID = appId => {
|
export function getProdAppID(appId: string) {
|
||||||
if (!appId || !appId.startsWith(APP_DEV_PREFIX)) {
|
if (!appId || !appId.startsWith(APP_DEV_PREFIX)) {
|
||||||
return appId
|
return appId
|
||||||
}
|
}
|
||||||
|
@ -52,7 +53,7 @@ exports.getProdAppID = appId => {
|
||||||
return `${APP_PREFIX}${rest}`
|
return `${APP_PREFIX}${rest}`
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.extractAppUUID = id => {
|
export function extractAppUUID(id: string) {
|
||||||
const split = id?.split("_") || []
|
const split = id?.split("_") || []
|
||||||
return split.length ? split[split.length - 1] : null
|
return split.length ? split[split.length - 1] : null
|
||||||
}
|
}
|
|
@ -0,0 +1,193 @@
|
||||||
|
import Nano from "nano"
|
||||||
|
import {
|
||||||
|
AllDocsResponse,
|
||||||
|
AnyDocument,
|
||||||
|
Database,
|
||||||
|
DatabaseOpts,
|
||||||
|
DatabaseQueryOpts,
|
||||||
|
DatabasePutOpts,
|
||||||
|
DatabaseCreateIndexOpts,
|
||||||
|
DatabaseDeleteIndexOpts,
|
||||||
|
Document,
|
||||||
|
isDocument,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import { getCouchInfo } from "./connections"
|
||||||
|
import { directCouchCall } from "./utils"
|
||||||
|
import { getPouchDB } from "./pouchDB"
|
||||||
|
import { WriteStream, ReadStream } from "fs"
|
||||||
|
|
||||||
|
export class DatabaseImpl implements Database {
|
||||||
|
public readonly name: string
|
||||||
|
private static nano: Nano.ServerScope
|
||||||
|
private readonly pouchOpts: DatabaseOpts
|
||||||
|
|
||||||
|
constructor(dbName?: string, opts?: DatabaseOpts) {
|
||||||
|
if (dbName == null) {
|
||||||
|
throw new Error("Database name cannot be undefined.")
|
||||||
|
}
|
||||||
|
this.name = dbName
|
||||||
|
this.pouchOpts = opts || {}
|
||||||
|
if (!DatabaseImpl.nano) {
|
||||||
|
DatabaseImpl.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static init() {
|
||||||
|
const couchInfo = getCouchInfo()
|
||||||
|
DatabaseImpl.nano = Nano({
|
||||||
|
url: couchInfo.url,
|
||||||
|
requestDefaults: {
|
||||||
|
headers: {
|
||||||
|
Authorization: couchInfo.cookie,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parseUrl: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async exists() {
|
||||||
|
let response = await directCouchCall(`/${this.name}`, "HEAD")
|
||||||
|
return response.status === 200
|
||||||
|
}
|
||||||
|
|
||||||
|
async checkSetup() {
|
||||||
|
let shouldCreate = !this.pouchOpts?.skip_setup
|
||||||
|
// check exists in a lightweight fashion
|
||||||
|
let exists = await this.exists()
|
||||||
|
if (!shouldCreate && !exists) {
|
||||||
|
throw new Error("DB does not exist")
|
||||||
|
}
|
||||||
|
if (!exists) {
|
||||||
|
await DatabaseImpl.nano.db.create(this.name)
|
||||||
|
}
|
||||||
|
return DatabaseImpl.nano.db.use(this.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateOutput(fnc: any) {
|
||||||
|
try {
|
||||||
|
return await fnc()
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.statusCode) {
|
||||||
|
err.status = err.statusCode
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async get<T>(id?: string): Promise<T | any> {
|
||||||
|
const db = await this.checkSetup()
|
||||||
|
if (!id) {
|
||||||
|
throw new Error("Unable to get doc without a valid _id.")
|
||||||
|
}
|
||||||
|
return this.updateOutput(() => db.get(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(idOrDoc: string | Document, rev?: string) {
|
||||||
|
const db = await this.checkSetup()
|
||||||
|
let _id: string
|
||||||
|
let _rev: string
|
||||||
|
|
||||||
|
if (isDocument(idOrDoc)) {
|
||||||
|
_id = idOrDoc._id!
|
||||||
|
_rev = idOrDoc._rev!
|
||||||
|
} else {
|
||||||
|
_id = idOrDoc
|
||||||
|
_rev = rev!
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_id || !_rev) {
|
||||||
|
throw new Error("Unable to remove doc without a valid _id and _rev.")
|
||||||
|
}
|
||||||
|
return this.updateOutput(() => db.destroy(_id, _rev))
|
||||||
|
}
|
||||||
|
|
||||||
|
async put(document: AnyDocument, opts?: DatabasePutOpts) {
|
||||||
|
if (!document._id) {
|
||||||
|
throw new Error("Cannot store document without _id field.")
|
||||||
|
}
|
||||||
|
const db = await this.checkSetup()
|
||||||
|
if (!document.createdAt) {
|
||||||
|
document.createdAt = new Date().toISOString()
|
||||||
|
}
|
||||||
|
document.updatedAt = new Date().toISOString()
|
||||||
|
if (opts?.force && document._id) {
|
||||||
|
try {
|
||||||
|
const existing = await this.get(document._id)
|
||||||
|
if (existing) {
|
||||||
|
document._rev = existing._rev
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.status !== 404) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.updateOutput(() => db.insert(document))
|
||||||
|
}
|
||||||
|
|
||||||
|
async bulkDocs(documents: AnyDocument[]) {
|
||||||
|
const db = await this.checkSetup()
|
||||||
|
return this.updateOutput(() => db.bulk({ docs: documents }))
|
||||||
|
}
|
||||||
|
|
||||||
|
async allDocs<T>(params: DatabaseQueryOpts): Promise<AllDocsResponse<T>> {
|
||||||
|
const db = await this.checkSetup()
|
||||||
|
return this.updateOutput(() => db.list(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
async query<T>(
|
||||||
|
viewName: string,
|
||||||
|
params: DatabaseQueryOpts
|
||||||
|
): Promise<AllDocsResponse<T>> {
|
||||||
|
const db = await this.checkSetup()
|
||||||
|
const [database, view] = viewName.split("/")
|
||||||
|
return this.updateOutput(() => db.view(database, view, params))
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy() {
|
||||||
|
try {
|
||||||
|
await DatabaseImpl.nano.db.destroy(this.name)
|
||||||
|
} catch (err: any) {
|
||||||
|
// didn't exist, don't worry
|
||||||
|
if (err.statusCode === 404) {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
throw { ...err, status: err.statusCode }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async compact() {
|
||||||
|
const db = await this.checkSetup()
|
||||||
|
return this.updateOutput(() => db.compact())
|
||||||
|
}
|
||||||
|
|
||||||
|
// All below functions are in-frequently called, just utilise PouchDB
|
||||||
|
// for them as it implements them better than we can
|
||||||
|
async dump(stream: WriteStream, opts?: { filter?: any }) {
|
||||||
|
const pouch = getPouchDB(this.name)
|
||||||
|
// @ts-ignore
|
||||||
|
return pouch.dump(stream, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
async load(stream: ReadStream) {
|
||||||
|
const pouch = getPouchDB(this.name)
|
||||||
|
// @ts-ignore
|
||||||
|
return pouch.load(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
async createIndex(opts: DatabaseCreateIndexOpts) {
|
||||||
|
const pouch = getPouchDB(this.name)
|
||||||
|
return pouch.createIndex(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteIndex(opts: DatabaseDeleteIndexOpts) {
|
||||||
|
const pouch = getPouchDB(this.name)
|
||||||
|
return pouch.deleteIndex(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIndexes() {
|
||||||
|
const pouch = getPouchDB(this.name)
|
||||||
|
return pouch.getIndexes()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,37 @@
|
||||||
import PouchDB from "pouchdb"
|
import env from "../../environment"
|
||||||
import env from "../environment"
|
|
||||||
import { PouchOptions } from "@budibase/types"
|
export const getCouchInfo = () => {
|
||||||
|
const urlInfo = getUrlInfo()
|
||||||
|
let username
|
||||||
|
let password
|
||||||
|
if (env.COUCH_DB_USERNAME) {
|
||||||
|
// set from env
|
||||||
|
username = env.COUCH_DB_USERNAME
|
||||||
|
} else if (urlInfo.auth.username) {
|
||||||
|
// set from url
|
||||||
|
username = urlInfo.auth.username
|
||||||
|
} else if (!env.isTest()) {
|
||||||
|
throw new Error("CouchDB username not set")
|
||||||
|
}
|
||||||
|
if (env.COUCH_DB_PASSWORD) {
|
||||||
|
// set from env
|
||||||
|
password = env.COUCH_DB_PASSWORD
|
||||||
|
} else if (urlInfo.auth.password) {
|
||||||
|
// set from url
|
||||||
|
password = urlInfo.auth.password
|
||||||
|
} else if (!env.isTest()) {
|
||||||
|
throw new Error("CouchDB password not set")
|
||||||
|
}
|
||||||
|
const authCookie = Buffer.from(`${username}:${password}`).toString("base64")
|
||||||
|
return {
|
||||||
|
url: urlInfo.url!,
|
||||||
|
auth: {
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
},
|
||||||
|
cookie: `Basic ${authCookie}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const getUrlInfo = (url = env.COUCH_DB_URL) => {
|
export const getUrlInfo = (url = env.COUCH_DB_URL) => {
|
||||||
let cleanUrl, username, password, host
|
let cleanUrl, username, password, host
|
||||||
|
@ -44,85 +75,3 @@ export const getUrlInfo = (url = env.COUCH_DB_URL) => {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCouchInfo = () => {
|
|
||||||
const urlInfo = getUrlInfo()
|
|
||||||
let username
|
|
||||||
let password
|
|
||||||
if (env.COUCH_DB_USERNAME) {
|
|
||||||
// set from env
|
|
||||||
username = env.COUCH_DB_USERNAME
|
|
||||||
} else if (urlInfo.auth.username) {
|
|
||||||
// set from url
|
|
||||||
username = urlInfo.auth.username
|
|
||||||
} else if (!env.isTest()) {
|
|
||||||
throw new Error("CouchDB username not set")
|
|
||||||
}
|
|
||||||
if (env.COUCH_DB_PASSWORD) {
|
|
||||||
// set from env
|
|
||||||
password = env.COUCH_DB_PASSWORD
|
|
||||||
} else if (urlInfo.auth.password) {
|
|
||||||
// set from url
|
|
||||||
password = urlInfo.auth.password
|
|
||||||
} else if (!env.isTest()) {
|
|
||||||
throw new Error("CouchDB password not set")
|
|
||||||
}
|
|
||||||
const authCookie = Buffer.from(`${username}:${password}`).toString("base64")
|
|
||||||
return {
|
|
||||||
url: urlInfo.url,
|
|
||||||
auth: {
|
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
},
|
|
||||||
cookie: `Basic ${authCookie}`,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a constructor for PouchDB.
|
|
||||||
* This should be rarely used outside of the main application config.
|
|
||||||
* Exposed for exceptional cases such as in-memory views.
|
|
||||||
*/
|
|
||||||
export const getPouch = (opts: PouchOptions = {}) => {
|
|
||||||
let { url, cookie } = getCouchInfo()
|
|
||||||
let POUCH_DB_DEFAULTS = {
|
|
||||||
prefix: url,
|
|
||||||
fetch: (url: string, opts: any) => {
|
|
||||||
// use a specific authorization cookie - be very explicit about how we authenticate
|
|
||||||
opts.headers.set("Authorization", cookie)
|
|
||||||
return PouchDB.fetch(url, opts)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.inMemory) {
|
|
||||||
const inMemory = require("pouchdb-adapter-memory")
|
|
||||||
PouchDB.plugin(inMemory)
|
|
||||||
POUCH_DB_DEFAULTS = {
|
|
||||||
prefix: undefined,
|
|
||||||
// @ts-ignore
|
|
||||||
adapter: "memory",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.onDisk) {
|
|
||||||
POUCH_DB_DEFAULTS = {
|
|
||||||
prefix: undefined,
|
|
||||||
// @ts-ignore
|
|
||||||
adapter: "leveldb",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.replication) {
|
|
||||||
const replicationStream = require("pouchdb-replication-stream")
|
|
||||||
PouchDB.plugin(replicationStream.plugin)
|
|
||||||
// @ts-ignore
|
|
||||||
PouchDB.adapter("writableStream", replicationStream.adapters.writableStream)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.find) {
|
|
||||||
const find = require("pouchdb-find")
|
|
||||||
PouchDB.plugin(find)
|
|
||||||
}
|
|
||||||
|
|
||||||
return PouchDB.defaults(POUCH_DB_DEFAULTS)
|
|
||||||
}
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export * from "./connections"
|
||||||
|
export * from "./DatabaseImpl"
|
||||||
|
export * from "./utils"
|
||||||
|
export { init, getPouch, getPouchDB, closePouchDB } from "./pouchDB"
|
|
@ -0,0 +1,97 @@
|
||||||
|
import PouchDB from "pouchdb"
|
||||||
|
import env from "../../environment"
|
||||||
|
import { PouchOptions } from "@budibase/types"
|
||||||
|
import { getCouchInfo } from "./connections"
|
||||||
|
|
||||||
|
let Pouch: any
|
||||||
|
let initialised = false
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a constructor for PouchDB.
|
||||||
|
* This should be rarely used outside of the main application config.
|
||||||
|
* Exposed for exceptional cases such as in-memory views.
|
||||||
|
*/
|
||||||
|
export const getPouch = (opts: PouchOptions = {}) => {
|
||||||
|
let { url, cookie } = getCouchInfo()
|
||||||
|
let POUCH_DB_DEFAULTS = {
|
||||||
|
prefix: url,
|
||||||
|
fetch: (url: string, opts: any) => {
|
||||||
|
// use a specific authorization cookie - be very explicit about how we authenticate
|
||||||
|
opts.headers.set("Authorization", cookie)
|
||||||
|
return PouchDB.fetch(url, opts)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.inMemory) {
|
||||||
|
const inMemory = require("pouchdb-adapter-memory")
|
||||||
|
PouchDB.plugin(inMemory)
|
||||||
|
POUCH_DB_DEFAULTS = {
|
||||||
|
// @ts-ignore
|
||||||
|
adapter: "memory",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.onDisk) {
|
||||||
|
POUCH_DB_DEFAULTS = {
|
||||||
|
// @ts-ignore
|
||||||
|
adapter: "leveldb",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.replication) {
|
||||||
|
const replicationStream = require("pouchdb-replication-stream")
|
||||||
|
PouchDB.plugin(replicationStream.plugin)
|
||||||
|
// @ts-ignore
|
||||||
|
PouchDB.adapter("writableStream", replicationStream.adapters.writableStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.find) {
|
||||||
|
const find = require("pouchdb-find")
|
||||||
|
PouchDB.plugin(find)
|
||||||
|
}
|
||||||
|
|
||||||
|
return PouchDB.defaults(POUCH_DB_DEFAULTS)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function init(opts?: PouchOptions) {
|
||||||
|
Pouch = getPouch(opts)
|
||||||
|
initialised = true
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkInitialised = () => {
|
||||||
|
if (!initialised) {
|
||||||
|
throw new Error("init has not been called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPouchDB(dbName: string, opts?: any): PouchDB.Database {
|
||||||
|
checkInitialised()
|
||||||
|
const db = new Pouch(dbName, opts)
|
||||||
|
const dbPut = db.put
|
||||||
|
db.put = async (doc: any, options = {}) => {
|
||||||
|
if (!doc.createdAt) {
|
||||||
|
doc.createdAt = new Date().toISOString()
|
||||||
|
}
|
||||||
|
doc.updatedAt = new Date().toISOString()
|
||||||
|
return dbPut(doc, options)
|
||||||
|
}
|
||||||
|
db.exists = async () => {
|
||||||
|
const info = await db.info()
|
||||||
|
return !info.error
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
// use this function if you have called getPouchDB - close
|
||||||
|
// the databases you've opened once finished
|
||||||
|
export async function closePouchDB(db: PouchDB.Database) {
|
||||||
|
if (!db || env.isTest()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// specifically await so that if there is an error, it can be ignored
|
||||||
|
return await db.close()
|
||||||
|
} catch (err) {
|
||||||
|
// ignore error, already closed
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { getCouchInfo } from "./connections"
|
||||||
|
import fetch from "node-fetch"
|
||||||
|
import { checkSlashesInUrl } from "../../helpers"
|
||||||
|
|
||||||
|
export async function directCouchCall(
|
||||||
|
path: string,
|
||||||
|
method: string = "GET",
|
||||||
|
body?: any
|
||||||
|
) {
|
||||||
|
let { url, cookie } = getCouchInfo()
|
||||||
|
const couchUrl = `${url}/${path}`
|
||||||
|
const params: any = {
|
||||||
|
method: method,
|
||||||
|
headers: {
|
||||||
|
Authorization: cookie,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if (body && method !== "GET") {
|
||||||
|
params.body = JSON.stringify(body)
|
||||||
|
params.headers["Content-Type"] = "application/json"
|
||||||
|
}
|
||||||
|
return await fetch(checkSlashesInUrl(encodeURI(couchUrl)), params)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function directCouchQuery(
|
||||||
|
path: string,
|
||||||
|
method: string = "GET",
|
||||||
|
body?: any
|
||||||
|
) {
|
||||||
|
const response = await directCouchCall(path, method, body)
|
||||||
|
if (response.status < 300) {
|
||||||
|
return await response.json()
|
||||||
|
} else {
|
||||||
|
throw "Cannot connect to CouchDB instance"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import env from "../environment"
|
||||||
|
import { directCouchQuery, getPouchDB } from "./couch"
|
||||||
|
import { CouchFindOptions, Database } from "@budibase/types"
|
||||||
|
import { DatabaseImpl } from "../db"
|
||||||
|
|
||||||
|
const dbList = new Set()
|
||||||
|
|
||||||
|
export function getDB(dbName?: string, opts?: any): Database {
|
||||||
|
// TODO: once using the test image, need to remove this
|
||||||
|
if (env.isTest()) {
|
||||||
|
dbList.add(dbName)
|
||||||
|
// @ts-ignore
|
||||||
|
return getPouchDB(dbName, opts)
|
||||||
|
}
|
||||||
|
return new DatabaseImpl(dbName, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have to use a callback for this so that we can close
|
||||||
|
// the DB when we're done, without this manual requests would
|
||||||
|
// need to close the database when done with it to avoid memory leaks
|
||||||
|
export async function doWithDB(dbName: string, cb: any, opts = {}) {
|
||||||
|
const db = getDB(dbName, opts)
|
||||||
|
// need this to be async so that we can correctly close DB after all
|
||||||
|
// async operations have been completed
|
||||||
|
return await cb(db)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function allDbs() {
|
||||||
|
if (!env.isTest()) {
|
||||||
|
throw new Error("Cannot be used outside test environment.")
|
||||||
|
}
|
||||||
|
return [...dbList]
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function directCouchAllDbs(queryString?: string) {
|
||||||
|
let couchPath = "/_all_dbs"
|
||||||
|
if (queryString) {
|
||||||
|
couchPath += `?${queryString}`
|
||||||
|
}
|
||||||
|
return await directCouchQuery(couchPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function directCouchFind(dbName: string, opts: CouchFindOptions) {
|
||||||
|
const json = await directCouchQuery(`${dbName}/_find`, "POST", opts)
|
||||||
|
return { rows: json.docs, bookmark: json.bookmark }
|
||||||
|
}
|
|
@ -1,133 +1,7 @@
|
||||||
import * as pouch from "./pouch"
|
export * from "./couch"
|
||||||
import env from "../environment"
|
export * from "./db"
|
||||||
import { checkSlashesInUrl } from "../helpers"
|
export * from "./utils"
|
||||||
import fetch from "node-fetch"
|
export * from "./views"
|
||||||
import { PouchOptions, CouchFindOptions } from "@budibase/types"
|
export * from "./constants"
|
||||||
import PouchDB from "pouchdb"
|
export * from "./conversions"
|
||||||
|
export * from "./tenancy"
|
||||||
const openDbs: string[] = []
|
|
||||||
let Pouch: any
|
|
||||||
let initialised = false
|
|
||||||
const dbList = new Set()
|
|
||||||
|
|
||||||
if (env.MEMORY_LEAK_CHECK) {
|
|
||||||
setInterval(() => {
|
|
||||||
console.log("--- OPEN DBS ---")
|
|
||||||
console.log(openDbs)
|
|
||||||
}, 5000)
|
|
||||||
}
|
|
||||||
|
|
||||||
const put =
|
|
||||||
(dbPut: any) =>
|
|
||||||
async (doc: any, options = {}) => {
|
|
||||||
if (!doc.createdAt) {
|
|
||||||
doc.createdAt = new Date().toISOString()
|
|
||||||
}
|
|
||||||
doc.updatedAt = new Date().toISOString()
|
|
||||||
return dbPut(doc, options)
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkInitialised = () => {
|
|
||||||
if (!initialised) {
|
|
||||||
throw new Error("init has not been called")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function init(opts?: PouchOptions) {
|
|
||||||
Pouch = pouch.getPouch(opts)
|
|
||||||
initialised = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: THIS IS A DANGEROUS FUNCTION - USE WITH CAUTION
|
|
||||||
// this function is prone to leaks, should only be used
|
|
||||||
// in situations that using the function doWithDB does not work
|
|
||||||
export function dangerousGetDB(dbName: string, opts?: any): PouchDB.Database {
|
|
||||||
checkInitialised()
|
|
||||||
if (env.isTest()) {
|
|
||||||
dbList.add(dbName)
|
|
||||||
}
|
|
||||||
const db = new Pouch(dbName, opts)
|
|
||||||
if (env.MEMORY_LEAK_CHECK) {
|
|
||||||
openDbs.push(db.name)
|
|
||||||
}
|
|
||||||
const dbPut = db.put
|
|
||||||
db.put = put(dbPut)
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
// use this function if you have called dangerousGetDB - close
|
|
||||||
// the databases you've opened once finished
|
|
||||||
export async function closeDB(db: PouchDB.Database) {
|
|
||||||
if (!db || env.isTest()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (env.MEMORY_LEAK_CHECK) {
|
|
||||||
openDbs.splice(openDbs.indexOf(db.name), 1)
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// specifically await so that if there is an error, it can be ignored
|
|
||||||
return await db.close()
|
|
||||||
} catch (err) {
|
|
||||||
// ignore error, already closed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have to use a callback for this so that we can close
|
|
||||||
// the DB when we're done, without this manual requests would
|
|
||||||
// need to close the database when done with it to avoid memory leaks
|
|
||||||
export async function doWithDB(dbName: string, cb: any, opts = {}) {
|
|
||||||
const db = dangerousGetDB(dbName, opts)
|
|
||||||
// need this to be async so that we can correctly close DB after all
|
|
||||||
// async operations have been completed
|
|
||||||
try {
|
|
||||||
return await cb(db)
|
|
||||||
} finally {
|
|
||||||
await closeDB(db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function allDbs() {
|
|
||||||
if (!env.isTest()) {
|
|
||||||
throw new Error("Cannot be used outside test environment.")
|
|
||||||
}
|
|
||||||
checkInitialised()
|
|
||||||
return [...dbList]
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function directCouchQuery(
|
|
||||||
path: string,
|
|
||||||
method: string = "GET",
|
|
||||||
body?: any
|
|
||||||
) {
|
|
||||||
let { url, cookie } = pouch.getCouchInfo()
|
|
||||||
const couchUrl = `${url}/${path}`
|
|
||||||
const params: any = {
|
|
||||||
method: method,
|
|
||||||
headers: {
|
|
||||||
Authorization: cookie,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if (body && method !== "GET") {
|
|
||||||
params.body = JSON.stringify(body)
|
|
||||||
params.headers["Content-Type"] = "application/json"
|
|
||||||
}
|
|
||||||
const response = await fetch(checkSlashesInUrl(encodeURI(couchUrl)), params)
|
|
||||||
if (response.status < 300) {
|
|
||||||
return await response.json()
|
|
||||||
} else {
|
|
||||||
throw "Cannot connect to CouchDB instance"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function directCouchAllDbs(queryString?: string) {
|
|
||||||
let couchPath = "/_all_dbs"
|
|
||||||
if (queryString) {
|
|
||||||
couchPath += `?${queryString}`
|
|
||||||
}
|
|
||||||
return await directCouchQuery(couchPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function directCouchFind(dbName: string, opts: CouchFindOptions) {
|
|
||||||
const json = await directCouchQuery(`${dbName}/_find`, "POST", opts)
|
|
||||||
return { rows: json.docs, bookmark: json.bookmark }
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
require("../../../tests")
|
require("../../../tests")
|
||||||
const { dangerousGetDB } = require("../")
|
const { getDB } = require("../")
|
||||||
|
|
||||||
describe("db", () => {
|
describe("db", () => {
|
||||||
|
|
||||||
describe("getDB", () => {
|
describe("getDB", () => {
|
||||||
it("returns a db", async () => {
|
it("returns a db", async () => {
|
||||||
const db = dangerousGetDB("test")
|
const db = getDB("test")
|
||||||
expect(db).toBeDefined()
|
expect(db).toBeDefined()
|
||||||
expect(db._adapter).toBe("memory")
|
expect(db._adapter).toBe("memory")
|
||||||
expect(db.prefix).toBe("_pouch_")
|
expect(db.prefix).toBe("_pouch_")
|
||||||
|
@ -13,7 +13,7 @@ describe("db", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("uses the custom put function", async () => {
|
it("uses the custom put function", async () => {
|
||||||
const db = dangerousGetDB("test")
|
const db = getDB("test")
|
||||||
let doc = { _id: "test" }
|
let doc = { _id: "test" }
|
||||||
await db.put(doc)
|
await db.put(doc)
|
||||||
doc = await db.get(doc._id)
|
doc = await db.get(doc._id)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
require("../../../tests")
|
require("../../../tests")
|
||||||
const getUrlInfo = require("../pouch").getUrlInfo
|
const getUrlInfo = require("../couch").getUrlInfo
|
||||||
|
|
||||||
describe("pouch", () => {
|
describe("pouch", () => {
|
||||||
describe("Couch DB URL parsing", () => {
|
describe("Couch DB URL parsing", () => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
require("../../../tests");
|
require("../../../tests")
|
||||||
const {
|
const {
|
||||||
generateAppID,
|
generateAppID,
|
||||||
getDevelopmentAppID,
|
getDevelopmentAppID,
|
||||||
|
@ -8,8 +8,8 @@ const {
|
||||||
getPlatformUrl,
|
getPlatformUrl,
|
||||||
getScopedConfig
|
getScopedConfig
|
||||||
} = require("../utils")
|
} = require("../utils")
|
||||||
const tenancy = require("../../tenancy");
|
const tenancy = require("../../tenancy")
|
||||||
const { Configs, DEFAULT_TENANT_ID } = require("../../constants");
|
const { Config, DEFAULT_TENANT_ID } = require("../../constants")
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
|
|
||||||
describe("utils", () => {
|
describe("utils", () => {
|
||||||
|
@ -77,7 +77,7 @@ const setDbPlatformUrl = async () => {
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
db.put({
|
db.put({
|
||||||
_id: "config_settings",
|
_id: "config_settings",
|
||||||
type: Configs.SETTINGS,
|
type: Config.SETTINGS,
|
||||||
config: {
|
config: {
|
||||||
platformUrl: DB_URL
|
platformUrl: DB_URL
|
||||||
}
|
}
|
||||||
|
@ -178,7 +178,7 @@ describe("getScopedConfig", () => {
|
||||||
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||||
await setDbPlatformUrl()
|
await setDbPlatformUrl()
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
const config = await getScopedConfig(db, { type: Configs.SETTINGS })
|
const config = await getScopedConfig(db, { type: Config.SETTINGS })
|
||||||
expect(config.platformUrl).toBe(DB_URL)
|
expect(config.platformUrl).toBe(DB_URL)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -186,7 +186,7 @@ describe("getScopedConfig", () => {
|
||||||
it("returns the platform url without an existing config", async () => {
|
it("returns the platform url without an existing config", async () => {
|
||||||
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
await tenancy.doInTenant(DEFAULT_TENANT_ID, async () => {
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
const config = await getScopedConfig(db, { type: Configs.SETTINGS })
|
const config = await getScopedConfig(db, { type: Config.SETTINGS })
|
||||||
expect(config.platformUrl).toBe(DEFAULT_URL)
|
expect(config.platformUrl).toBe(DEFAULT_URL)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { newid } from "../hashing"
|
import { newid } from "../hashing"
|
||||||
import { DEFAULT_TENANT_ID, Configs } from "../constants"
|
import { DEFAULT_TENANT_ID, Config } from "../constants"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import {
|
import {
|
||||||
SEPARATOR,
|
SEPARATOR,
|
||||||
|
@ -10,12 +10,12 @@ import {
|
||||||
} from "./constants"
|
} from "./constants"
|
||||||
import { getTenantId, getGlobalDB } from "../context"
|
import { getTenantId, getGlobalDB } from "../context"
|
||||||
import { getGlobalDBName } from "./tenancy"
|
import { getGlobalDBName } from "./tenancy"
|
||||||
import { doWithDB, allDbs, directCouchAllDbs } from "./index"
|
import { doWithDB, allDbs, directCouchAllDbs } from "./db"
|
||||||
import { getAppMetadata } from "../cache/appMetadata"
|
import { getAppMetadata } from "../cache/appMetadata"
|
||||||
import { isDevApp, isDevAppID, getProdAppID } from "./conversions"
|
import { isDevApp, isDevAppID, getProdAppID } from "./conversions"
|
||||||
import { APP_PREFIX } from "./constants"
|
import { APP_PREFIX } from "./constants"
|
||||||
import * as events from "../events"
|
import * as events from "../events"
|
||||||
import { App } from "@budibase/types"
|
import { App, Database } from "@budibase/types"
|
||||||
|
|
||||||
export * from "./constants"
|
export * from "./constants"
|
||||||
export * from "./conversions"
|
export * from "./conversions"
|
||||||
|
@ -26,7 +26,7 @@ export * from "./tenancy"
|
||||||
* Generates a new app ID.
|
* Generates a new app ID.
|
||||||
* @returns {string} The new app ID which the app doc can be stored under.
|
* @returns {string} The new app ID which the app doc can be stored under.
|
||||||
*/
|
*/
|
||||||
export const generateAppID = (tenantId = null) => {
|
export const generateAppID = (tenantId?: string | null) => {
|
||||||
let id = APP_PREFIX
|
let id = APP_PREFIX
|
||||||
if (tenantId) {
|
if (tenantId) {
|
||||||
id += `${tenantId}${SEPARATOR}`
|
id += `${tenantId}${SEPARATOR}`
|
||||||
|
@ -251,11 +251,11 @@ export function generateRoleID(id: any) {
|
||||||
/**
|
/**
|
||||||
* Gets parameters for retrieving a role, this is a utility function for the getDocParams function.
|
* Gets parameters for retrieving a role, this is a utility function for the getDocParams function.
|
||||||
*/
|
*/
|
||||||
export function getRoleParams(roleId = null, otherProps = {}) {
|
export function getRoleParams(roleId?: string | null, otherProps = {}) {
|
||||||
return getDocParams(DocumentType.ROLE, roleId, otherProps)
|
return getDocParams(DocumentType.ROLE, roleId, otherProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getStartEndKeyURL(baseKey: any, tenantId = null) {
|
export function getStartEndKeyURL(baseKey: any, tenantId?: string) {
|
||||||
const tenancy = tenantId ? `${SEPARATOR}${tenantId}` : ""
|
const tenancy = tenantId ? `${SEPARATOR}${tenantId}` : ""
|
||||||
return `startkey="${baseKey}${tenancy}"&endkey="${baseKey}${tenancy}${UNICODE_MAX}"`
|
return `startkey="${baseKey}${tenancy}"&endkey="${baseKey}${tenancy}${UNICODE_MAX}"`
|
||||||
}
|
}
|
||||||
|
@ -392,20 +392,10 @@ export async function getDevAppIDs() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function dbExists(dbName: any) {
|
export async function dbExists(dbName: any) {
|
||||||
let exists = false
|
|
||||||
return doWithDB(
|
return doWithDB(
|
||||||
dbName,
|
dbName,
|
||||||
async (db: any) => {
|
async (db: Database) => {
|
||||||
try {
|
return await db.exists()
|
||||||
// check if database exists
|
|
||||||
const info = await db.info()
|
|
||||||
if (info && !info.error) {
|
|
||||||
exists = true
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
exists = false
|
|
||||||
}
|
|
||||||
return exists
|
|
||||||
},
|
},
|
||||||
{ skip_setup: true }
|
{ skip_setup: true }
|
||||||
)
|
)
|
||||||
|
@ -504,7 +494,7 @@ export const getScopedFullConfig = async function (
|
||||||
)[0]
|
)[0]
|
||||||
|
|
||||||
// custom logic for settings doc
|
// custom logic for settings doc
|
||||||
if (type === Configs.SETTINGS) {
|
if (type === Config.SETTINGS) {
|
||||||
if (scopedConfig && scopedConfig.doc) {
|
if (scopedConfig && scopedConfig.doc) {
|
||||||
// overrides affected by environment variables
|
// overrides affected by environment variables
|
||||||
scopedConfig.doc.config.platformUrl = await getPlatformUrl({
|
scopedConfig.doc.config.platformUrl = await getPlatformUrl({
|
||||||
|
@ -543,7 +533,7 @@ export const getPlatformUrl = async (opts = { tenantAware: true }) => {
|
||||||
// get the doc directly instead of with getScopedConfig to prevent loop
|
// get the doc directly instead of with getScopedConfig to prevent loop
|
||||||
let settings
|
let settings
|
||||||
try {
|
try {
|
||||||
settings = await db.get(generateConfigID({ type: Configs.SETTINGS }))
|
settings = await db.get(generateConfigID({ type: Config.SETTINGS }))
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.status !== 404) {
|
if (e.status !== 404) {
|
||||||
throw e
|
throw e
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { DocumentType, ViewName, DeprecatedViews, SEPARATOR } from "./utils"
|
import { DocumentType, ViewName, DeprecatedViews, SEPARATOR } from "./utils"
|
||||||
import { getGlobalDB } from "../context"
|
import { getGlobalDB } from "../context"
|
||||||
import PouchDB from "pouchdb"
|
|
||||||
import { StaticDatabases } from "./constants"
|
import { StaticDatabases } from "./constants"
|
||||||
import { doWithDB } from "./"
|
import { doWithDB } from "./"
|
||||||
|
import { Database, DatabaseQueryOpts } from "@budibase/types"
|
||||||
|
|
||||||
const DESIGN_DB = "_design/database"
|
const DESIGN_DB = "_design/database"
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ interface DesignDocument {
|
||||||
views: any
|
views: any
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeDeprecated(db: PouchDB.Database, viewName: ViewName) {
|
async function removeDeprecated(db: Database, viewName: ViewName) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (!DeprecatedViews[viewName]) {
|
if (!DeprecatedViews[viewName]) {
|
||||||
return
|
return
|
||||||
|
@ -70,16 +70,13 @@ export const createAccountEmailView = async () => {
|
||||||
emit(doc.email.toLowerCase(), doc._id)
|
emit(doc.email.toLowerCase(), doc._id)
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
await doWithDB(
|
await doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: Database) => {
|
||||||
StaticDatabases.PLATFORM_INFO.name,
|
|
||||||
async (db: PouchDB.Database) => {
|
|
||||||
await createView(db, viewJs, ViewName.ACCOUNT_BY_EMAIL)
|
await createView(db, viewJs, ViewName.ACCOUNT_BY_EMAIL)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createUserAppView = async () => {
|
export const createUserAppView = async () => {
|
||||||
const db = getGlobalDB() as PouchDB.Database
|
const db = getGlobalDB()
|
||||||
const viewJs = `function(doc) {
|
const viewJs = `function(doc) {
|
||||||
if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}") && doc.roles) {
|
if (doc._id.startsWith("${DocumentType.USER}${SEPARATOR}") && doc.roles) {
|
||||||
for (let prodAppId of Object.keys(doc.roles)) {
|
for (let prodAppId of Object.keys(doc.roles)) {
|
||||||
|
@ -117,12 +114,9 @@ export const createPlatformUserView = async () => {
|
||||||
emit(doc._id.toLowerCase(), doc._id)
|
emit(doc._id.toLowerCase(), doc._id)
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
await doWithDB(
|
await doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: Database) => {
|
||||||
StaticDatabases.PLATFORM_INFO.name,
|
|
||||||
async (db: PouchDB.Database) => {
|
|
||||||
await createView(db, viewJs, ViewName.PLATFORM_USERS_LOWERCASE)
|
await createView(db, viewJs, ViewName.PLATFORM_USERS_LOWERCASE)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface QueryViewOptions {
|
export interface QueryViewOptions {
|
||||||
|
@ -131,22 +125,24 @@ export interface QueryViewOptions {
|
||||||
|
|
||||||
export const queryView = async <T>(
|
export const queryView = async <T>(
|
||||||
viewName: ViewName,
|
viewName: ViewName,
|
||||||
params: PouchDB.Query.Options<T, T>,
|
params: DatabaseQueryOpts,
|
||||||
db: PouchDB.Database,
|
db: Database,
|
||||||
createFunc: any,
|
createFunc: any,
|
||||||
opts?: QueryViewOptions
|
opts?: QueryViewOptions
|
||||||
): Promise<T[] | T | undefined> => {
|
): Promise<T[] | T | undefined> => {
|
||||||
try {
|
try {
|
||||||
let response = await db.query<T, T>(`database/${viewName}`, params)
|
let response = await db.query<T>(`database/${viewName}`, params)
|
||||||
const rows = response.rows
|
const rows = response.rows
|
||||||
const docs = rows.map(row => (params.include_docs ? row.doc : row.value))
|
const docs = rows.map((row: any) =>
|
||||||
|
params.include_docs ? row.doc : row.value
|
||||||
|
)
|
||||||
|
|
||||||
// if arrayResponse has been requested, always return array regardless of length
|
// if arrayResponse has been requested, always return array regardless of length
|
||||||
if (opts?.arrayResponse) {
|
if (opts?.arrayResponse) {
|
||||||
return docs
|
return docs as T[]
|
||||||
} else {
|
} else {
|
||||||
// return the single document if there is only one
|
// return the single document if there is only one
|
||||||
return docs.length <= 1 ? docs[0] : docs
|
return docs.length <= 1 ? (docs[0] as T) : (docs as T[])
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err != null && err.name === "not_found") {
|
if (err != null && err.name === "not_found") {
|
||||||
|
@ -161,7 +157,7 @@ export const queryView = async <T>(
|
||||||
|
|
||||||
export const queryPlatformView = async <T>(
|
export const queryPlatformView = async <T>(
|
||||||
viewName: ViewName,
|
viewName: ViewName,
|
||||||
params: PouchDB.Query.Options<T, T>,
|
params: DatabaseQueryOpts,
|
||||||
opts?: QueryViewOptions
|
opts?: QueryViewOptions
|
||||||
): Promise<T[] | T | undefined> => {
|
): Promise<T[] | T | undefined> => {
|
||||||
const CreateFuncByName: any = {
|
const CreateFuncByName: any = {
|
||||||
|
@ -169,19 +165,16 @@ export const queryPlatformView = async <T>(
|
||||||
[ViewName.PLATFORM_USERS_LOWERCASE]: createPlatformUserView,
|
[ViewName.PLATFORM_USERS_LOWERCASE]: createPlatformUserView,
|
||||||
}
|
}
|
||||||
|
|
||||||
return doWithDB(
|
return doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: Database) => {
|
||||||
StaticDatabases.PLATFORM_INFO.name,
|
|
||||||
async (db: PouchDB.Database) => {
|
|
||||||
const createFn = CreateFuncByName[viewName]
|
const createFn = CreateFuncByName[viewName]
|
||||||
return queryView(viewName, params, db, createFn, opts)
|
return queryView(viewName, params, db, createFn, opts)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const queryGlobalView = async <T>(
|
export const queryGlobalView = async <T>(
|
||||||
viewName: ViewName,
|
viewName: ViewName,
|
||||||
params: PouchDB.Query.Options<T, T>,
|
params: DatabaseQueryOpts,
|
||||||
db?: PouchDB.Database,
|
db?: Database,
|
||||||
opts?: QueryViewOptions
|
opts?: QueryViewOptions
|
||||||
): Promise<T[] | T | undefined> => {
|
): Promise<T[] | T | undefined> => {
|
||||||
const CreateFuncByName: any = {
|
const CreateFuncByName: any = {
|
||||||
|
@ -192,8 +185,8 @@ export const queryGlobalView = async <T>(
|
||||||
}
|
}
|
||||||
// can pass DB in if working with something specific
|
// can pass DB in if working with something specific
|
||||||
if (!db) {
|
if (!db) {
|
||||||
db = getGlobalDB() as PouchDB.Database
|
db = getGlobalDB()
|
||||||
}
|
}
|
||||||
const createFn = CreateFuncByName[viewName]
|
const createFn = CreateFuncByName[viewName]
|
||||||
return queryView(viewName, params, db, createFn, opts)
|
return queryView(viewName, params, db!, createFn, opts)
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,6 @@ const env = {
|
||||||
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
|
DISABLE_DEVELOPER_LICENSE: process.env.DISABLE_DEVELOPER_LICENSE,
|
||||||
DEFAULT_LICENSE: process.env.DEFAULT_LICENSE,
|
DEFAULT_LICENSE: process.env.DEFAULT_LICENSE,
|
||||||
SERVICE: process.env.SERVICE || "budibase",
|
SERVICE: process.env.SERVICE || "budibase",
|
||||||
MEMORY_LEAK_CHECK: process.env.MEMORY_LEAK_CHECK || false,
|
|
||||||
LOG_LEVEL: process.env.LOG_LEVEL,
|
LOG_LEVEL: process.env.LOG_LEVEL,
|
||||||
SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD,
|
SESSION_UPDATE_PERIOD: process.env.SESSION_UPDATE_PERIOD,
|
||||||
DEPLOYMENT_ENVIRONMENT:
|
DEPLOYMENT_ENVIRONMENT:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import tenancy from "../tenancy"
|
import tenancy from "../tenancy"
|
||||||
import * as dbUtils from "../db/utils"
|
import * as dbUtils from "../db/utils"
|
||||||
import { Configs } from "../constants"
|
import { Config } from "../constants"
|
||||||
import { withCache, TTL, CacheKeys } from "../cache/generic"
|
import { withCache, TTL, CacheKeys } from "../cache/generic"
|
||||||
|
|
||||||
export const enabled = async () => {
|
export const enabled = async () => {
|
||||||
|
@ -45,9 +45,7 @@ const getSettingsDoc = async () => {
|
||||||
const db = tenancy.getGlobalDB()
|
const db = tenancy.getGlobalDB()
|
||||||
let settings
|
let settings
|
||||||
try {
|
try {
|
||||||
settings = await db.get(
|
settings = await db.get(dbUtils.generateConfigID({ type: Config.SETTINGS }))
|
||||||
dbUtils.generateConfigID({ type: Configs.SETTINGS })
|
|
||||||
)
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.status !== 404) {
|
if (e.status !== 404) {
|
||||||
throw e
|
throw e
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { processors } from "./processors"
|
import { processors } from "./processors"
|
||||||
import * as dbUtils from "../db/utils"
|
import * as dbUtils from "../db/utils"
|
||||||
import { Configs } from "../constants"
|
import { Config } from "../constants"
|
||||||
import * as hashing from "../hashing"
|
import * as hashing from "../hashing"
|
||||||
import * as installation from "../installation"
|
import * as installation from "../installation"
|
||||||
import { withCache, TTL, CacheKeys } from "../cache/generic"
|
import { withCache, TTL, CacheKeys } from "../cache/generic"
|
||||||
|
@ -273,7 +273,7 @@ const getUniqueTenantId = async (tenantId: string): Promise<string> => {
|
||||||
return withCache(CacheKeys.UNIQUE_TENANT_ID, TTL.ONE_DAY, async () => {
|
return withCache(CacheKeys.UNIQUE_TENANT_ID, TTL.ONE_DAY, async () => {
|
||||||
const db = context.getGlobalDB()
|
const db = context.getGlobalDB()
|
||||||
const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, {
|
const config: SettingsConfig = await dbUtils.getScopedFullConfig(db, {
|
||||||
type: Configs.SETTINGS,
|
type: Config.SETTINGS,
|
||||||
})
|
})
|
||||||
|
|
||||||
let uniqueTenantId: string
|
let uniqueTenantId: string
|
||||||
|
|
|
@ -1,12 +1,34 @@
|
||||||
import { AppBackup, AppBackupRestoreEvent, Event } from "@budibase/types"
|
import {
|
||||||
|
AppBackup,
|
||||||
|
AppBackupRestoreEvent,
|
||||||
|
AppBackupTriggeredEvent,
|
||||||
|
AppBackupTrigger,
|
||||||
|
AppBackupType,
|
||||||
|
Event,
|
||||||
|
} from "@budibase/types"
|
||||||
import { publishEvent } from "../events"
|
import { publishEvent } from "../events"
|
||||||
|
|
||||||
export async function appBackupRestored(backup: AppBackup) {
|
export async function appBackupRestored(backup: AppBackup) {
|
||||||
const properties: AppBackupRestoreEvent = {
|
const properties: AppBackupRestoreEvent = {
|
||||||
appId: backup.appId,
|
appId: backup.appId,
|
||||||
backupName: backup.name!,
|
restoreId: backup._id!,
|
||||||
backupCreatedAt: backup.timestamp,
|
backupCreatedAt: backup.timestamp,
|
||||||
}
|
}
|
||||||
|
|
||||||
await publishEvent(Event.APP_BACKUP_RESTORED, properties)
|
await publishEvent(Event.APP_BACKUP_RESTORED, properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function appBackupTriggered(
|
||||||
|
appId: string,
|
||||||
|
backupId: string,
|
||||||
|
type: AppBackupType,
|
||||||
|
trigger: AppBackupTrigger
|
||||||
|
) {
|
||||||
|
const properties: AppBackupTriggeredEvent = {
|
||||||
|
appId: appId,
|
||||||
|
backupId,
|
||||||
|
type,
|
||||||
|
trigger,
|
||||||
|
}
|
||||||
|
await publishEvent(Event.APP_BACKUP_TRIGGERED, properties)
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import featureFlags from "./featureFlags"
|
||||||
import * as sessions from "./security/sessions"
|
import * as sessions from "./security/sessions"
|
||||||
import * as deprovisioning from "./context/deprovision"
|
import * as deprovisioning from "./context/deprovision"
|
||||||
import auth from "./auth"
|
import auth from "./auth"
|
||||||
import constants from "./constants"
|
import * as constants from "./constants"
|
||||||
import * as dbConstants from "./db/constants"
|
import * as dbConstants from "./db/constants"
|
||||||
import * as logging from "./logging"
|
import * as logging from "./logging"
|
||||||
import pino from "./pino"
|
import pino from "./pino"
|
||||||
|
@ -21,9 +21,9 @@ import * as middleware from "./middleware"
|
||||||
import plugins from "./plugin"
|
import plugins from "./plugin"
|
||||||
import encryption from "./security/encryption"
|
import encryption from "./security/encryption"
|
||||||
import * as queue from "./queue"
|
import * as queue from "./queue"
|
||||||
|
import * as db from "./db"
|
||||||
|
|
||||||
// mimic the outer package exports
|
// mimic the outer package exports
|
||||||
import * as db from "./pkg/db"
|
|
||||||
import * as objectStore from "./pkg/objectStore"
|
import * as objectStore from "./pkg/objectStore"
|
||||||
import * as utils from "./pkg/utils"
|
import * as utils from "./pkg/utils"
|
||||||
import redis from "./pkg/redis"
|
import redis from "./pkg/redis"
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import { Cookies, Headers } from "../constants"
|
import { Cookie, Header } from "../constants"
|
||||||
import { getCookie, clearCookie, openJwt } from "../utils"
|
import { getCookie, clearCookie, openJwt } from "../utils"
|
||||||
import { getUser } from "../cache/user"
|
import { getUser } from "../cache/user"
|
||||||
import { getSession, updateSessionTTL } from "../security/sessions"
|
import { getSession, updateSessionTTL } from "../security/sessions"
|
||||||
import { buildMatcherRegex, matches } from "./matchers"
|
import { buildMatcherRegex, matches } from "./matchers"
|
||||||
import { SEPARATOR } from "../db/constants"
|
import { SEPARATOR, queryGlobalView, ViewName } from "../db"
|
||||||
import { ViewName } from "../db/utils"
|
|
||||||
import { queryGlobalView } from "../db/views"
|
|
||||||
import { getGlobalDB, doInTenant } from "../tenancy"
|
import { getGlobalDB, doInTenant } from "../tenancy"
|
||||||
import { decrypt } from "../security/encryption"
|
import { decrypt } from "../security/encryption"
|
||||||
const identity = require("../context/identity")
|
const identity = require("../context/identity")
|
||||||
|
@ -74,7 +72,7 @@ export = (
|
||||||
const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : []
|
const noAuthOptions = noAuthPatterns ? buildMatcherRegex(noAuthPatterns) : []
|
||||||
return async (ctx: any, next: any) => {
|
return async (ctx: any, next: any) => {
|
||||||
let publicEndpoint = false
|
let publicEndpoint = false
|
||||||
const version = ctx.request.headers[Headers.API_VER]
|
const version = ctx.request.headers[Header.API_VER]
|
||||||
// the path is not authenticated
|
// the path is not authenticated
|
||||||
const found = matches(ctx, noAuthOptions)
|
const found = matches(ctx, noAuthOptions)
|
||||||
if (found) {
|
if (found) {
|
||||||
|
@ -82,10 +80,10 @@ export = (
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// check the actual user is authenticated first, try header or cookie
|
// check the actual user is authenticated first, try header or cookie
|
||||||
const headerToken = ctx.request.headers[Headers.TOKEN]
|
const headerToken = ctx.request.headers[Header.TOKEN]
|
||||||
const authCookie = getCookie(ctx, Cookies.Auth) || openJwt(headerToken)
|
const authCookie = getCookie(ctx, Cookie.Auth) || openJwt(headerToken)
|
||||||
const apiKey = ctx.request.headers[Headers.API_KEY]
|
const apiKey = ctx.request.headers[Header.API_KEY]
|
||||||
const tenantId = ctx.request.headers[Headers.TENANT_ID]
|
const tenantId = ctx.request.headers[Header.TENANT_ID]
|
||||||
let authenticated = false,
|
let authenticated = false,
|
||||||
user = null,
|
user = null,
|
||||||
internal = false
|
internal = false
|
||||||
|
@ -116,7 +114,7 @@ export = (
|
||||||
authenticated = false
|
authenticated = false
|
||||||
console.error("Auth Error", err?.message || err)
|
console.error("Auth Error", err?.message || err)
|
||||||
// remove the cookie as the user does not exist anymore
|
// remove the cookie as the user does not exist anymore
|
||||||
clearCookie(ctx, Cookies.Auth)
|
clearCookie(ctx, Cookie.Auth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// this is an internal request, no user made it
|
// this is an internal request, no user made it
|
||||||
|
@ -140,7 +138,7 @@ export = (
|
||||||
delete user.password
|
delete user.password
|
||||||
}
|
}
|
||||||
// be explicit
|
// be explicit
|
||||||
if (authenticated !== true) {
|
if (!authenticated) {
|
||||||
authenticated = false
|
authenticated = false
|
||||||
}
|
}
|
||||||
// isAuthenticated is a function, so use a variable to be able to check authed state
|
// isAuthenticated is a function, so use a variable to be able to check authed state
|
||||||
|
@ -155,7 +153,7 @@ export = (
|
||||||
console.error("Auth Error", err?.message || err)
|
console.error("Auth Error", err?.message || err)
|
||||||
// invalid token, clear the cookie
|
// invalid token, clear the cookie
|
||||||
if (err && err.name === "JsonWebTokenError") {
|
if (err && err.name === "JsonWebTokenError") {
|
||||||
clearCookie(ctx, Cookies.Auth)
|
clearCookie(ctx, Cookie.Auth)
|
||||||
}
|
}
|
||||||
// allow configuring for public access
|
// allow configuring for public access
|
||||||
if ((opts && opts.publicAllowed) || publicEndpoint) {
|
if ((opts && opts.publicAllowed) || publicEndpoint) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const { Headers } = require("../constants")
|
const { Header } = require("../constants")
|
||||||
const { buildMatcherRegex, matches } = require("./matchers")
|
const { buildMatcherRegex, matches } = require("./matchers")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,7 +68,7 @@ module.exports = (opts = { noCsrfPatterns: [] }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// reject if no token in request or mismatch
|
// reject if no token in request or mismatch
|
||||||
const requestToken = ctx.get(Headers.CSRF_TOKEN)
|
const requestToken = ctx.get(Header.CSRF_TOKEN)
|
||||||
if (!requestToken || requestToken !== userToken) {
|
if (!requestToken || requestToken !== userToken) {
|
||||||
ctx.throw(403, "Invalid CSRF token")
|
ctx.throw(403, "Invalid CSRF token")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
const env = require("../environment")
|
const env = require("../environment")
|
||||||
const { Headers } = require("../constants")
|
const { Header } = require("../constants")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API Key only endpoint.
|
* API Key only endpoint.
|
||||||
*/
|
*/
|
||||||
module.exports = async (ctx, next) => {
|
module.exports = async (ctx, next) => {
|
||||||
const apiKey = ctx.request.headers[Headers.API_KEY]
|
const apiKey = ctx.request.headers[Header.API_KEY]
|
||||||
if (apiKey !== env.INTERNAL_API_KEY) {
|
if (apiKey !== env.INTERNAL_API_KEY) {
|
||||||
ctx.throw(403, "Unauthorized")
|
ctx.throw(403, "Unauthorized")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const google = require("../google")
|
const google = require("../google")
|
||||||
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||||
const { Cookies, Configs } = require("../../../constants")
|
const { Cookie, Config } = require("../../../constants")
|
||||||
const { clearCookie, getCookie } = require("../../../utils")
|
const { clearCookie, getCookie } = require("../../../utils")
|
||||||
const { getScopedConfig, getPlatformUrl } = require("../../../db/utils")
|
const { getScopedConfig, getPlatformUrl } = require("../../../db/utils")
|
||||||
const { doWithDB } = require("../../../db")
|
const { doWithDB } = require("../../../db")
|
||||||
|
@ -11,7 +11,7 @@ async function fetchGoogleCreds() {
|
||||||
// try and get the config from the tenant
|
// try and get the config from the tenant
|
||||||
const db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
const googleConfig = await getScopedConfig(db, {
|
const googleConfig = await getScopedConfig(db, {
|
||||||
type: Configs.GOOGLE,
|
type: Config.GOOGLE,
|
||||||
})
|
})
|
||||||
// or fall back to env variables
|
// or fall back to env variables
|
||||||
return (
|
return (
|
||||||
|
@ -47,7 +47,7 @@ async function postAuth(passport, ctx, next) {
|
||||||
const platformUrl = await getPlatformUrl({ tenantAware: false })
|
const platformUrl = await getPlatformUrl({ tenantAware: false })
|
||||||
|
|
||||||
let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback`
|
let callbackUrl = `${platformUrl}/api/global/auth/datasource/google/callback`
|
||||||
const authStateCookie = getCookie(ctx, Cookies.DatasourceAuth)
|
const authStateCookie = getCookie(ctx, Cookie.DatasourceAuth)
|
||||||
|
|
||||||
return passport.authenticate(
|
return passport.authenticate(
|
||||||
new GoogleStrategy(
|
new GoogleStrategy(
|
||||||
|
@ -57,7 +57,7 @@ async function postAuth(passport, ctx, next) {
|
||||||
callbackURL: callbackUrl,
|
callbackURL: callbackUrl,
|
||||||
},
|
},
|
||||||
(accessToken, refreshToken, profile, done) => {
|
(accessToken, refreshToken, profile, done) => {
|
||||||
clearCookie(ctx, Cookies.DatasourceAuth)
|
clearCookie(ctx, Cookie.DatasourceAuth)
|
||||||
done(null, { accessToken, refreshToken })
|
done(null, { accessToken, refreshToken })
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||||
const { ssoCallbackUrl } = require("./utils")
|
const { ssoCallbackUrl } = require("./utils")
|
||||||
const { authenticateThirdParty } = require("./third-party-common")
|
const { authenticateThirdParty } = require("./third-party-common")
|
||||||
const { Configs } = require("../../../constants")
|
const { Config } = require("../../../constants")
|
||||||
|
|
||||||
const buildVerifyFn = saveUserFn => {
|
const buildVerifyFn = saveUserFn => {
|
||||||
return (accessToken, refreshToken, profile, done) => {
|
return (accessToken, refreshToken, profile, done) => {
|
||||||
|
@ -60,7 +60,7 @@ exports.strategyFactory = async function (config, callbackUrl, saveUserFn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getCallbackUrl = async function (db, config) {
|
exports.getCallbackUrl = async function (db, config) {
|
||||||
return ssoCallbackUrl(db, config, Configs.GOOGLE)
|
return ssoCallbackUrl(db, config, Config.GOOGLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
// expose for testing
|
// expose for testing
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
const { Cookies } = require("../../constants")
|
const { Cookie } = require("../../constants")
|
||||||
const env = require("../../environment")
|
const env = require("../../environment")
|
||||||
const { authError } = require("./utils")
|
const { authError } = require("./utils")
|
||||||
|
|
||||||
exports.options = {
|
exports.options = {
|
||||||
secretOrKey: env.JWT_SECRET,
|
secretOrKey: env.JWT_SECRET,
|
||||||
jwtFromRequest: function (ctx) {
|
jwtFromRequest: function (ctx) {
|
||||||
return ctx.cookies.get(Cookies.Auth)
|
return ctx.cookies.get(Cookie.Auth)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ const fetch = require("node-fetch")
|
||||||
const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy
|
const OIDCStrategy = require("@techpass/passport-openidconnect").Strategy
|
||||||
const { authenticateThirdParty } = require("./third-party-common")
|
const { authenticateThirdParty } = require("./third-party-common")
|
||||||
const { ssoCallbackUrl } = require("./utils")
|
const { ssoCallbackUrl } = require("./utils")
|
||||||
const { Configs } = require("../../../constants")
|
const { Config } = require("../../../constants")
|
||||||
|
|
||||||
const buildVerifyFn = saveUserFn => {
|
const buildVerifyFn = saveUserFn => {
|
||||||
/**
|
/**
|
||||||
|
@ -140,7 +140,7 @@ exports.fetchStrategyConfig = async function (enrichedConfig, callbackUrl) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getCallbackUrl = async function (db, config) {
|
exports.getCallbackUrl = async function (db, config) {
|
||||||
return ssoCallbackUrl(db, config, Configs.OIDC)
|
return ssoCallbackUrl(db, config, Config.OIDC)
|
||||||
}
|
}
|
||||||
|
|
||||||
// expose for testing
|
// expose for testing
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const { isMultiTenant, getTenantId } = require("../../tenancy")
|
const { isMultiTenant, getTenantId } = require("../../tenancy")
|
||||||
const { getScopedConfig } = require("../../db/utils")
|
const { getScopedConfig } = require("../../db/utils")
|
||||||
const { Configs } = require("../../constants")
|
const { Config } = require("../../constants")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility to handle authentication errors.
|
* Utility to handle authentication errors.
|
||||||
|
@ -24,7 +24,7 @@ exports.ssoCallbackUrl = async (db, config, type) => {
|
||||||
return config.callbackURL
|
return config.callbackURL
|
||||||
}
|
}
|
||||||
const publicConfig = await getScopedConfig(db, {
|
const publicConfig = await getScopedConfig(db, {
|
||||||
type: Configs.SETTINGS,
|
type: Config.SETTINGS,
|
||||||
})
|
})
|
||||||
|
|
||||||
let callbackUrl = `/api/global/auth`
|
let callbackUrl = `/api/global/auth`
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { doInTenant, getTenantIDFromCtx } from "../tenancy"
|
import { doInTenant, getTenantIDFromCtx } from "../tenancy"
|
||||||
import { buildMatcherRegex, matches } from "./matchers"
|
import { buildMatcherRegex, matches } from "./matchers"
|
||||||
import { Headers } from "../constants"
|
import { Header } from "../constants"
|
||||||
import {
|
import {
|
||||||
BBContext,
|
BBContext,
|
||||||
EndpointMatcher,
|
EndpointMatcher,
|
||||||
|
@ -29,7 +29,7 @@ const tenancy = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const tenantId = getTenantIDFromCtx(ctx, tenantOpts)
|
const tenantId = getTenantIDFromCtx(ctx, tenantOpts)
|
||||||
ctx.set(Headers.TENANT_ID, tenantId as string)
|
ctx.set(Header.TENANT_ID, tenantId as string)
|
||||||
return doInTenant(tenantId, next)
|
return doInTenant(tenantId, next)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ export const runMigration = async (
|
||||||
options: MigrationOptions = {}
|
options: MigrationOptions = {}
|
||||||
) => {
|
) => {
|
||||||
const migrationType = migration.type
|
const migrationType = migration.type
|
||||||
let tenantId: string
|
let tenantId: string | undefined
|
||||||
if (migrationType !== MigrationType.INSTALLATION) {
|
if (migrationType !== MigrationType.INSTALLATION) {
|
||||||
tenantId = getTenantId()
|
tenantId = getTenantId()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require("../../../tests")
|
require("../../../tests")
|
||||||
const { runMigrations, getMigrationsDoc } = require("../index")
|
const { runMigrations, getMigrationsDoc } = require("../index")
|
||||||
const { dangerousGetDB } = require("../../db")
|
const { getDB } = require("../../db")
|
||||||
const {
|
const {
|
||||||
StaticDatabases,
|
StaticDatabases,
|
||||||
} = require("../../db/utils")
|
} = require("../../db/utils")
|
||||||
|
@ -18,7 +18,7 @@ describe("migrations", () => {
|
||||||
}]
|
}]
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
db = dangerousGetDB(StaticDatabases.GLOBAL.name)
|
db = getDB(StaticDatabases.GLOBAL.name)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
import * as generic from "../cache/generic"
|
import * as generic from "../cache/generic"
|
||||||
import * as user from "../cache/user"
|
import * as user from "../cache/user"
|
||||||
import * as app from "../cache/appMetadata"
|
import * as app from "../cache/appMetadata"
|
||||||
|
import * as writethrough from "../cache/writethrough"
|
||||||
|
|
||||||
export = {
|
export = {
|
||||||
app,
|
app,
|
||||||
user,
|
user,
|
||||||
|
writethrough,
|
||||||
...generic,
|
...generic,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
// Mimic the outer package export for usage in index.ts
|
|
||||||
// The outer exports can't be used as they now reference dist directly
|
|
||||||
export * from "../db"
|
|
||||||
export * from "../db/utils"
|
|
||||||
export * from "../db/views"
|
|
||||||
export * from "../db/pouch"
|
|
||||||
export * from "../db/constants"
|
|
|
@ -51,6 +51,7 @@ function validateDatasource(schema) {
|
||||||
const queryValidator = joi
|
const queryValidator = joi
|
||||||
.object({
|
.object({
|
||||||
type: joi.string().allow(...Object.values(QueryType)),
|
type: joi.string().allow(...Object.values(QueryType)),
|
||||||
|
readable: joi.boolean(),
|
||||||
fields: joi.object().pattern(joi.string(), fieldValidator),
|
fields: joi.object().pattern(joi.string(), fieldValidator),
|
||||||
})
|
})
|
||||||
.required()
|
.required()
|
||||||
|
|
|
@ -1,164 +0,0 @@
|
||||||
const { flatten } = require("lodash")
|
|
||||||
const { cloneDeep } = require("lodash/fp")
|
|
||||||
|
|
||||||
const PermissionLevels = {
|
|
||||||
READ: "read",
|
|
||||||
WRITE: "write",
|
|
||||||
EXECUTE: "execute",
|
|
||||||
ADMIN: "admin",
|
|
||||||
}
|
|
||||||
|
|
||||||
// these are the global types, that govern the underlying default behaviour
|
|
||||||
const PermissionTypes = {
|
|
||||||
APP: "app",
|
|
||||||
TABLE: "table",
|
|
||||||
USER: "user",
|
|
||||||
AUTOMATION: "automation",
|
|
||||||
WEBHOOK: "webhook",
|
|
||||||
BUILDER: "builder",
|
|
||||||
VIEW: "view",
|
|
||||||
QUERY: "query",
|
|
||||||
}
|
|
||||||
|
|
||||||
function Permission(type, level) {
|
|
||||||
this.level = level
|
|
||||||
this.type = type
|
|
||||||
}
|
|
||||||
|
|
||||||
function levelToNumber(perm) {
|
|
||||||
switch (perm) {
|
|
||||||
// not everything has execute privileges
|
|
||||||
case PermissionLevels.EXECUTE:
|
|
||||||
return 0
|
|
||||||
case PermissionLevels.READ:
|
|
||||||
return 1
|
|
||||||
case PermissionLevels.WRITE:
|
|
||||||
return 2
|
|
||||||
case PermissionLevels.ADMIN:
|
|
||||||
return 3
|
|
||||||
default:
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Given the specified permission level for the user return the levels they are allowed to carry out.
|
|
||||||
* @param {string} userPermLevel The permission level of the user.
|
|
||||||
* @return {string[]} All the permission levels this user is allowed to carry out.
|
|
||||||
*/
|
|
||||||
function getAllowedLevels(userPermLevel) {
|
|
||||||
switch (userPermLevel) {
|
|
||||||
case PermissionLevels.EXECUTE:
|
|
||||||
return [PermissionLevels.EXECUTE]
|
|
||||||
case PermissionLevels.READ:
|
|
||||||
return [PermissionLevels.EXECUTE, PermissionLevels.READ]
|
|
||||||
case PermissionLevels.WRITE:
|
|
||||||
case PermissionLevels.ADMIN:
|
|
||||||
return [
|
|
||||||
PermissionLevels.READ,
|
|
||||||
PermissionLevels.WRITE,
|
|
||||||
PermissionLevels.EXECUTE,
|
|
||||||
]
|
|
||||||
default:
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.BUILTIN_PERMISSION_IDS = {
|
|
||||||
PUBLIC: "public",
|
|
||||||
READ_ONLY: "read_only",
|
|
||||||
WRITE: "write",
|
|
||||||
ADMIN: "admin",
|
|
||||||
POWER: "power",
|
|
||||||
}
|
|
||||||
|
|
||||||
const BUILTIN_PERMISSIONS = {
|
|
||||||
PUBLIC: {
|
|
||||||
_id: exports.BUILTIN_PERMISSION_IDS.PUBLIC,
|
|
||||||
name: "Public",
|
|
||||||
permissions: [
|
|
||||||
new Permission(PermissionTypes.WEBHOOK, PermissionLevels.EXECUTE),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
READ_ONLY: {
|
|
||||||
_id: exports.BUILTIN_PERMISSION_IDS.READ_ONLY,
|
|
||||||
name: "Read only",
|
|
||||||
permissions: [
|
|
||||||
new Permission(PermissionTypes.QUERY, PermissionLevels.READ),
|
|
||||||
new Permission(PermissionTypes.TABLE, PermissionLevels.READ),
|
|
||||||
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
WRITE: {
|
|
||||||
_id: exports.BUILTIN_PERMISSION_IDS.WRITE,
|
|
||||||
name: "Read/Write",
|
|
||||||
permissions: [
|
|
||||||
new Permission(PermissionTypes.QUERY, PermissionLevels.WRITE),
|
|
||||||
new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
|
||||||
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
|
||||||
new Permission(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
POWER: {
|
|
||||||
_id: exports.BUILTIN_PERMISSION_IDS.POWER,
|
|
||||||
name: "Power",
|
|
||||||
permissions: [
|
|
||||||
new Permission(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
|
||||||
new Permission(PermissionTypes.USER, PermissionLevels.READ),
|
|
||||||
new Permission(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE),
|
|
||||||
new Permission(PermissionTypes.VIEW, PermissionLevels.READ),
|
|
||||||
new Permission(PermissionTypes.WEBHOOK, PermissionLevels.READ),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
ADMIN: {
|
|
||||||
_id: exports.BUILTIN_PERMISSION_IDS.ADMIN,
|
|
||||||
name: "Admin",
|
|
||||||
permissions: [
|
|
||||||
new Permission(PermissionTypes.TABLE, PermissionLevels.ADMIN),
|
|
||||||
new Permission(PermissionTypes.USER, PermissionLevels.ADMIN),
|
|
||||||
new Permission(PermissionTypes.AUTOMATION, PermissionLevels.ADMIN),
|
|
||||||
new Permission(PermissionTypes.VIEW, PermissionLevels.ADMIN),
|
|
||||||
new Permission(PermissionTypes.WEBHOOK, PermissionLevels.READ),
|
|
||||||
new Permission(PermissionTypes.QUERY, PermissionLevels.ADMIN),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.getBuiltinPermissions = () => {
|
|
||||||
return cloneDeep(BUILTIN_PERMISSIONS)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.getBuiltinPermissionByID = id => {
|
|
||||||
const perms = Object.values(BUILTIN_PERMISSIONS)
|
|
||||||
return perms.find(perm => perm._id === id)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.doesHaveBasePermission = (permType, permLevel, rolesHierarchy) => {
|
|
||||||
const basePermissions = [
|
|
||||||
...new Set(rolesHierarchy.map(role => role.permissionId)),
|
|
||||||
]
|
|
||||||
const builtins = Object.values(BUILTIN_PERMISSIONS)
|
|
||||||
let permissions = flatten(
|
|
||||||
builtins
|
|
||||||
.filter(builtin => basePermissions.indexOf(builtin._id) !== -1)
|
|
||||||
.map(builtin => builtin.permissions)
|
|
||||||
)
|
|
||||||
for (let permission of permissions) {
|
|
||||||
if (
|
|
||||||
permission.type === permType &&
|
|
||||||
getAllowedLevels(permission.level).indexOf(permLevel) !== -1
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.isPermissionLevelHigherThanRead = level => {
|
|
||||||
return levelToNumber(level) > 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// utility as a lot of things need simply the builder permission
|
|
||||||
exports.BUILDER = PermissionTypes.BUILDER
|
|
||||||
exports.PermissionTypes = PermissionTypes
|
|
||||||
exports.PermissionLevels = PermissionLevels
|
|
|
@ -0,0 +1,175 @@
|
||||||
|
const { flatten } = require("lodash")
|
||||||
|
const { cloneDeep } = require("lodash/fp")
|
||||||
|
|
||||||
|
export type RoleHierarchy = {
|
||||||
|
permissionId: string
|
||||||
|
}[]
|
||||||
|
|
||||||
|
export enum PermissionLevel {
|
||||||
|
READ = "read",
|
||||||
|
WRITE = "write",
|
||||||
|
EXECUTE = "execute",
|
||||||
|
ADMIN = "admin",
|
||||||
|
}
|
||||||
|
|
||||||
|
// these are the global types, that govern the underlying default behaviour
|
||||||
|
export enum PermissionType {
|
||||||
|
APP = "app",
|
||||||
|
TABLE = "table",
|
||||||
|
USER = "user",
|
||||||
|
AUTOMATION = "automation",
|
||||||
|
WEBHOOK = "webhook",
|
||||||
|
BUILDER = "builder",
|
||||||
|
VIEW = "view",
|
||||||
|
QUERY = "query",
|
||||||
|
}
|
||||||
|
|
||||||
|
class Permission {
|
||||||
|
type: PermissionType
|
||||||
|
level: PermissionLevel
|
||||||
|
|
||||||
|
constructor(type: PermissionType, level: PermissionLevel) {
|
||||||
|
this.type = type
|
||||||
|
this.level = level
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function levelToNumber(perm: PermissionLevel) {
|
||||||
|
switch (perm) {
|
||||||
|
// not everything has execute privileges
|
||||||
|
case PermissionLevel.EXECUTE:
|
||||||
|
return 0
|
||||||
|
case PermissionLevel.READ:
|
||||||
|
return 1
|
||||||
|
case PermissionLevel.WRITE:
|
||||||
|
return 2
|
||||||
|
case PermissionLevel.ADMIN:
|
||||||
|
return 3
|
||||||
|
default:
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the specified permission level for the user return the levels they are allowed to carry out.
|
||||||
|
* @param {string} userPermLevel The permission level of the user.
|
||||||
|
* @return {string[]} All the permission levels this user is allowed to carry out.
|
||||||
|
*/
|
||||||
|
function getAllowedLevels(userPermLevel: PermissionLevel) {
|
||||||
|
switch (userPermLevel) {
|
||||||
|
case PermissionLevel.EXECUTE:
|
||||||
|
return [PermissionLevel.EXECUTE]
|
||||||
|
case PermissionLevel.READ:
|
||||||
|
return [PermissionLevel.EXECUTE, PermissionLevel.READ]
|
||||||
|
case PermissionLevel.WRITE:
|
||||||
|
case PermissionLevel.ADMIN:
|
||||||
|
return [
|
||||||
|
PermissionLevel.READ,
|
||||||
|
PermissionLevel.WRITE,
|
||||||
|
PermissionLevel.EXECUTE,
|
||||||
|
]
|
||||||
|
default:
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum BuiltinPermissionID {
|
||||||
|
PUBLIC = "public",
|
||||||
|
READ_ONLY = "read_only",
|
||||||
|
WRITE = "write",
|
||||||
|
ADMIN = "admin",
|
||||||
|
POWER = "power",
|
||||||
|
}
|
||||||
|
|
||||||
|
const BUILTIN_PERMISSIONS = {
|
||||||
|
PUBLIC: {
|
||||||
|
_id: BuiltinPermissionID.PUBLIC,
|
||||||
|
name: "Public",
|
||||||
|
permissions: [
|
||||||
|
new Permission(PermissionType.WEBHOOK, PermissionLevel.EXECUTE),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
READ_ONLY: {
|
||||||
|
_id: BuiltinPermissionID.READ_ONLY,
|
||||||
|
name: "Read only",
|
||||||
|
permissions: [
|
||||||
|
new Permission(PermissionType.QUERY, PermissionLevel.READ),
|
||||||
|
new Permission(PermissionType.TABLE, PermissionLevel.READ),
|
||||||
|
new Permission(PermissionType.VIEW, PermissionLevel.READ),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
WRITE: {
|
||||||
|
_id: BuiltinPermissionID.WRITE,
|
||||||
|
name: "Read/Write",
|
||||||
|
permissions: [
|
||||||
|
new Permission(PermissionType.QUERY, PermissionLevel.WRITE),
|
||||||
|
new Permission(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||||
|
new Permission(PermissionType.VIEW, PermissionLevel.READ),
|
||||||
|
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
POWER: {
|
||||||
|
_id: BuiltinPermissionID.POWER,
|
||||||
|
name: "Power",
|
||||||
|
permissions: [
|
||||||
|
new Permission(PermissionType.TABLE, PermissionLevel.WRITE),
|
||||||
|
new Permission(PermissionType.USER, PermissionLevel.READ),
|
||||||
|
new Permission(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
||||||
|
new Permission(PermissionType.VIEW, PermissionLevel.READ),
|
||||||
|
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ADMIN: {
|
||||||
|
_id: BuiltinPermissionID.ADMIN,
|
||||||
|
name: "Admin",
|
||||||
|
permissions: [
|
||||||
|
new Permission(PermissionType.TABLE, PermissionLevel.ADMIN),
|
||||||
|
new Permission(PermissionType.USER, PermissionLevel.ADMIN),
|
||||||
|
new Permission(PermissionType.AUTOMATION, PermissionLevel.ADMIN),
|
||||||
|
new Permission(PermissionType.VIEW, PermissionLevel.ADMIN),
|
||||||
|
new Permission(PermissionType.WEBHOOK, PermissionLevel.READ),
|
||||||
|
new Permission(PermissionType.QUERY, PermissionLevel.ADMIN),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBuiltinPermissions() {
|
||||||
|
return cloneDeep(BUILTIN_PERMISSIONS)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBuiltinPermissionByID(id: string) {
|
||||||
|
const perms = Object.values(BUILTIN_PERMISSIONS)
|
||||||
|
return perms.find(perm => perm._id === id)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function doesHaveBasePermission(
|
||||||
|
permType: PermissionType,
|
||||||
|
permLevel: PermissionLevel,
|
||||||
|
rolesHierarchy: RoleHierarchy
|
||||||
|
) {
|
||||||
|
const basePermissions = [
|
||||||
|
...new Set(rolesHierarchy.map(role => role.permissionId)),
|
||||||
|
]
|
||||||
|
const builtins = Object.values(BUILTIN_PERMISSIONS)
|
||||||
|
let permissions = flatten(
|
||||||
|
builtins
|
||||||
|
.filter(builtin => basePermissions.indexOf(builtin._id) !== -1)
|
||||||
|
.map(builtin => builtin.permissions)
|
||||||
|
)
|
||||||
|
for (let permission of permissions) {
|
||||||
|
if (
|
||||||
|
permission.type === permType &&
|
||||||
|
getAllowedLevels(permission.level).indexOf(permLevel) !== -1
|
||||||
|
) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPermissionLevelHigherThanRead(level: PermissionLevel) {
|
||||||
|
return levelToNumber(level) > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// utility as a lot of things need simply the builder permission
|
||||||
|
export const BUILDER = PermissionType.BUILDER
|
|
@ -1,4 +1,4 @@
|
||||||
import { BUILTIN_PERMISSION_IDS, PermissionLevels } from "./permissions"
|
import { BuiltinPermissionID, PermissionLevel } from "./permissions"
|
||||||
import {
|
import {
|
||||||
generateRoleID,
|
generateRoleID,
|
||||||
getRoleParams,
|
getRoleParams,
|
||||||
|
@ -54,19 +54,19 @@ export class Role {
|
||||||
|
|
||||||
const BUILTIN_ROLES = {
|
const BUILTIN_ROLES = {
|
||||||
ADMIN: new Role(BUILTIN_IDS.ADMIN, "Admin")
|
ADMIN: new Role(BUILTIN_IDS.ADMIN, "Admin")
|
||||||
.addPermission(BUILTIN_PERMISSION_IDS.ADMIN)
|
.addPermission(BuiltinPermissionID.ADMIN)
|
||||||
.addInheritance(BUILTIN_IDS.POWER),
|
.addInheritance(BUILTIN_IDS.POWER),
|
||||||
POWER: new Role(BUILTIN_IDS.POWER, "Power")
|
POWER: new Role(BUILTIN_IDS.POWER, "Power")
|
||||||
.addPermission(BUILTIN_PERMISSION_IDS.POWER)
|
.addPermission(BuiltinPermissionID.POWER)
|
||||||
.addInheritance(BUILTIN_IDS.BASIC),
|
.addInheritance(BUILTIN_IDS.BASIC),
|
||||||
BASIC: new Role(BUILTIN_IDS.BASIC, "Basic")
|
BASIC: new Role(BUILTIN_IDS.BASIC, "Basic")
|
||||||
.addPermission(BUILTIN_PERMISSION_IDS.WRITE)
|
.addPermission(BuiltinPermissionID.WRITE)
|
||||||
.addInheritance(BUILTIN_IDS.PUBLIC),
|
.addInheritance(BUILTIN_IDS.PUBLIC),
|
||||||
PUBLIC: new Role(BUILTIN_IDS.PUBLIC, "Public").addPermission(
|
PUBLIC: new Role(BUILTIN_IDS.PUBLIC, "Public").addPermission(
|
||||||
BUILTIN_PERMISSION_IDS.PUBLIC
|
BuiltinPermissionID.PUBLIC
|
||||||
),
|
),
|
||||||
BUILDER: new Role(BUILTIN_IDS.BUILDER, "Builder").addPermission(
|
BUILDER: new Role(BUILTIN_IDS.BUILDER, "Builder").addPermission(
|
||||||
BUILTIN_PERMISSION_IDS.ADMIN
|
BuiltinPermissionID.ADMIN
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,9 +147,9 @@ export function lowerBuiltinRoleID(roleId1?: string, roleId2?: string) {
|
||||||
* @param {string|null} roleId The level ID to lookup.
|
* @param {string|null} roleId The level ID to lookup.
|
||||||
* @returns {Promise<Role|object|null>} The role object, which may contain an "inherits" property.
|
* @returns {Promise<Role|object|null>} The role object, which may contain an "inherits" property.
|
||||||
*/
|
*/
|
||||||
export async function getRole(roleId?: string) {
|
export async function getRole(roleId?: string): Promise<RoleDoc | undefined> {
|
||||||
if (!roleId) {
|
if (!roleId) {
|
||||||
return null
|
return undefined
|
||||||
}
|
}
|
||||||
let role: any = {}
|
let role: any = {}
|
||||||
// built in roles mostly come from the in-code implementation,
|
// built in roles mostly come from the in-code implementation,
|
||||||
|
@ -193,8 +193,10 @@ async function getAllUserRoles(userRoleId?: string): Promise<RoleDoc[]> {
|
||||||
) {
|
) {
|
||||||
roleIds.push(currentRole.inherits)
|
roleIds.push(currentRole.inherits)
|
||||||
currentRole = await getRole(currentRole.inherits)
|
currentRole = await getRole(currentRole.inherits)
|
||||||
|
if (currentRole) {
|
||||||
roles.push(currentRole)
|
roles.push(currentRole)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return roles
|
return roles
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,8 +227,8 @@ export function checkForRoleResourceArray(
|
||||||
if (rolePerms && !Array.isArray(rolePerms[resourceId])) {
|
if (rolePerms && !Array.isArray(rolePerms[resourceId])) {
|
||||||
const permLevel = rolePerms[resourceId] as any
|
const permLevel = rolePerms[resourceId] as any
|
||||||
rolePerms[resourceId] = [permLevel]
|
rolePerms[resourceId] = [permLevel]
|
||||||
if (permLevel === PermissionLevels.WRITE) {
|
if (permLevel === PermissionLevel.WRITE) {
|
||||||
rolePerms[resourceId].push(PermissionLevels.READ)
|
rolePerms[resourceId].push(PermissionLevel.READ)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rolePerms
|
return rolePerms
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import { doWithDB } from "../db"
|
|
||||||
import { queryPlatformView } from "../db/views"
|
|
||||||
import { StaticDatabases, ViewName } from "../db/constants"
|
|
||||||
import { getGlobalDBName } from "../db/tenancy"
|
|
||||||
import {
|
import {
|
||||||
getTenantId,
|
doWithDB,
|
||||||
|
queryPlatformView,
|
||||||
|
StaticDatabases,
|
||||||
|
getGlobalDBName,
|
||||||
|
ViewName,
|
||||||
|
} from "../db"
|
||||||
|
import {
|
||||||
DEFAULT_TENANT_ID,
|
DEFAULT_TENANT_ID,
|
||||||
isMultiTenant,
|
getTenantId,
|
||||||
getTenantIDFromAppID,
|
getTenantIDFromAppID,
|
||||||
|
isMultiTenant,
|
||||||
} from "../context"
|
} from "../context"
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import {
|
import {
|
||||||
|
@ -15,12 +18,12 @@ import {
|
||||||
TenantResolutionStrategy,
|
TenantResolutionStrategy,
|
||||||
GetTenantIdOptions,
|
GetTenantIdOptions,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { Headers } from "../constants"
|
import { Header } from "../constants"
|
||||||
|
|
||||||
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
const TENANT_DOC = StaticDatabases.PLATFORM_INFO.docs.tenants
|
||||||
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
const PLATFORM_INFO_DB = StaticDatabases.PLATFORM_INFO.name
|
||||||
|
|
||||||
export const addTenantToUrl = (url: string) => {
|
export function addTenantToUrl(url: string) {
|
||||||
const tenantId = getTenantId()
|
const tenantId = getTenantId()
|
||||||
|
|
||||||
if (isMultiTenant()) {
|
if (isMultiTenant()) {
|
||||||
|
@ -31,7 +34,7 @@ export const addTenantToUrl = (url: string) => {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
export const doesTenantExist = async (tenantId: string) => {
|
export async function doesTenantExist(tenantId: string) {
|
||||||
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
||||||
let tenants
|
let tenants
|
||||||
try {
|
try {
|
||||||
|
@ -48,12 +51,12 @@ export const doesTenantExist = async (tenantId: string) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const tryAddTenant = async (
|
export async function tryAddTenant(
|
||||||
tenantId: string,
|
tenantId: string,
|
||||||
userId: string,
|
userId: string,
|
||||||
email: string,
|
email: string,
|
||||||
afterCreateTenant: () => Promise<void>
|
afterCreateTenant: () => Promise<void>
|
||||||
) => {
|
) {
|
||||||
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
||||||
const getDoc = async (id: string) => {
|
const getDoc = async (id: string) => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
@ -95,11 +98,11 @@ export const tryAddTenant = async (
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const doWithGlobalDB = (tenantId: string, cb: any) => {
|
export function doWithGlobalDB(tenantId: string, cb: any) {
|
||||||
return doWithDB(getGlobalDBName(tenantId), cb)
|
return doWithDB(getGlobalDBName(tenantId), cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lookupTenantId = async (userId: string) => {
|
export async function lookupTenantId(userId: string) {
|
||||||
return doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: any) => {
|
return doWithDB(StaticDatabases.PLATFORM_INFO.name, async (db: any) => {
|
||||||
let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null
|
let tenantId = env.MULTI_TENANCY ? DEFAULT_TENANT_ID : null
|
||||||
try {
|
try {
|
||||||
|
@ -115,19 +118,26 @@ export const lookupTenantId = async (userId: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// lookup, could be email or userId, either will return a doc
|
// lookup, could be email or userId, either will return a doc
|
||||||
export const getTenantUser = async (
|
export async function getTenantUser(
|
||||||
identifier: string
|
identifier: string
|
||||||
): Promise<PlatformUser | null> => {
|
): Promise<PlatformUser | undefined> {
|
||||||
// use the view here and allow to find anyone regardless of casing
|
// use the view here and allow to find anyone regardless of casing
|
||||||
// Use lowercase to ensure email login is case insensitive
|
// Use lowercase to ensure email login is case-insensitive
|
||||||
const response = queryPlatformView(ViewName.PLATFORM_USERS_LOWERCASE, {
|
const users = await queryPlatformView<PlatformUser>(
|
||||||
|
ViewName.PLATFORM_USERS_LOWERCASE,
|
||||||
|
{
|
||||||
keys: [identifier.toLowerCase()],
|
keys: [identifier.toLowerCase()],
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
}) as Promise<PlatformUser>
|
}
|
||||||
return response
|
)
|
||||||
|
if (Array.isArray(users)) {
|
||||||
|
return users[0]
|
||||||
|
} else {
|
||||||
|
return users
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isUserInAppTenant = (appId: string, user?: any) => {
|
export function isUserInAppTenant(appId: string, user?: any) {
|
||||||
let userTenantId
|
let userTenantId
|
||||||
if (user) {
|
if (user) {
|
||||||
userTenantId = user.tenantId || DEFAULT_TENANT_ID
|
userTenantId = user.tenantId || DEFAULT_TENANT_ID
|
||||||
|
@ -138,7 +148,7 @@ export const isUserInAppTenant = (appId: string, user?: any) => {
|
||||||
return tenantId === userTenantId
|
return tenantId === userTenantId
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTenantIds = async () => {
|
export async function getTenantIds() {
|
||||||
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
return doWithDB(PLATFORM_INFO_DB, async (db: any) => {
|
||||||
let tenants
|
let tenants
|
||||||
try {
|
try {
|
||||||
|
@ -193,7 +203,7 @@ export const getTenantIDFromCtx = (
|
||||||
|
|
||||||
// header
|
// header
|
||||||
if (isAllowed(TenantResolutionStrategy.HEADER)) {
|
if (isAllowed(TenantResolutionStrategy.HEADER)) {
|
||||||
const headerTenantId = ctx.request.headers[Headers.TENANT_ID]
|
const headerTenantId = ctx.request.headers[Header.TENANT_ID]
|
||||||
if (headerTenantId) {
|
if (headerTenantId) {
|
||||||
return headerTenantId as string
|
return headerTenantId as string
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,15 +3,14 @@ import {
|
||||||
getUsersByAppParams,
|
getUsersByAppParams,
|
||||||
getProdAppID,
|
getProdAppID,
|
||||||
generateAppUserID,
|
generateAppUserID,
|
||||||
} from "./db/utils"
|
queryGlobalView,
|
||||||
import { queryGlobalView } from "./db/views"
|
UNICODE_MAX,
|
||||||
import { UNICODE_MAX } from "./db/constants"
|
} from "./db"
|
||||||
import { BulkDocsResponse, User } from "@budibase/types"
|
import { BulkDocsResponse, User } from "@budibase/types"
|
||||||
import { getGlobalDB } from "./context"
|
import { getGlobalDB } from "./context"
|
||||||
import PouchDB from "pouchdb"
|
|
||||||
|
|
||||||
export const bulkGetGlobalUsersById = async (userIds: string[]) => {
|
export const bulkGetGlobalUsersById = async (userIds: string[]) => {
|
||||||
const db = getGlobalDB() as PouchDB.Database
|
const db = getGlobalDB()
|
||||||
return (
|
return (
|
||||||
await db.allDocs({
|
await db.allDocs({
|
||||||
keys: userIds,
|
keys: userIds,
|
||||||
|
@ -21,7 +20,7 @@ export const bulkGetGlobalUsersById = async (userIds: string[]) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const bulkUpdateGlobalUsers = async (users: User[]) => {
|
export const bulkUpdateGlobalUsers = async (users: User[]) => {
|
||||||
const db = getGlobalDB() as PouchDB.Database
|
const db = getGlobalDB()
|
||||||
return (await db.bulkDocs(users)) as BulkDocsResponse
|
return (await db.bulkDocs(users)) as BulkDocsResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +68,7 @@ export const getGlobalUserByAppPage = (appId: string, user: User) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return generateAppUserID(getProdAppID(appId), user._id!)
|
return generateAppUserID(getProdAppID(appId)!, user._id!)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import { DocumentType, SEPARATOR, ViewName, getAllApps } from "./db/utils"
|
import {
|
||||||
const jwt = require("jsonwebtoken")
|
DocumentType,
|
||||||
|
SEPARATOR,
|
||||||
|
ViewName,
|
||||||
|
getAllApps,
|
||||||
|
queryGlobalView,
|
||||||
|
} from "./db"
|
||||||
import { options } from "./middleware/passport/jwt"
|
import { options } from "./middleware/passport/jwt"
|
||||||
import { queryGlobalView } from "./db/views"
|
import { Header, Cookie, MAX_VALID_DATE } from "./constants"
|
||||||
import { Headers, Cookies, MAX_VALID_DATE } from "./constants"
|
|
||||||
import env from "./environment"
|
import env from "./environment"
|
||||||
import userCache from "./cache/user"
|
import userCache from "./cache/user"
|
||||||
import { getSessionsForUser, invalidateSessions } from "./security/sessions"
|
import { getSessionsForUser, invalidateSessions } from "./security/sessions"
|
||||||
|
@ -15,6 +19,7 @@ import {
|
||||||
TenantResolutionStrategy,
|
TenantResolutionStrategy,
|
||||||
} from "@budibase/types"
|
} from "@budibase/types"
|
||||||
import { SetOption } from "cookies"
|
import { SetOption } from "cookies"
|
||||||
|
const jwt = require("jsonwebtoken")
|
||||||
|
|
||||||
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
||||||
const PROD_APP_PREFIX = "/app/"
|
const PROD_APP_PREFIX = "/app/"
|
||||||
|
@ -29,7 +34,7 @@ async function resolveAppUrl(ctx: BBContext) {
|
||||||
const appUrl = ctx.path.split("/")[2]
|
const appUrl = ctx.path.split("/")[2]
|
||||||
let possibleAppUrl = `/${appUrl.toLowerCase()}`
|
let possibleAppUrl = `/${appUrl.toLowerCase()}`
|
||||||
|
|
||||||
let tenantId = tenancy.getTenantId()
|
let tenantId: string | null = tenancy.getTenantId()
|
||||||
if (env.MULTI_TENANCY) {
|
if (env.MULTI_TENANCY) {
|
||||||
// always use the tenant id from the subdomain in multi tenancy
|
// always use the tenant id from the subdomain in multi tenancy
|
||||||
// this ensures the logged-in user tenant id doesn't overwrite
|
// this ensures the logged-in user tenant id doesn't overwrite
|
||||||
|
@ -50,7 +55,7 @@ async function resolveAppUrl(ctx: BBContext) {
|
||||||
return app && app.appId ? app.appId : undefined
|
return app && app.appId ? app.appId : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export const isServingApp = (ctx: BBContext) => {
|
export function isServingApp(ctx: BBContext) {
|
||||||
// dev app
|
// dev app
|
||||||
if (ctx.path.startsWith(`/${APP_PREFIX}`)) {
|
if (ctx.path.startsWith(`/${APP_PREFIX}`)) {
|
||||||
return true
|
return true
|
||||||
|
@ -67,9 +72,9 @@ export const isServingApp = (ctx: BBContext) => {
|
||||||
* @param {object} ctx The main request body to look through.
|
* @param {object} ctx The main request body to look through.
|
||||||
* @returns {string|undefined} If an appId was found it will be returned.
|
* @returns {string|undefined} If an appId was found it will be returned.
|
||||||
*/
|
*/
|
||||||
export const getAppIdFromCtx = async (ctx: BBContext) => {
|
export async function getAppIdFromCtx(ctx: BBContext) {
|
||||||
// look in headers
|
// look in headers
|
||||||
const options = [ctx.headers[Headers.APP_ID]]
|
const options = [ctx.headers[Header.APP_ID]]
|
||||||
let appId
|
let appId
|
||||||
for (let option of options) {
|
for (let option of options) {
|
||||||
appId = confirmAppId(option as string)
|
appId = confirmAppId(option as string)
|
||||||
|
@ -103,7 +108,7 @@ export const getAppIdFromCtx = async (ctx: BBContext) => {
|
||||||
* opens the contents of the specified encrypted JWT.
|
* opens the contents of the specified encrypted JWT.
|
||||||
* @return {object} the contents of the token.
|
* @return {object} the contents of the token.
|
||||||
*/
|
*/
|
||||||
export const openJwt = (token: string) => {
|
export function openJwt(token: string) {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
@ -115,7 +120,7 @@ export const openJwt = (token: string) => {
|
||||||
* @param {object} ctx The request which is to be manipulated.
|
* @param {object} ctx The request which is to be manipulated.
|
||||||
* @param {string} name The name of the cookie to get.
|
* @param {string} name The name of the cookie to get.
|
||||||
*/
|
*/
|
||||||
export const getCookie = (ctx: BBContext, name: string) => {
|
export function getCookie(ctx: BBContext, name: string) {
|
||||||
const cookie = ctx.cookies.get(name)
|
const cookie = ctx.cookies.get(name)
|
||||||
|
|
||||||
if (!cookie) {
|
if (!cookie) {
|
||||||
|
@ -132,12 +137,12 @@ export const getCookie = (ctx: BBContext, name: string) => {
|
||||||
* @param {string|object} value The value of cookie which will be set.
|
* @param {string|object} value The value of cookie which will be set.
|
||||||
* @param {object} opts options like whether to sign.
|
* @param {object} opts options like whether to sign.
|
||||||
*/
|
*/
|
||||||
export const setCookie = (
|
export function setCookie(
|
||||||
ctx: BBContext,
|
ctx: BBContext,
|
||||||
value: any,
|
value: any,
|
||||||
name = "builder",
|
name = "builder",
|
||||||
opts = { sign: true }
|
opts = { sign: true }
|
||||||
) => {
|
) {
|
||||||
if (value && opts && opts.sign) {
|
if (value && opts && opts.sign) {
|
||||||
value = jwt.sign(value, options.secretOrKey)
|
value = jwt.sign(value, options.secretOrKey)
|
||||||
}
|
}
|
||||||
|
@ -159,7 +164,7 @@ export const setCookie = (
|
||||||
/**
|
/**
|
||||||
* Utility function, simply calls setCookie with an empty string for value
|
* Utility function, simply calls setCookie with an empty string for value
|
||||||
*/
|
*/
|
||||||
export const clearCookie = (ctx: BBContext, name: string) => {
|
export function clearCookie(ctx: BBContext, name: string) {
|
||||||
setCookie(ctx, null, name)
|
setCookie(ctx, null, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,11 +174,11 @@ export const clearCookie = (ctx: BBContext, name: string) => {
|
||||||
* @param {object} ctx The koa context object to be tested.
|
* @param {object} ctx The koa context object to be tested.
|
||||||
* @return {boolean} returns true if the call is from the client lib (a built app rather than the builder).
|
* @return {boolean} returns true if the call is from the client lib (a built app rather than the builder).
|
||||||
*/
|
*/
|
||||||
export const isClient = (ctx: BBContext) => {
|
export function isClient(ctx: BBContext) {
|
||||||
return ctx.headers[Headers.TYPE] === "client"
|
return ctx.headers[Header.TYPE] === "client"
|
||||||
}
|
}
|
||||||
|
|
||||||
const getBuilders = async () => {
|
async function getBuilders() {
|
||||||
const builders = await queryGlobalView(ViewName.USER_BY_BUILDERS, {
|
const builders = await queryGlobalView(ViewName.USER_BY_BUILDERS, {
|
||||||
include_docs: false,
|
include_docs: false,
|
||||||
})
|
})
|
||||||
|
@ -189,7 +194,7 @@ const getBuilders = async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBuildersCount = async () => {
|
export async function getBuildersCount() {
|
||||||
const builders = await getBuilders()
|
const builders = await getBuilders()
|
||||||
return builders.length
|
return builders.length
|
||||||
}
|
}
|
||||||
|
@ -197,14 +202,14 @@ export const getBuildersCount = async () => {
|
||||||
/**
|
/**
|
||||||
* Logs a user out from budibase. Re-used across account portal and builder.
|
* Logs a user out from budibase. Re-used across account portal and builder.
|
||||||
*/
|
*/
|
||||||
export const platformLogout = async (opts: PlatformLogoutOpts) => {
|
export async function platformLogout(opts: PlatformLogoutOpts) {
|
||||||
const ctx = opts.ctx
|
const ctx = opts.ctx
|
||||||
const userId = opts.userId
|
const userId = opts.userId
|
||||||
const keepActiveSession = opts.keepActiveSession
|
const keepActiveSession = opts.keepActiveSession
|
||||||
|
|
||||||
if (!ctx) throw new Error("Koa context must be supplied to logout.")
|
if (!ctx) throw new Error("Koa context must be supplied to logout.")
|
||||||
|
|
||||||
const currentSession = getCookie(ctx, Cookies.Auth)
|
const currentSession = getCookie(ctx, Cookie.Auth)
|
||||||
let sessions = await getSessionsForUser(userId)
|
let sessions = await getSessionsForUser(userId)
|
||||||
|
|
||||||
if (keepActiveSession) {
|
if (keepActiveSession) {
|
||||||
|
@ -213,8 +218,8 @@ export const platformLogout = async (opts: PlatformLogoutOpts) => {
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// clear cookies
|
// clear cookies
|
||||||
clearCookie(ctx, Cookies.Auth)
|
clearCookie(ctx, Cookie.Auth)
|
||||||
clearCookie(ctx, Cookies.CurrentApp)
|
clearCookie(ctx, Cookie.CurrentApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionIds = sessions.map(({ sessionId }) => sessionId)
|
const sessionIds = sessions.map(({ sessionId }) => sessionId)
|
||||||
|
@ -223,6 +228,6 @@ export const platformLogout = async (opts: PlatformLogoutOpts) => {
|
||||||
await userCache.invalidateUser(userId)
|
await userCache.invalidateUser(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const timeout = (timeMs: number) => {
|
export function timeout(timeMs: number) {
|
||||||
return new Promise(resolve => setTimeout(resolve, timeMs))
|
return new Promise(resolve => setTimeout(resolve, timeMs))
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"types": [ "node", "jest" ],
|
"types": [ "node", "jest" ],
|
||||||
"outDir": "dist"
|
"outDir": "dist",
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.js",
|
"**/*.js",
|
||||||
|
|
|
@ -1335,6 +1335,11 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
|
"@types/tough-cookie@^4.0.2":
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
|
||||||
|
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
|
||||||
|
|
||||||
"@types/uuid@8.3.4":
|
"@types/uuid@8.3.4":
|
||||||
version "8.3.4"
|
version "8.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||||
|
@ -1411,7 +1416,7 @@ acorn@^8.4.1:
|
||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
|
||||||
integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
|
integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
|
||||||
|
|
||||||
agent-base@6:
|
agent-base@6, agent-base@^6.0.2:
|
||||||
version "6.0.2"
|
version "6.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
|
||||||
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
|
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
|
||||||
|
@ -1568,6 +1573,15 @@ axios@0.24.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects "^1.14.4"
|
follow-redirects "^1.14.4"
|
||||||
|
|
||||||
|
axios@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.3.tgz#8274250dada2edf53814ed7db644b9c2866c1e35"
|
||||||
|
integrity sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==
|
||||||
|
dependencies:
|
||||||
|
follow-redirects "^1.15.0"
|
||||||
|
form-data "^4.0.0"
|
||||||
|
proxy-from-env "^1.1.0"
|
||||||
|
|
||||||
babel-jest@^28.1.3:
|
babel-jest@^28.1.3:
|
||||||
version "28.1.3"
|
version "28.1.3"
|
||||||
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.3.tgz#c1187258197c099072156a0a121c11ee1e3917d5"
|
resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.3.tgz#c1187258197c099072156a0a121c11ee1e3917d5"
|
||||||
|
@ -1814,6 +1828,14 @@ cacheable-request@^6.0.0:
|
||||||
normalize-url "^4.1.0"
|
normalize-url "^4.1.0"
|
||||||
responselike "^1.0.2"
|
responselike "^1.0.2"
|
||||||
|
|
||||||
|
call-bind@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
|
||||||
|
integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
|
||||||
|
dependencies:
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
get-intrinsic "^1.0.2"
|
||||||
|
|
||||||
callsites@^3.0.0:
|
callsites@^3.0.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
|
||||||
|
@ -2472,6 +2494,11 @@ follow-redirects@^1.14.4:
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5"
|
||||||
integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
|
integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==
|
||||||
|
|
||||||
|
follow-redirects@^1.15.0:
|
||||||
|
version "1.15.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13"
|
||||||
|
integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==
|
||||||
|
|
||||||
forever-agent@~0.6.1:
|
forever-agent@~0.6.1:
|
||||||
version "0.6.1"
|
version "0.6.1"
|
||||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||||
|
@ -2486,6 +2513,15 @@ form-data@^3.0.0:
|
||||||
combined-stream "^1.0.8"
|
combined-stream "^1.0.8"
|
||||||
mime-types "^2.1.12"
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
|
form-data@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452"
|
||||||
|
integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==
|
||||||
|
dependencies:
|
||||||
|
asynckit "^0.4.0"
|
||||||
|
combined-stream "^1.0.8"
|
||||||
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
form-data@~2.3.2:
|
form-data@~2.3.2:
|
||||||
version "2.3.3"
|
version "2.3.3"
|
||||||
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||||
|
@ -2557,6 +2593,15 @@ get-caller-file@^2.0.5:
|
||||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
|
||||||
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
|
||||||
|
|
||||||
|
get-intrinsic@^1.0.2:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385"
|
||||||
|
integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==
|
||||||
|
dependencies:
|
||||||
|
function-bind "^1.1.1"
|
||||||
|
has "^1.0.3"
|
||||||
|
has-symbols "^1.0.3"
|
||||||
|
|
||||||
get-package-type@^0.1.0:
|
get-package-type@^0.1.0:
|
||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
|
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
|
||||||
|
@ -2705,7 +2750,7 @@ has-flag@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||||
|
|
||||||
has-symbols@^1.0.2:
|
has-symbols@^1.0.2, has-symbols@^1.0.3:
|
||||||
version "1.0.3"
|
version "1.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
|
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
|
||||||
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
|
integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
|
||||||
|
@ -2752,6 +2797,13 @@ http-cache-semantics@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
|
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390"
|
||||||
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
|
integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==
|
||||||
|
|
||||||
|
http-cookie-agent@^4.0.2:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-cookie-agent/-/http-cookie-agent-4.0.2.tgz#dcdaae18ed1f7452d81ae4d5cd80b227d6831b69"
|
||||||
|
integrity sha512-noTmxdH5CuytTnLj/Qv3Z84e/YFq8yLXAw3pqIYZ25Edhb9pQErIAC+ednw40Cic6Le/h9ryph5/TqsvkOaUCw==
|
||||||
|
dependencies:
|
||||||
|
agent-base "^6.0.2"
|
||||||
|
|
||||||
http-errors@^1.6.3, http-errors@~1.8.0:
|
http-errors@^1.6.3, http-errors@~1.8.0:
|
||||||
version "1.8.1"
|
version "1.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
|
resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.1.tgz#7c3f28577cbc8a207388455dbd62295ed07bd68c"
|
||||||
|
@ -4018,6 +4070,18 @@ msgpackr@^1.5.2:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
msgpackr-extract "^2.1.2"
|
msgpackr-extract "^2.1.2"
|
||||||
|
|
||||||
|
nano@^10.1.0:
|
||||||
|
version "10.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/nano/-/nano-10.1.0.tgz#afdd5a7440e62f09a8e23f41fcea328d27383922"
|
||||||
|
integrity sha512-COeN2TpLcHuSN44QLnPmfZCoCsKAg8/aelPOVqqm/2/MvRHDEA11/Kld5C4sLzDlWlhFZ3SO2WGJGevCsvcEzQ==
|
||||||
|
dependencies:
|
||||||
|
"@types/tough-cookie" "^4.0.2"
|
||||||
|
axios "^1.1.3"
|
||||||
|
http-cookie-agent "^4.0.2"
|
||||||
|
node-abort-controller "^3.0.1"
|
||||||
|
qs "^6.11.0"
|
||||||
|
tough-cookie "^4.1.2"
|
||||||
|
|
||||||
napi-macros@~2.0.0:
|
napi-macros@~2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b"
|
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b"
|
||||||
|
@ -4043,6 +4107,11 @@ negotiator@0.6.3:
|
||||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
|
||||||
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
|
||||||
|
|
||||||
|
node-abort-controller@^3.0.1:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.0.1.tgz#f91fa50b1dee3f909afabb7e261b1e1d6b0cb74e"
|
||||||
|
integrity sha512-/ujIVxthRs+7q6hsdjHMaj8hRG9NuWmwrz+JdRwZ14jdFoKSkm+vDsCbF9PLpnSqjaWQJuTmVtcWHNLr+vrOFw==
|
||||||
|
|
||||||
node-addon-api@^3.1.0:
|
node-addon-api@^3.1.0:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
|
resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161"
|
||||||
|
@ -4178,6 +4247,11 @@ object-assign@^4.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
||||||
|
|
||||||
|
object-inspect@^1.9.0:
|
||||||
|
version "1.12.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
|
||||||
|
integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==
|
||||||
|
|
||||||
on-finished@^2.3.0:
|
on-finished@^2.3.0:
|
||||||
version "2.4.1"
|
version "2.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
|
||||||
|
@ -4675,6 +4749,11 @@ prompts@^2.0.1:
|
||||||
kleur "^3.0.3"
|
kleur "^3.0.3"
|
||||||
sisteransi "^1.0.5"
|
sisteransi "^1.0.5"
|
||||||
|
|
||||||
|
proxy-from-env@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2"
|
||||||
|
integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==
|
||||||
|
|
||||||
prr@~1.0.1:
|
prr@~1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
|
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
|
||||||
|
@ -4715,6 +4794,13 @@ pupa@^2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
escape-goat "^2.0.0"
|
escape-goat "^2.0.0"
|
||||||
|
|
||||||
|
qs@^6.11.0:
|
||||||
|
version "6.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a"
|
||||||
|
integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==
|
||||||
|
dependencies:
|
||||||
|
side-channel "^1.0.4"
|
||||||
|
|
||||||
qs@~6.5.2:
|
qs@~6.5.2:
|
||||||
version "6.5.3"
|
version "6.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad"
|
||||||
|
@ -4725,6 +4811,11 @@ querystring@0.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
|
resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
|
||||||
integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==
|
integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==
|
||||||
|
|
||||||
|
querystringify@^2.1.1:
|
||||||
|
version "2.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||||
|
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||||
|
|
||||||
range-parser@^1.2.0:
|
range-parser@^1.2.0:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
|
||||||
|
@ -4878,6 +4969,11 @@ require-directory@^2.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
|
||||||
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
|
integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
|
||||||
|
|
||||||
|
requires-port@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||||
|
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
|
||||||
|
|
||||||
resolve-cwd@^3.0.0:
|
resolve-cwd@^3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
|
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
|
||||||
|
@ -4999,6 +5095,15 @@ shimmer@^1.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
|
resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337"
|
||||||
integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==
|
integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==
|
||||||
|
|
||||||
|
side-channel@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
|
||||||
|
integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
|
||||||
|
dependencies:
|
||||||
|
call-bind "^1.0.0"
|
||||||
|
get-intrinsic "^1.0.2"
|
||||||
|
object-inspect "^1.9.0"
|
||||||
|
|
||||||
signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
|
signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7:
|
||||||
version "3.0.7"
|
version "3.0.7"
|
||||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||||
|
@ -5329,6 +5434,16 @@ touch@^3.1.0:
|
||||||
punycode "^2.1.1"
|
punycode "^2.1.1"
|
||||||
universalify "^0.1.2"
|
universalify "^0.1.2"
|
||||||
|
|
||||||
|
tough-cookie@^4.1.2:
|
||||||
|
version "4.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874"
|
||||||
|
integrity sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==
|
||||||
|
dependencies:
|
||||||
|
psl "^1.1.33"
|
||||||
|
punycode "^2.1.1"
|
||||||
|
universalify "^0.2.0"
|
||||||
|
url-parse "^1.5.3"
|
||||||
|
|
||||||
tough-cookie@~2.5.0:
|
tough-cookie@~2.5.0:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
|
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
|
||||||
|
@ -5458,6 +5573,11 @@ universalify@^0.1.2:
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
|
||||||
|
|
||||||
|
universalify@^0.2.0:
|
||||||
|
version "0.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||||
|
integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
|
||||||
|
|
||||||
update-browserslist-db@^1.0.9:
|
update-browserslist-db@^1.0.9:
|
||||||
version "1.0.10"
|
version "1.0.10"
|
||||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"
|
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"
|
||||||
|
@ -5500,6 +5620,14 @@ url-parse-lax@^3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prepend-http "^2.0.0"
|
prepend-http "^2.0.0"
|
||||||
|
|
||||||
|
url-parse@^1.5.3:
|
||||||
|
version "1.5.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
|
||||||
|
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
|
||||||
|
dependencies:
|
||||||
|
querystringify "^2.1.1"
|
||||||
|
requires-port "^1.0.0"
|
||||||
|
|
||||||
url@0.10.3:
|
url@0.10.3:
|
||||||
version "0.10.3"
|
version "0.10.3"
|
||||||
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
|
resolved "https://registry.yarnpkg.com/url/-/url-0.10.3.tgz#021e4d9c7705f21bbf37d03ceb58767402774c64"
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/bbui",
|
"name": "@budibase/bbui",
|
||||||
"description": "A UI solution used in the different Budibase projects.",
|
"description": "A UI solution used in the different Budibase projects.",
|
||||||
"version": "2.1.22-alpha.6",
|
"version": "2.1.32-alpha.1",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"module": "dist/bbui.es.js",
|
"module": "dist/bbui.es.js",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
|
||||||
"@budibase/string-templates": "2.1.22-alpha.6",
|
"@budibase/string-templates": "2.1.32-alpha.1",
|
||||||
"@spectrum-css/actionbutton": "^1.0.1",
|
"@spectrum-css/actionbutton": "^1.0.1",
|
||||||
"@spectrum-css/actiongroup": "^1.0.1",
|
"@spectrum-css/actiongroup": "^1.0.1",
|
||||||
"@spectrum-css/avatar": "^3.0.2",
|
"@spectrum-css/avatar": "^3.0.2",
|
||||||
|
@ -85,5 +85,8 @@
|
||||||
"svelte-flatpickr": "^3.2.3",
|
"svelte-flatpickr": "^3.2.3",
|
||||||
"svelte-portal": "^1.0.0"
|
"svelte-portal": "^1.0.0"
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"loader-utils": "1.4.1"
|
||||||
|
},
|
||||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/builder",
|
"name": "@budibase/builder",
|
||||||
"version": "2.1.22-alpha.6",
|
"version": "2.1.32-alpha.1",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -71,10 +71,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.1.22-alpha.6",
|
"@budibase/bbui": "2.1.32-alpha.1",
|
||||||
"@budibase/client": "2.1.22-alpha.6",
|
"@budibase/client": "2.1.32-alpha.1",
|
||||||
"@budibase/frontend-core": "2.1.22-alpha.6",
|
"@budibase/frontend-core": "2.1.32-alpha.1",
|
||||||
"@budibase/string-templates": "2.1.22-alpha.6",
|
"@budibase/string-templates": "2.1.32-alpha.1",
|
||||||
"@sentry/browser": "5.19.1",
|
"@sentry/browser": "5.19.1",
|
||||||
"@spectrum-css/page": "^3.0.1",
|
"@spectrum-css/page": "^3.0.1",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -304,6 +304,8 @@
|
||||||
const newError = {}
|
const newError = {}
|
||||||
if (!external && fieldInfo.name?.startsWith("_")) {
|
if (!external && fieldInfo.name?.startsWith("_")) {
|
||||||
newError.name = `Column name cannot start with an underscore.`
|
newError.name = `Column name cannot start with an underscore.`
|
||||||
|
} else if (fieldInfo.name && !fieldInfo.name.match(/^[_a-zA-Z0-9\s]*$/g)) {
|
||||||
|
newError.name = `Illegal character; must be alpha-numeric.`
|
||||||
} else if (PROHIBITED_COLUMN_NAMES.some(name => fieldInfo.name === name)) {
|
} else if (PROHIBITED_COLUMN_NAMES.some(name => fieldInfo.name === name)) {
|
||||||
newError.name = `${PROHIBITED_COLUMN_NAMES.join(
|
newError.name = `${PROHIBITED_COLUMN_NAMES.join(
|
||||||
", "
|
", "
|
||||||
|
|
|
@ -97,7 +97,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function fieldOptions(field) {
|
function fieldOptions(field) {
|
||||||
return schema[field]?.type === "options"
|
return schema[field]?.type === "options" || schema[field]?.type === "array"
|
||||||
? schema[field]?.constraints.inclusion
|
? schema[field]?.constraints.inclusion
|
||||||
: [true, false]
|
: [true, false]
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,18 @@
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
export let nested
|
export let nested
|
||||||
|
|
||||||
$: showAvailableActions = !actions?.length
|
|
||||||
|
|
||||||
let actionQuery
|
let actionQuery
|
||||||
$: parsedQuery =
|
|
||||||
typeof actionQuery === "string" ? actionQuery.toLowerCase().trim() : ""
|
|
||||||
|
|
||||||
let selectedAction = actions?.length ? actions[0] : null
|
let selectedAction = actions?.length ? actions[0] : null
|
||||||
|
|
||||||
|
$: {
|
||||||
|
// Ensure parameters object is never null
|
||||||
|
if (selectedAction && !selectedAction.parameters) {
|
||||||
|
selectedAction.parameters = {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$: parsedQuery =
|
||||||
|
typeof actionQuery === "string" ? actionQuery.toLowerCase().trim() : ""
|
||||||
|
$: showAvailableActions = !actions?.length
|
||||||
$: mappedActionTypes = actionTypes.reduce((acc, action) => {
|
$: mappedActionTypes = actionTypes.reduce((acc, action) => {
|
||||||
let parsedName = action.name.toLowerCase().trim()
|
let parsedName = action.name.toLowerCase().trim()
|
||||||
if (parsedQuery.length && parsedName.indexOf(parsedQuery) < 0) {
|
if (parsedQuery.length && parsedName.indexOf(parsedQuery) < 0) {
|
||||||
|
@ -40,7 +44,6 @@
|
||||||
acc[action.type].push(action)
|
acc[action.type].push(action)
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
// These are ephemeral bindings which only exist while executing actions
|
// These are ephemeral bindings which only exist while executing actions
|
||||||
$: eventContexBindings = getEventContextBindings(
|
$: eventContexBindings = getEventContextBindings(
|
||||||
$currentAsset,
|
$currentAsset,
|
||||||
|
@ -50,9 +53,8 @@
|
||||||
selectedAction?.id
|
selectedAction?.id
|
||||||
)
|
)
|
||||||
$: allBindings = eventContexBindings.concat(bindings)
|
$: allBindings = eventContexBindings.concat(bindings)
|
||||||
|
|
||||||
// Assign a unique ID to each action
|
|
||||||
$: {
|
$: {
|
||||||
|
// Ensure each action has a unique ID
|
||||||
if (actions) {
|
if (actions) {
|
||||||
actions.forEach(action => {
|
actions.forEach(action => {
|
||||||
if (!action.id) {
|
if (!action.id) {
|
||||||
|
@ -61,13 +63,11 @@
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: selectedActionComponent =
|
$: selectedActionComponent =
|
||||||
selectedAction &&
|
selectedAction &&
|
||||||
actionTypes.find(t => t.name === selectedAction[EVENT_TYPE_KEY])?.component
|
actionTypes.find(t => t.name === selectedAction[EVENT_TYPE_KEY])?.component
|
||||||
|
|
||||||
// Select the first action if we delete an action
|
|
||||||
$: {
|
$: {
|
||||||
|
// Select the first action if we delete an action
|
||||||
if (selectedAction && !actions?.includes(selectedAction)) {
|
if (selectedAction && !actions?.includes(selectedAction)) {
|
||||||
selectedAction = actions?.[0]
|
selectedAction = actions?.[0]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
<script>
|
<script>
|
||||||
import { Label, Body } from "@budibase/bbui"
|
import { Label } from "@budibase/bbui"
|
||||||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte"
|
||||||
|
|
||||||
export let parameters
|
export let parameters
|
||||||
export let bindings = []
|
export let bindings = []
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Body size="S">Navigate To screen, or leave blank.</Body>
|
|
||||||
<br />
|
|
||||||
<div class="root">
|
<div class="root">
|
||||||
|
<span>
|
||||||
|
You can optionally navigate to another screen after closing the screen
|
||||||
|
modal.
|
||||||
|
</span>
|
||||||
<Label small>Screen</Label>
|
<Label small>Screen</Label>
|
||||||
<DrawerBindableInput
|
<DrawerBindableInput
|
||||||
title="Destination URL"
|
title="Destination URL"
|
||||||
|
@ -20,6 +22,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
span {
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
}
|
||||||
.root {
|
.root {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
|
@ -405,6 +405,7 @@
|
||||||
bind:value={providers.oidc.config.configs[0][field.name]}
|
bind:value={providers.oidc.config.configs[0][field.name]}
|
||||||
readonly={field.readonly}
|
readonly={field.readonly}
|
||||||
placeholder={field.placeholder}
|
placeholder={field.placeholder}
|
||||||
|
dataCy={field.name}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{#if field.copyButton}
|
{#if field.copyButton}
|
||||||
|
|
|
@ -155,9 +155,9 @@ export function createAuthStore() {
|
||||||
await actions.getSelf()
|
await actions.getSelf()
|
||||||
},
|
},
|
||||||
logout: async () => {
|
logout: async () => {
|
||||||
setUser(null)
|
|
||||||
setPostLogout()
|
|
||||||
await API.logOut()
|
await API.logOut()
|
||||||
|
setPostLogout()
|
||||||
|
setUser(null)
|
||||||
await setInitInfo({})
|
await setInitInfo({})
|
||||||
},
|
},
|
||||||
updateSelf: async fields => {
|
updateSelf: async fields => {
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/cli",
|
"name": "@budibase/cli",
|
||||||
"version": "2.1.22-alpha.6",
|
"version": "2.1.32-alpha.1",
|
||||||
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
"description": "Budibase CLI, for developers, self hosting and migrations.",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -26,9 +26,9 @@
|
||||||
"outputPath": "build"
|
"outputPath": "build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/backend-core": "2.1.22-alpha.6",
|
"@budibase/backend-core": "2.1.32-alpha.1",
|
||||||
"@budibase/string-templates": "2.1.22-alpha.6",
|
"@budibase/string-templates": "2.1.32-alpha.1",
|
||||||
"@budibase/types": "2.1.22-alpha.6",
|
"@budibase/types": "2.1.32-alpha.1",
|
||||||
"axios": "0.21.2",
|
"axios": "0.21.2",
|
||||||
"chalk": "4.1.0",
|
"chalk": "4.1.0",
|
||||||
"cli-progress": "3.11.2",
|
"cli-progress": "3.11.2",
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
"joi": "17.6.0",
|
"joi": "17.6.0",
|
||||||
"lookpath": "1.1.0",
|
"lookpath": "1.1.0",
|
||||||
"node-fetch": "2",
|
"node-fetch": "2",
|
||||||
"pkg": "5.7.0",
|
"pkg": "5.8.0",
|
||||||
"posthog-node": "1.0.7",
|
"posthog-node": "1.0.7",
|
||||||
"pouchdb": "7.3.0",
|
"pouchdb": "7.3.0",
|
||||||
"pouchdb-replication-stream": "1.2.9",
|
"pouchdb-replication-stream": "1.2.9",
|
||||||
|
|
|
@ -9,11 +9,30 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/highlight" "^7.10.4"
|
"@babel/highlight" "^7.10.4"
|
||||||
|
|
||||||
|
"@babel/generator@7.18.2":
|
||||||
|
version "7.18.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.2.tgz#33873d6f89b21efe2da63fe554460f3df1c5880d"
|
||||||
|
integrity sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/types" "^7.18.2"
|
||||||
|
"@jridgewell/gen-mapping" "^0.3.0"
|
||||||
|
jsesc "^2.5.1"
|
||||||
|
|
||||||
|
"@babel/helper-string-parser@^7.19.4":
|
||||||
|
version "7.19.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
|
||||||
|
integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==
|
||||||
|
|
||||||
"@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.18.6":
|
"@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.18.6":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
|
||||||
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
|
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
|
||||||
|
|
||||||
|
"@babel/helper-validator-identifier@^7.19.1":
|
||||||
|
version "7.19.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
|
||||||
|
integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
|
||||||
|
|
||||||
"@babel/highlight@^7.10.4":
|
"@babel/highlight@^7.10.4":
|
||||||
version "7.18.6"
|
version "7.18.6"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
|
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
|
||||||
|
@ -23,10 +42,10 @@
|
||||||
chalk "^2.0.0"
|
chalk "^2.0.0"
|
||||||
js-tokens "^4.0.0"
|
js-tokens "^4.0.0"
|
||||||
|
|
||||||
"@babel/parser@7.17.10":
|
"@babel/parser@7.18.4":
|
||||||
version "7.17.10"
|
version "7.18.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.10.tgz#873b16db82a8909e0fbd7f115772f4b739f6ce78"
|
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef"
|
||||||
integrity sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==
|
integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow==
|
||||||
|
|
||||||
"@babel/runtime@^7.15.4":
|
"@babel/runtime@^7.15.4":
|
||||||
version "7.18.9"
|
version "7.18.9"
|
||||||
|
@ -35,14 +54,23 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.13.4"
|
regenerator-runtime "^0.13.4"
|
||||||
|
|
||||||
"@babel/types@7.17.10":
|
"@babel/types@7.18.4":
|
||||||
version "7.17.10"
|
version "7.18.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.10.tgz#d35d7b4467e439fcf06d195f8100e0fea7fc82c4"
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354"
|
||||||
integrity sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==
|
integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/helper-validator-identifier" "^7.16.7"
|
"@babel/helper-validator-identifier" "^7.16.7"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
|
"@babel/types@^7.18.2":
|
||||||
|
version "7.20.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.2.tgz#67ac09266606190f496322dbaff360fdaa5e7842"
|
||||||
|
integrity sha512-FnnvsNWgZCr232sqtXggapvlkk/tuwR/qhGzcmxI0GXLCjmPYQPzio2FbdlWuY6y1sHFfQKk+rRbUZ9VStQMog==
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-string-parser" "^7.19.4"
|
||||||
|
"@babel/helper-validator-identifier" "^7.19.1"
|
||||||
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
"@eslint/eslintrc@^0.4.3":
|
"@eslint/eslintrc@^0.4.3":
|
||||||
version "0.4.3"
|
version "0.4.3"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
|
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c"
|
||||||
|
@ -84,6 +112,38 @@
|
||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
|
||||||
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
|
||||||
|
|
||||||
|
"@jridgewell/gen-mapping@^0.3.0":
|
||||||
|
version "0.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9"
|
||||||
|
integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/set-array" "^1.0.1"
|
||||||
|
"@jridgewell/sourcemap-codec" "^1.4.10"
|
||||||
|
"@jridgewell/trace-mapping" "^0.3.9"
|
||||||
|
|
||||||
|
"@jridgewell/resolve-uri@3.1.0":
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
|
||||||
|
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
|
||||||
|
|
||||||
|
"@jridgewell/set-array@^1.0.1":
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
|
||||||
|
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
|
||||||
|
|
||||||
|
"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10":
|
||||||
|
version "1.4.14"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||||
|
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||||
|
|
||||||
|
"@jridgewell/trace-mapping@^0.3.9":
|
||||||
|
version "0.3.17"
|
||||||
|
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
|
||||||
|
integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/resolve-uri" "3.1.0"
|
||||||
|
"@jridgewell/sourcemap-codec" "1.4.14"
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.5":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||||
|
@ -701,7 +761,7 @@ deep-extend@^0.6.0, deep-extend@~0.6.0:
|
||||||
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
|
||||||
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
|
||||||
|
|
||||||
deep-is@^0.1.3, deep-is@~0.1.3:
|
deep-is@^0.1.3:
|
||||||
version "0.1.4"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
|
||||||
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
|
||||||
|
@ -833,18 +893,6 @@ escape-string-regexp@^4.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
|
||||||
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
|
||||||
|
|
||||||
escodegen@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd"
|
|
||||||
integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==
|
|
||||||
dependencies:
|
|
||||||
esprima "^4.0.1"
|
|
||||||
estraverse "^5.2.0"
|
|
||||||
esutils "^2.0.2"
|
|
||||||
optionator "^0.8.1"
|
|
||||||
optionalDependencies:
|
|
||||||
source-map "~0.6.1"
|
|
||||||
|
|
||||||
eslint-scope@^5.1.1:
|
eslint-scope@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
|
||||||
|
@ -925,7 +973,7 @@ espree@^7.3.0, espree@^7.3.1:
|
||||||
acorn-jsx "^5.3.1"
|
acorn-jsx "^5.3.1"
|
||||||
eslint-visitor-keys "^1.3.0"
|
eslint-visitor-keys "^1.3.0"
|
||||||
|
|
||||||
esprima@^4.0.0, esprima@^4.0.1:
|
esprima@^4.0.0:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||||
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
|
||||||
|
@ -1019,7 +1067,7 @@ fast-json-stable-stringify@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||||
|
|
||||||
fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
|
fast-levenshtein@^2.0.6:
|
||||||
version "2.0.6"
|
version "2.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
|
||||||
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
|
||||||
|
@ -1585,6 +1633,11 @@ js-yaml@^3.13.1:
|
||||||
argparse "^1.0.7"
|
argparse "^1.0.7"
|
||||||
esprima "^4.0.0"
|
esprima "^4.0.0"
|
||||||
|
|
||||||
|
jsesc@^2.5.1:
|
||||||
|
version "2.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
|
||||||
|
integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
|
||||||
|
|
||||||
json-buffer@3.0.0:
|
json-buffer@3.0.0:
|
||||||
version "3.0.0"
|
version "3.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
|
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
|
||||||
|
@ -1723,14 +1776,6 @@ levn@^0.4.1:
|
||||||
prelude-ls "^1.2.1"
|
prelude-ls "^1.2.1"
|
||||||
type-check "~0.4.0"
|
type-check "~0.4.0"
|
||||||
|
|
||||||
levn@~0.3.0:
|
|
||||||
version "0.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
|
|
||||||
integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==
|
|
||||||
dependencies:
|
|
||||||
prelude-ls "~1.1.2"
|
|
||||||
type-check "~0.3.2"
|
|
||||||
|
|
||||||
lie@3.1.1:
|
lie@3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
|
resolved "https://registry.yarnpkg.com/lie/-/lie-3.1.1.tgz#9a436b2cc7746ca59de7a41fa469b3efb76bd87e"
|
||||||
|
@ -2009,18 +2054,6 @@ onetime@^5.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn "^2.1.0"
|
mimic-fn "^2.1.0"
|
||||||
|
|
||||||
optionator@^0.8.1:
|
|
||||||
version "0.8.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
|
|
||||||
integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
|
|
||||||
dependencies:
|
|
||||||
deep-is "~0.1.3"
|
|
||||||
fast-levenshtein "~2.0.6"
|
|
||||||
levn "~0.3.0"
|
|
||||||
prelude-ls "~1.1.2"
|
|
||||||
type-check "~0.3.2"
|
|
||||||
word-wrap "~1.2.3"
|
|
||||||
|
|
||||||
optionator@^0.9.1:
|
optionator@^0.9.1:
|
||||||
version "0.9.1"
|
version "0.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
|
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
|
||||||
|
@ -2136,10 +2169,10 @@ pinkie@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||||
integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==
|
integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==
|
||||||
|
|
||||||
pkg-fetch@3.4.1:
|
pkg-fetch@3.4.2:
|
||||||
version "3.4.1"
|
version "3.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/pkg-fetch/-/pkg-fetch-3.4.1.tgz#be68bb9f7fdb0f6ed995abc518ab2e35aa64d2fd"
|
resolved "https://registry.yarnpkg.com/pkg-fetch/-/pkg-fetch-3.4.2.tgz#6f68ebc54842b73f8c0808959a9df3739dcb28b7"
|
||||||
integrity sha512-fS4cdayCa1r4jHkOKGPJKnS9PEs6OWZst+s+m0+CmhmPZObMnxoRnf9T9yUWl+lzM2b5aJF7cnQIySCT7Hq8Dg==
|
integrity sha512-0+uijmzYcnhC0hStDjm/cl2VYdrmVVBpe7Q8k9YBojxmR5tG8mvR9/nooQq3QSXiQqORDVOTY3XqMEqJVIzkHA==
|
||||||
dependencies:
|
dependencies:
|
||||||
chalk "^4.1.2"
|
chalk "^4.1.2"
|
||||||
fs-extra "^9.1.0"
|
fs-extra "^9.1.0"
|
||||||
|
@ -2150,22 +2183,22 @@ pkg-fetch@3.4.1:
|
||||||
tar-fs "^2.1.1"
|
tar-fs "^2.1.1"
|
||||||
yargs "^16.2.0"
|
yargs "^16.2.0"
|
||||||
|
|
||||||
pkg@5.7.0:
|
pkg@5.8.0:
|
||||||
version "5.7.0"
|
version "5.8.0"
|
||||||
resolved "https://registry.yarnpkg.com/pkg/-/pkg-5.7.0.tgz#6422df05e8aa147764be6ef912921d0fa719ea95"
|
resolved "https://registry.yarnpkg.com/pkg/-/pkg-5.8.0.tgz#a77644aeff0b94a1656d7f76558837f7c754a4c0"
|
||||||
integrity sha512-PTiAjNq/CGAtK5qUBR6pjheqnipTFjeecgSgIKEcAOJA4GpmZeOZC8pMOoT0rfes5vHsmcFo7wbSRTAmXQurrg==
|
integrity sha512-8h9PUDYFi+LOMLbIyGRdP21g08mAtHidSpofSrf8LWhxUWGHymaRzcopEGiynB5EhQmZUKM6PQ9kCImV2TpdjQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/parser" "7.17.10"
|
"@babel/generator" "7.18.2"
|
||||||
"@babel/types" "7.17.10"
|
"@babel/parser" "7.18.4"
|
||||||
|
"@babel/types" "7.18.4"
|
||||||
chalk "^4.1.2"
|
chalk "^4.1.2"
|
||||||
escodegen "^2.0.0"
|
|
||||||
fs-extra "^9.1.0"
|
fs-extra "^9.1.0"
|
||||||
globby "^11.1.0"
|
globby "^11.1.0"
|
||||||
into-stream "^6.0.0"
|
into-stream "^6.0.0"
|
||||||
is-core-module "2.9.0"
|
is-core-module "2.9.0"
|
||||||
minimist "^1.2.6"
|
minimist "^1.2.6"
|
||||||
multistream "^4.1.0"
|
multistream "^4.1.0"
|
||||||
pkg-fetch "3.4.1"
|
pkg-fetch "3.4.2"
|
||||||
prebuild-install "6.1.4"
|
prebuild-install "6.1.4"
|
||||||
resolve "^1.22.0"
|
resolve "^1.22.0"
|
||||||
stream-meter "^1.0.4"
|
stream-meter "^1.0.4"
|
||||||
|
@ -2262,11 +2295,6 @@ prelude-ls@^1.2.1:
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
|
||||||
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
|
||||||
|
|
||||||
prelude-ls@~1.1.2:
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
|
|
||||||
integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
|
|
||||||
|
|
||||||
prepend-http@^2.0.0:
|
prepend-http@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||||
|
@ -2611,11 +2639,6 @@ sort-keys@^2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-plain-obj "^1.0.0"
|
is-plain-obj "^1.0.0"
|
||||||
|
|
||||||
source-map@~0.6.1:
|
|
||||||
version "0.6.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
|
||||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
|
||||||
|
|
||||||
spark-md5@3.0.2:
|
spark-md5@3.0.2:
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc"
|
resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc"
|
||||||
|
@ -2907,13 +2930,6 @@ type-check@^0.4.0, type-check@~0.4.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls "^1.2.1"
|
prelude-ls "^1.2.1"
|
||||||
|
|
||||||
type-check@~0.3.2:
|
|
||||||
version "0.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
|
|
||||||
integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==
|
|
||||||
dependencies:
|
|
||||||
prelude-ls "~1.1.2"
|
|
||||||
|
|
||||||
type-fest@^0.20.2:
|
type-fest@^0.20.2:
|
||||||
version "0.20.2"
|
version "0.20.2"
|
||||||
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
|
||||||
|
@ -3036,7 +3052,7 @@ wide-align@^1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
string-width "^1.0.2 || 2 || 3 || 4"
|
string-width "^1.0.2 || 2 || 3 || 4"
|
||||||
|
|
||||||
word-wrap@^1.2.3, word-wrap@~1.2.3:
|
word-wrap@^1.2.3:
|
||||||
version "1.2.3"
|
version "1.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
||||||
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
|
||||||
|
|
|
@ -84,7 +84,6 @@
|
||||||
"description": "This component contains things within itself",
|
"description": "This component contains things within itself",
|
||||||
"icon": "Selection",
|
"icon": "Selection",
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"showSettingsBar": true,
|
|
||||||
"size": {
|
"size": {
|
||||||
"width": 400,
|
"width": 400,
|
||||||
"height": 200
|
"height": 200
|
||||||
|
@ -283,7 +282,6 @@
|
||||||
"description": "A basic html button that is ready for styling",
|
"description": "A basic html button that is ready for styling",
|
||||||
"icon": "Button",
|
"icon": "Button",
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"showSettingsBar": true,
|
|
||||||
"size": {
|
"size": {
|
||||||
"width": 105,
|
"width": 105,
|
||||||
"height": 35
|
"height": 35
|
||||||
|
@ -420,7 +418,6 @@
|
||||||
"section"
|
"section"
|
||||||
],
|
],
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"showSettingsBar": true,
|
|
||||||
"size": {
|
"size": {
|
||||||
"width": 400,
|
"width": 400,
|
||||||
"height": 100
|
"height": 100
|
||||||
|
@ -683,7 +680,6 @@
|
||||||
"illegalChildren": [
|
"illegalChildren": [
|
||||||
"section"
|
"section"
|
||||||
],
|
],
|
||||||
"showSettingsBar": true,
|
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"size": {
|
"size": {
|
||||||
"width": 400,
|
"width": 400,
|
||||||
|
@ -809,7 +805,6 @@
|
||||||
"illegalChildren": [
|
"illegalChildren": [
|
||||||
"section"
|
"section"
|
||||||
],
|
],
|
||||||
"showSettingsBar": true,
|
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"size": {
|
"size": {
|
||||||
"width": 400,
|
"width": 400,
|
||||||
|
@ -931,7 +926,6 @@
|
||||||
"tag": {
|
"tag": {
|
||||||
"name": "Tag",
|
"name": "Tag",
|
||||||
"icon": "Label",
|
"icon": "Label",
|
||||||
"showSettingsBar": true,
|
|
||||||
"size": {
|
"size": {
|
||||||
"width": 100,
|
"width": 100,
|
||||||
"height": 25
|
"height": 25
|
||||||
|
@ -1189,7 +1183,6 @@
|
||||||
"name": "Link",
|
"name": "Link",
|
||||||
"description": "A basic link component for internal and external links",
|
"description": "A basic link component for internal and external links",
|
||||||
"icon": "Link",
|
"icon": "Link",
|
||||||
"showSettingsBar": true,
|
|
||||||
"editable": true,
|
"editable": true,
|
||||||
"size": {
|
"size": {
|
||||||
"width": 200,
|
"width": 200,
|
||||||
|
@ -3927,7 +3920,6 @@
|
||||||
"dynamicfilter": {
|
"dynamicfilter": {
|
||||||
"name": "Dynamic Filter",
|
"name": "Dynamic Filter",
|
||||||
"icon": "Filter",
|
"icon": "Filter",
|
||||||
"showSettingsBar": true,
|
|
||||||
"size": {
|
"size": {
|
||||||
"width": 100,
|
"width": 100,
|
||||||
"height": 35
|
"height": 35
|
||||||
|
@ -4797,7 +4789,6 @@
|
||||||
"section"
|
"section"
|
||||||
],
|
],
|
||||||
"hasChildren": true,
|
"hasChildren": true,
|
||||||
"showSettingsBar": true,
|
|
||||||
"size": {
|
"size": {
|
||||||
"width": 400,
|
"width": 400,
|
||||||
"height": 100
|
"height": 100
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/client",
|
"name": "@budibase/client",
|
||||||
"version": "2.1.22-alpha.6",
|
"version": "2.1.32-alpha.1",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"module": "dist/budibase-client.js",
|
"module": "dist/budibase-client.js",
|
||||||
"main": "dist/budibase-client.js",
|
"main": "dist/budibase-client.js",
|
||||||
|
@ -19,9 +19,9 @@
|
||||||
"dev:builder": "rollup -cw"
|
"dev:builder": "rollup -cw"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.1.22-alpha.6",
|
"@budibase/bbui": "2.1.32-alpha.1",
|
||||||
"@budibase/frontend-core": "2.1.22-alpha.6",
|
"@budibase/frontend-core": "2.1.32-alpha.1",
|
||||||
"@budibase/string-templates": "2.1.22-alpha.6",
|
"@budibase/string-templates": "2.1.32-alpha.1",
|
||||||
"@spectrum-css/button": "^3.0.3",
|
"@spectrum-css/button": "^3.0.3",
|
||||||
"@spectrum-css/card": "^3.0.3",
|
"@spectrum-css/card": "^3.0.3",
|
||||||
"@spectrum-css/divider": "^1.0.3",
|
"@spectrum-css/divider": "^1.0.3",
|
||||||
|
@ -59,5 +59,8 @@
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"rollup-plugin-visualizer": "^5.5.4"
|
"rollup-plugin-visualizer": "^5.5.4"
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"loader-utils": "1.4.1"
|
||||||
|
},
|
||||||
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
"gitHead": "d1836a898cab3f8ab80ee6d8f42be1a9eed7dcdc"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
let structureLookupMap = {}
|
let structureLookupMap = {}
|
||||||
|
|
||||||
const registerBlockComponent = (id, order, parentId, instance) => {
|
const registerBlockComponent = (id, order, parentId, instance) => {
|
||||||
// Ensure child array exists
|
// Ensure child map exists
|
||||||
if (!structureLookupMap[parentId]) {
|
if (!structureLookupMap[parentId]) {
|
||||||
structureLookupMap[parentId] = {}
|
structureLookupMap[parentId] = {}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,14 @@
|
||||||
structureLookupMap[parentId][order] = instance
|
structureLookupMap[parentId][order] = instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const unregisterBlockComponent = (order, parentId) => {
|
||||||
|
// Ensure child map exists
|
||||||
|
if (!structureLookupMap[parentId]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete structureLookupMap[parentId][order]
|
||||||
|
}
|
||||||
|
|
||||||
const eject = () => {
|
const eject = () => {
|
||||||
// Start the new structure with the root component
|
// Start the new structure with the root component
|
||||||
let definition = structureLookupMap[$component.id][0]
|
let definition = structureLookupMap[$component.id][0]
|
||||||
|
@ -73,6 +81,7 @@
|
||||||
// We register block components with their raw props so that we can eject
|
// We register block components with their raw props so that we can eject
|
||||||
// blocks later on
|
// blocks later on
|
||||||
registerComponent: registerBlockComponent,
|
registerComponent: registerBlockComponent,
|
||||||
|
unregisterComponent: unregisterBlockComponent,
|
||||||
})
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext, onDestroy } from "svelte"
|
||||||
import { generate } from "shortid"
|
import { generate } from "shortid"
|
||||||
import { builderStore } from "../stores/builder.js"
|
import { builderStore } from "../stores/builder.js"
|
||||||
import Component from "components/Component.svelte"
|
import Component from "components/Component.svelte"
|
||||||
|
@ -41,6 +41,12 @@
|
||||||
block.registerComponent(id, order ?? 0, $component?.id, instance)
|
block.registerComponent(id, order ?? 0, $component?.id, instance)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if ($builderStore.inBuilder) {
|
||||||
|
block.unregisterComponent(order ?? 0, $component?.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Component {instance} isBlock>
|
<Component {instance} isBlock>
|
||||||
|
|
|
@ -1,242 +0,0 @@
|
||||||
<script>
|
|
||||||
import { getContext } from "svelte"
|
|
||||||
import BlockComponent from "../../BlockComponent.svelte"
|
|
||||||
import Block from "../../Block.svelte"
|
|
||||||
import Placeholder from "../Placeholder.svelte"
|
|
||||||
import { makePropSafe as safe } from "@budibase/string-templates"
|
|
||||||
|
|
||||||
export let actionType
|
|
||||||
export let dataSource
|
|
||||||
export let size
|
|
||||||
export let disabled
|
|
||||||
export let fields
|
|
||||||
export let labelPosition
|
|
||||||
export let title
|
|
||||||
export let showSaveButton
|
|
||||||
export let showDeleteButton
|
|
||||||
export let rowId
|
|
||||||
export let actionUrl
|
|
||||||
|
|
||||||
const { fetchDatasourceSchema } = getContext("sdk")
|
|
||||||
const FieldTypeToComponentMap = {
|
|
||||||
string: "stringfield",
|
|
||||||
number: "numberfield",
|
|
||||||
options: "optionsfield",
|
|
||||||
array: "multifieldselect",
|
|
||||||
boolean: "booleanfield",
|
|
||||||
longform: "longformfield",
|
|
||||||
datetime: "datetimefield",
|
|
||||||
attachment: "attachmentfield",
|
|
||||||
link: "relationshipfield",
|
|
||||||
json: "jsonfield",
|
|
||||||
barcodeqr: "codescanner",
|
|
||||||
}
|
|
||||||
|
|
||||||
let schema
|
|
||||||
let formId
|
|
||||||
let providerId
|
|
||||||
let repeaterId
|
|
||||||
|
|
||||||
$: fetchSchema(dataSource)
|
|
||||||
$: onSave = [
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Save Row",
|
|
||||||
parameters: {
|
|
||||||
providerId: formId,
|
|
||||||
tableId: dataSource?.tableId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Close Screen Modal",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Navigate To",
|
|
||||||
parameters: {
|
|
||||||
url: actionUrl,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
$: onDelete = [
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Delete Row",
|
|
||||||
parameters: {
|
|
||||||
confirm: true,
|
|
||||||
tableId: dataSource?.tableId,
|
|
||||||
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
|
|
||||||
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Close Screen Modal",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"##eventHandlerType": "Navigate To",
|
|
||||||
parameters: {
|
|
||||||
url: actionUrl,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
$: filter = [
|
|
||||||
{
|
|
||||||
field: "_id",
|
|
||||||
operator: "equal",
|
|
||||||
type: "string",
|
|
||||||
value: !rowId ? `{{ ${safe("url")}.${safe("id")} }}` : rowId,
|
|
||||||
valueType: "Binding",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
$: dataProvider = `{{ literal ${safe(providerId)} }}`
|
|
||||||
$: renderDeleteButton = showDeleteButton && actionType === "Update"
|
|
||||||
$: renderSaveButton = showSaveButton && actionType !== "View"
|
|
||||||
$: renderButtons = renderDeleteButton || renderSaveButton
|
|
||||||
$: renderHeader = renderButtons || title
|
|
||||||
|
|
||||||
const fetchSchema = async () => {
|
|
||||||
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getComponentForField = field => {
|
|
||||||
if (!field || !schema?.[field]) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
const type = schema[field].type
|
|
||||||
return FieldTypeToComponentMap[type]
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Block>
|
|
||||||
{#if fields?.length}
|
|
||||||
<BlockComponent
|
|
||||||
type="dataprovider"
|
|
||||||
context="provider"
|
|
||||||
bind:id={providerId}
|
|
||||||
props={{
|
|
||||||
dataSource,
|
|
||||||
filter,
|
|
||||||
limit: 1,
|
|
||||||
paginate: false,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<BlockComponent
|
|
||||||
type="repeater"
|
|
||||||
context="repeater"
|
|
||||||
bind:id={repeaterId}
|
|
||||||
props={{
|
|
||||||
dataProvider,
|
|
||||||
noRowsMessage: "We couldn't find a row to display",
|
|
||||||
direction: "column",
|
|
||||||
hAlign: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<BlockComponent
|
|
||||||
type="form"
|
|
||||||
props={{
|
|
||||||
actionType: actionType === "Create" ? "Create" : "Update",
|
|
||||||
dataSource,
|
|
||||||
size,
|
|
||||||
disabled: disabled || actionType === "View",
|
|
||||||
}}
|
|
||||||
styles={{
|
|
||||||
normal: {
|
|
||||||
width: "600px",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
context="form"
|
|
||||||
bind:id={formId}
|
|
||||||
>
|
|
||||||
<BlockComponent
|
|
||||||
type="container"
|
|
||||||
props={{
|
|
||||||
direction: "column",
|
|
||||||
hAlign: "stretch",
|
|
||||||
vAlign: "top",
|
|
||||||
gap: "M",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{#if renderHeader}
|
|
||||||
<BlockComponent
|
|
||||||
type="container"
|
|
||||||
props={{
|
|
||||||
direction: "row",
|
|
||||||
hAlign: "stretch",
|
|
||||||
vAlign: "center",
|
|
||||||
gap: "M",
|
|
||||||
wrap: true,
|
|
||||||
}}
|
|
||||||
order={0}
|
|
||||||
>
|
|
||||||
<BlockComponent
|
|
||||||
type="heading"
|
|
||||||
props={{ text: title || "" }}
|
|
||||||
order={0}
|
|
||||||
/>
|
|
||||||
{#if renderButtons}
|
|
||||||
<BlockComponent
|
|
||||||
type="container"
|
|
||||||
props={{
|
|
||||||
direction: "row",
|
|
||||||
hAlign: "stretch",
|
|
||||||
vAlign: "center",
|
|
||||||
gap: "M",
|
|
||||||
wrap: true,
|
|
||||||
}}
|
|
||||||
order={1}
|
|
||||||
>
|
|
||||||
{#if renderDeleteButton}
|
|
||||||
<BlockComponent
|
|
||||||
type="button"
|
|
||||||
props={{
|
|
||||||
text: "Delete",
|
|
||||||
onClick: onDelete,
|
|
||||||
quiet: true,
|
|
||||||
type: "secondary",
|
|
||||||
}}
|
|
||||||
order={0}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{#if renderSaveButton}
|
|
||||||
<BlockComponent
|
|
||||||
type="button"
|
|
||||||
props={{
|
|
||||||
text: "Save",
|
|
||||||
onClick: onSave,
|
|
||||||
type: "cta",
|
|
||||||
}}
|
|
||||||
order={1}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</BlockComponent>
|
|
||||||
{/if}
|
|
||||||
</BlockComponent>
|
|
||||||
{/if}
|
|
||||||
<BlockComponent
|
|
||||||
type="fieldgroup"
|
|
||||||
props={{ labelPosition }}
|
|
||||||
order={1}
|
|
||||||
>
|
|
||||||
{#each fields as field, idx}
|
|
||||||
{#if getComponentForField(field)}
|
|
||||||
<BlockComponent
|
|
||||||
type={getComponentForField(field)}
|
|
||||||
props={{
|
|
||||||
field,
|
|
||||||
label: field,
|
|
||||||
placeholder: field,
|
|
||||||
disabled,
|
|
||||||
}}
|
|
||||||
order={idx}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
</BlockComponent>
|
|
||||||
</BlockComponent>
|
|
||||||
</BlockComponent>
|
|
||||||
</BlockComponent>
|
|
||||||
</BlockComponent>
|
|
||||||
{:else}
|
|
||||||
<Placeholder
|
|
||||||
text="Choose your table and add some fields to your form to get started"
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</Block>
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
|
import Block from "components/Block.svelte"
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
import InnerFormBlock from "./InnerFormBlock.svelte"
|
||||||
|
|
||||||
|
export let actionType
|
||||||
|
export let dataSource
|
||||||
|
export let size
|
||||||
|
export let disabled
|
||||||
|
export let fields
|
||||||
|
export let labelPosition
|
||||||
|
export let title
|
||||||
|
export let showSaveButton
|
||||||
|
export let showDeleteButton
|
||||||
|
export let rowId
|
||||||
|
export let actionUrl
|
||||||
|
|
||||||
|
const { fetchDatasourceSchema } = getContext("sdk")
|
||||||
|
|
||||||
|
let schema
|
||||||
|
let providerId
|
||||||
|
let repeaterId
|
||||||
|
|
||||||
|
$: fetchSchema(dataSource)
|
||||||
|
$: dataProvider = `{{ literal ${safe(providerId)} }}`
|
||||||
|
$: filter = [
|
||||||
|
{
|
||||||
|
field: "_id",
|
||||||
|
operator: "equal",
|
||||||
|
type: "string",
|
||||||
|
value: !rowId ? `{{ ${safe("url")}.${safe("id")} }}` : rowId,
|
||||||
|
valueType: "Binding",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
// We could simply spread $$props into the inner form and append our
|
||||||
|
// additions, but that would create svelte warnings about unused props and
|
||||||
|
// make maintenance in future more confusing as we typically always have a
|
||||||
|
// proper mapping of schema settings to component exports, without having to
|
||||||
|
// search multiple files
|
||||||
|
$: innerProps = {
|
||||||
|
dataSource,
|
||||||
|
actionUrl,
|
||||||
|
actionType,
|
||||||
|
size,
|
||||||
|
disabled,
|
||||||
|
fields,
|
||||||
|
labelPosition,
|
||||||
|
title,
|
||||||
|
showSaveButton,
|
||||||
|
showDeleteButton,
|
||||||
|
schema,
|
||||||
|
repeaterId,
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchSchema = async () => {
|
||||||
|
schema = (await fetchDatasourceSchema(dataSource)) || {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Block>
|
||||||
|
{#if actionType === "Create"}
|
||||||
|
<BlockComponent
|
||||||
|
type="container"
|
||||||
|
props={{
|
||||||
|
direction: "column",
|
||||||
|
hAlign: "left",
|
||||||
|
vAlign: "stretch",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InnerFormBlock {...innerProps} />
|
||||||
|
</BlockComponent>
|
||||||
|
{:else}
|
||||||
|
<BlockComponent
|
||||||
|
type="dataprovider"
|
||||||
|
context="provider"
|
||||||
|
bind:id={providerId}
|
||||||
|
props={{
|
||||||
|
dataSource,
|
||||||
|
filter,
|
||||||
|
limit: 1,
|
||||||
|
paginate: false,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BlockComponent
|
||||||
|
type="repeater"
|
||||||
|
context="repeater"
|
||||||
|
bind:id={repeaterId}
|
||||||
|
props={{
|
||||||
|
dataProvider,
|
||||||
|
noRowsMessage: "We couldn't find a row to display",
|
||||||
|
direction: "column",
|
||||||
|
hAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InnerFormBlock {...innerProps} />
|
||||||
|
</BlockComponent>
|
||||||
|
</BlockComponent>
|
||||||
|
{/if}
|
||||||
|
</Block>
|
|
@ -0,0 +1,194 @@
|
||||||
|
<script>
|
||||||
|
import BlockComponent from "components/BlockComponent.svelte"
|
||||||
|
import Placeholder from "components/app/Placeholder.svelte"
|
||||||
|
import { makePropSafe as safe } from "@budibase/string-templates"
|
||||||
|
|
||||||
|
export let dataSource
|
||||||
|
export let actionUrl
|
||||||
|
export let actionType
|
||||||
|
export let size
|
||||||
|
export let disabled
|
||||||
|
export let fields
|
||||||
|
export let labelPosition
|
||||||
|
export let title
|
||||||
|
export let showSaveButton
|
||||||
|
export let showDeleteButton
|
||||||
|
export let schema
|
||||||
|
export let repeaterId
|
||||||
|
|
||||||
|
const FieldTypeToComponentMap = {
|
||||||
|
string: "stringfield",
|
||||||
|
number: "numberfield",
|
||||||
|
options: "optionsfield",
|
||||||
|
array: "multifieldselect",
|
||||||
|
boolean: "booleanfield",
|
||||||
|
longform: "longformfield",
|
||||||
|
datetime: "datetimefield",
|
||||||
|
attachment: "attachmentfield",
|
||||||
|
link: "relationshipfield",
|
||||||
|
json: "jsonfield",
|
||||||
|
barcodeqr: "codescanner",
|
||||||
|
}
|
||||||
|
|
||||||
|
let formId
|
||||||
|
|
||||||
|
$: onSave = [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Save Row",
|
||||||
|
parameters: {
|
||||||
|
providerId: formId,
|
||||||
|
tableId: dataSource?.tableId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Screen Modal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
url: actionUrl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
$: onDelete = [
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Delete Row",
|
||||||
|
parameters: {
|
||||||
|
confirm: true,
|
||||||
|
tableId: dataSource?.tableId,
|
||||||
|
rowId: `{{ ${safe(repeaterId)}.${safe("_id")} }}`,
|
||||||
|
revId: `{{ ${safe(repeaterId)}.${safe("_rev")} }}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Close Screen Modal",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"##eventHandlerType": "Navigate To",
|
||||||
|
parameters: {
|
||||||
|
url: actionUrl,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
$: renderDeleteButton = showDeleteButton && actionType === "Update"
|
||||||
|
$: renderSaveButton = showSaveButton && actionType !== "View"
|
||||||
|
$: renderButtons = renderDeleteButton || renderSaveButton
|
||||||
|
$: renderHeader = renderButtons || title
|
||||||
|
|
||||||
|
const getComponentForField = field => {
|
||||||
|
if (!field || !schema?.[field]) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const type = schema[field].type
|
||||||
|
return FieldTypeToComponentMap[type]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if fields?.length}
|
||||||
|
<BlockComponent
|
||||||
|
type="form"
|
||||||
|
props={{
|
||||||
|
actionType: actionType === "Create" ? "Create" : "Update",
|
||||||
|
dataSource,
|
||||||
|
size,
|
||||||
|
disabled: disabled || actionType === "View",
|
||||||
|
}}
|
||||||
|
styles={{
|
||||||
|
normal: {
|
||||||
|
width: "600px",
|
||||||
|
"margin-left": "auto",
|
||||||
|
"margin-right": "auto",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
context="form"
|
||||||
|
bind:id={formId}
|
||||||
|
>
|
||||||
|
<BlockComponent
|
||||||
|
type="container"
|
||||||
|
props={{
|
||||||
|
direction: "column",
|
||||||
|
hAlign: "stretch",
|
||||||
|
vAlign: "top",
|
||||||
|
gap: "M",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{#if renderHeader}
|
||||||
|
<BlockComponent
|
||||||
|
type="container"
|
||||||
|
props={{
|
||||||
|
direction: "row",
|
||||||
|
hAlign: "stretch",
|
||||||
|
vAlign: "center",
|
||||||
|
gap: "M",
|
||||||
|
wrap: true,
|
||||||
|
}}
|
||||||
|
order={0}
|
||||||
|
>
|
||||||
|
<BlockComponent
|
||||||
|
type="heading"
|
||||||
|
props={{ text: title || "" }}
|
||||||
|
order={0}
|
||||||
|
/>
|
||||||
|
{#if renderButtons}
|
||||||
|
<BlockComponent
|
||||||
|
type="container"
|
||||||
|
props={{
|
||||||
|
direction: "row",
|
||||||
|
hAlign: "stretch",
|
||||||
|
vAlign: "center",
|
||||||
|
gap: "M",
|
||||||
|
wrap: true,
|
||||||
|
}}
|
||||||
|
order={1}
|
||||||
|
>
|
||||||
|
{#if renderDeleteButton}
|
||||||
|
<BlockComponent
|
||||||
|
type="button"
|
||||||
|
props={{
|
||||||
|
text: "Delete",
|
||||||
|
onClick: onDelete,
|
||||||
|
quiet: true,
|
||||||
|
type: "secondary",
|
||||||
|
}}
|
||||||
|
order={0}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{#if renderSaveButton}
|
||||||
|
<BlockComponent
|
||||||
|
type="button"
|
||||||
|
props={{
|
||||||
|
text: "Save",
|
||||||
|
onClick: onSave,
|
||||||
|
type: "cta",
|
||||||
|
}}
|
||||||
|
order={1}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</BlockComponent>
|
||||||
|
{/if}
|
||||||
|
</BlockComponent>
|
||||||
|
{/if}
|
||||||
|
<BlockComponent type="fieldgroup" props={{ labelPosition }} order={1}>
|
||||||
|
{#each fields as field, idx}
|
||||||
|
{#if getComponentForField(field)}
|
||||||
|
<BlockComponent
|
||||||
|
type={getComponentForField(field)}
|
||||||
|
props={{
|
||||||
|
field,
|
||||||
|
label: field,
|
||||||
|
placeholder: field,
|
||||||
|
disabled,
|
||||||
|
}}
|
||||||
|
order={idx}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</BlockComponent>
|
||||||
|
</BlockComponent>
|
||||||
|
</BlockComponent>
|
||||||
|
{:else}
|
||||||
|
<Placeholder
|
||||||
|
text="Choose your table and add some fields to your form to get started"
|
||||||
|
/>
|
||||||
|
{/if}
|
|
@ -1,6 +1,6 @@
|
||||||
export { default as tableblock } from "./TableBlock.svelte"
|
export { default as tableblock } from "./TableBlock.svelte"
|
||||||
export { default as cardsblock } from "./CardsBlock.svelte"
|
export { default as cardsblock } from "./CardsBlock.svelte"
|
||||||
export { default as repeaterblock } from "./RepeaterBlock.svelte"
|
export { default as repeaterblock } from "./RepeaterBlock.svelte"
|
||||||
export { default as formblock } from "./FormBlock.svelte"
|
export { default as formblock } from "./form/FormBlock.svelte"
|
||||||
export { default as chartblock } from "./ChartBlock.svelte"
|
export { default as chartblock } from "./ChartBlock.svelte"
|
||||||
export { default as rowexplorer } from "./RowExplorer.svelte"
|
export { default as rowexplorer } from "./RowExplorer.svelte"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
export let label
|
export let label
|
||||||
export let placeholder
|
export let placeholder
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let enableTime = false
|
export let enableTime = true
|
||||||
export let timeOnly = false
|
export let timeOnly = false
|
||||||
export let time24hr = false
|
export let time24hr = false
|
||||||
export let ignoreTimezones = false
|
export let ignoreTimezones = false
|
||||||
|
|
|
@ -16,13 +16,16 @@
|
||||||
let measured = false
|
let measured = false
|
||||||
|
|
||||||
$: definition = $componentStore.selectedComponentDefinition
|
$: definition = $componentStore.selectedComponentDefinition
|
||||||
$: showBar = definition?.showSettingsBar && !$dndIsDragging
|
$: showBar =
|
||||||
|
definition?.showSettingsBar !== false && !$dndIsDragging && definition
|
||||||
$: {
|
$: {
|
||||||
if (!showBar) {
|
if (!showBar) {
|
||||||
measured = false
|
measured = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$: settings = getBarSettings(definition)
|
$: settings = getBarSettings(definition)
|
||||||
|
$: isScreen =
|
||||||
|
$builderStore.selectedComponentId === $builderStore.screen?.props?._id
|
||||||
|
|
||||||
const getBarSettings = definition => {
|
const getBarSettings = definition => {
|
||||||
let allSettings = []
|
let allSettings = []
|
||||||
|
@ -152,10 +155,11 @@
|
||||||
{:else if setting.type === "color"}
|
{:else if setting.type === "color"}
|
||||||
<SettingsColorPicker prop={setting.key} />
|
<SettingsColorPicker prop={setting.key} />
|
||||||
{/if}
|
{/if}
|
||||||
{#if setting.barSeparator !== false}
|
{#if setting.barSeparator !== false && (settings.length != idx + 1 || !isScreen)}
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if !isScreen}
|
||||||
<SettingsButton
|
<SettingsButton
|
||||||
icon="Duplicate"
|
icon="Duplicate"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
@ -168,10 +172,13 @@
|
||||||
<SettingsButton
|
<SettingsButton
|
||||||
icon="Delete"
|
icon="Delete"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
builderStore.actions.deleteComponent($builderStore.selectedComponentId)
|
builderStore.actions.deleteComponent(
|
||||||
|
$builderStore.selectedComponentId
|
||||||
|
)
|
||||||
}}
|
}}
|
||||||
title="Delete component"
|
title="Delete component"
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/frontend-core",
|
"name": "@budibase/frontend-core",
|
||||||
"version": "2.1.22-alpha.6",
|
"version": "2.1.32-alpha.1",
|
||||||
"description": "Budibase frontend core libraries used in builder and client",
|
"description": "Budibase frontend core libraries used in builder and client",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"svelte": "src/index.js",
|
"svelte": "src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "2.1.22-alpha.6",
|
"@budibase/bbui": "2.1.32-alpha.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"svelte": "^3.46.2"
|
"svelte": "^3.46.2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/sdk",
|
"name": "@budibase/sdk",
|
||||||
"version": "2.1.22-alpha.6",
|
"version": "2.1.32-alpha.1",
|
||||||
"description": "Budibase Public API SDK",
|
"description": "Budibase Public API SDK",
|
||||||
"author": "Budibase",
|
"author": "Budibase",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
|
|
|
@ -2,7 +2,7 @@ module FetchMock {
|
||||||
const fetch = jest.requireActual("node-fetch")
|
const fetch = jest.requireActual("node-fetch")
|
||||||
let failCount = 0
|
let failCount = 0
|
||||||
|
|
||||||
module.exports = async (url: any, opts: any) => {
|
const func = async (url: any, opts: any) => {
|
||||||
function json(body: any, status = 200) {
|
function json(body: any, status = 200) {
|
||||||
return {
|
return {
|
||||||
status,
|
status,
|
||||||
|
@ -106,4 +106,8 @@ module FetchMock {
|
||||||
}
|
}
|
||||||
return fetch(url, opts)
|
return fetch(url, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func.Headers = fetch.Headers
|
||||||
|
|
||||||
|
module.exports = func
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ if (!process.env.CI) {
|
||||||
"@budibase/backend-core/(.*)": "<rootDir>/../backend-core/$1",
|
"@budibase/backend-core/(.*)": "<rootDir>/../backend-core/$1",
|
||||||
"@budibase/backend-core": "<rootDir>/../backend-core/src",
|
"@budibase/backend-core": "<rootDir>/../backend-core/src",
|
||||||
"@budibase/types": "<rootDir>/../types/src",
|
"@budibase/types": "<rootDir>/../types/src",
|
||||||
|
"^axios.*$": "<rootDir>/node_modules/axios/lib/axios.js",
|
||||||
}
|
}
|
||||||
// add pro sources if they exist
|
// add pro sources if they exist
|
||||||
if (fs.existsSync("../../../budibase-pro")) {
|
if (fs.existsSync("../../../budibase-pro")) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@budibase/server",
|
"name": "@budibase/server",
|
||||||
"email": "hi@budibase.com",
|
"email": "hi@budibase.com",
|
||||||
"version": "2.1.22-alpha.6",
|
"version": "2.1.32-alpha.1",
|
||||||
"description": "Budibase Web Server",
|
"description": "Budibase Web Server",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -43,11 +43,11 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "10.0.3",
|
"@apidevtools/swagger-parser": "10.0.3",
|
||||||
"@budibase/backend-core": "2.1.22-alpha.6",
|
"@budibase/backend-core": "2.1.32-alpha.1",
|
||||||
"@budibase/client": "2.1.22-alpha.6",
|
"@budibase/client": "2.1.32-alpha.1",
|
||||||
"@budibase/pro": "2.1.22-alpha.6",
|
"@budibase/pro": "2.1.31",
|
||||||
"@budibase/string-templates": "2.1.22-alpha.6",
|
"@budibase/string-templates": "2.1.32-alpha.1",
|
||||||
"@budibase/types": "2.1.22-alpha.6",
|
"@budibase/types": "2.1.32-alpha.1",
|
||||||
"@bull-board/api": "3.7.0",
|
"@bull-board/api": "3.7.0",
|
||||||
"@bull-board/koa": "3.9.4",
|
"@bull-board/koa": "3.9.4",
|
||||||
"@elastic/elasticsearch": "7.10.0",
|
"@elastic/elasticsearch": "7.10.0",
|
||||||
|
|
|
@ -14,17 +14,16 @@ import {
|
||||||
DocumentType,
|
DocumentType,
|
||||||
AppStatus,
|
AppStatus,
|
||||||
} from "../../db/utils"
|
} from "../../db/utils"
|
||||||
const {
|
import {
|
||||||
BUILTIN_ROLE_IDS,
|
db as dbCore,
|
||||||
AccessController,
|
roles,
|
||||||
} = require("@budibase/backend-core/roles")
|
cache,
|
||||||
const { CacheKeys, bustCache } = require("@budibase/backend-core/cache")
|
tenancy,
|
||||||
const {
|
context,
|
||||||
getAllApps,
|
errors,
|
||||||
isDevAppID,
|
events,
|
||||||
getProdAppID,
|
migrations,
|
||||||
Replication,
|
} from "@budibase/backend-core"
|
||||||
} = require("@budibase/backend-core/db")
|
|
||||||
import { USERS_TABLE_SCHEMA } from "../../constants"
|
import { USERS_TABLE_SCHEMA } from "../../constants"
|
||||||
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
||||||
import { clientLibraryPath, stringToReadStream } from "../../utilities"
|
import { clientLibraryPath, stringToReadStream } from "../../utilities"
|
||||||
|
@ -34,15 +33,11 @@ import {
|
||||||
backupClientLibrary,
|
backupClientLibrary,
|
||||||
revertClientLibrary,
|
revertClientLibrary,
|
||||||
} from "../../utilities/fileSystem/clientLibrary"
|
} from "../../utilities/fileSystem/clientLibrary"
|
||||||
const { getTenantId, isMultiTenant } = require("@budibase/backend-core/tenancy")
|
|
||||||
import { syncGlobalUsers } from "./user"
|
import { syncGlobalUsers } from "./user"
|
||||||
const { app: appCache } = require("@budibase/backend-core/cache")
|
|
||||||
import { cleanupAutomations } from "../../automations/utils"
|
import { cleanupAutomations } from "../../automations/utils"
|
||||||
import { context } from "@budibase/backend-core"
|
|
||||||
import { checkAppMetadata } from "../../automations/logging"
|
import { checkAppMetadata } from "../../automations/logging"
|
||||||
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
||||||
import { quotas, groups } from "@budibase/pro"
|
import { quotas, groups } from "@budibase/pro"
|
||||||
import { errors, events, migrations } from "@budibase/backend-core"
|
|
||||||
import { App, Layout, Screen, MigrationType } from "@budibase/types"
|
import { App, Layout, Screen, MigrationType } from "@budibase/types"
|
||||||
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
import { BASE_LAYOUT_PROP_IDS } from "../../constants/layouts"
|
||||||
import { enrichPluginURLs } from "../../utilities/plugins"
|
import { enrichPluginURLs } from "../../utilities/plugins"
|
||||||
|
@ -75,7 +70,7 @@ async function getScreens() {
|
||||||
|
|
||||||
function getUserRoleId(ctx: any) {
|
function getUserRoleId(ctx: any) {
|
||||||
return !ctx.user.role || !ctx.user.role._id
|
return !ctx.user.role || !ctx.user.role._id
|
||||||
? BUILTIN_ROLE_IDS.PUBLIC
|
? roles.BUILTIN_ROLE_IDS.PUBLIC
|
||||||
: ctx.user.role._id
|
: ctx.user.role._id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,7 +118,7 @@ const checkAppName = (
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createInstance(template: any) {
|
async function createInstance(template: any) {
|
||||||
const tenantId = isMultiTenant() ? getTenantId() : null
|
const tenantId = tenancy.isMultiTenant() ? tenancy.getTenantId() : null
|
||||||
const baseAppId = generateAppID(tenantId)
|
const baseAppId = generateAppID(tenantId)
|
||||||
const appId = generateDevAppID(baseAppId)
|
const appId = generateDevAppID(baseAppId)
|
||||||
await context.updateAppId(appId)
|
await context.updateAppId(appId)
|
||||||
|
@ -162,7 +157,7 @@ async function createInstance(template: any) {
|
||||||
export const fetch = async (ctx: any) => {
|
export const fetch = async (ctx: any) => {
|
||||||
const dev = ctx.query && ctx.query.status === AppStatus.DEV
|
const dev = ctx.query && ctx.query.status === AppStatus.DEV
|
||||||
const all = ctx.query && ctx.query.status === AppStatus.ALL
|
const all = ctx.query && ctx.query.status === AppStatus.ALL
|
||||||
const apps = await getAllApps({ dev, all })
|
const apps = (await dbCore.getAllApps({ dev, all })) as App[]
|
||||||
|
|
||||||
const appIds = apps
|
const appIds = apps
|
||||||
.filter((app: any) => app.status === "development")
|
.filter((app: any) => app.status === "development")
|
||||||
|
@ -187,7 +182,7 @@ export const fetch = async (ctx: any) => {
|
||||||
export const fetchAppDefinition = async (ctx: any) => {
|
export const fetchAppDefinition = async (ctx: any) => {
|
||||||
const layouts = await getLayouts()
|
const layouts = await getLayouts()
|
||||||
const userRoleId = getUserRoleId(ctx)
|
const userRoleId = getUserRoleId(ctx)
|
||||||
const accessController = new AccessController()
|
const accessController = new roles.AccessController()
|
||||||
const screens = await accessController.checkScreensAccess(
|
const screens = await accessController.checkScreensAccess(
|
||||||
await getScreens(),
|
await getScreens(),
|
||||||
userRoleId
|
userRoleId
|
||||||
|
@ -211,7 +206,7 @@ export const fetchAppPackage = async (ctx: any) => {
|
||||||
// Only filter screens if the user is not a builder
|
// Only filter screens if the user is not a builder
|
||||||
if (!(ctx.user.builder && ctx.user.builder.global)) {
|
if (!(ctx.user.builder && ctx.user.builder.global)) {
|
||||||
const userRoleId = getUserRoleId(ctx)
|
const userRoleId = getUserRoleId(ctx)
|
||||||
const accessController = new AccessController()
|
const accessController = new roles.AccessController()
|
||||||
screens = await accessController.checkScreensAccess(screens, userRoleId)
|
screens = await accessController.checkScreensAccess(screens, userRoleId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,7 +219,7 @@ export const fetchAppPackage = async (ctx: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const performAppCreate = async (ctx: any) => {
|
const performAppCreate = async (ctx: any) => {
|
||||||
const apps = await getAllApps({ dev: true })
|
const apps = await dbCore.getAllApps({ dev: true })
|
||||||
const name = ctx.request.body.name
|
const name = ctx.request.body.name
|
||||||
checkAppName(ctx, apps, name)
|
checkAppName(ctx, apps, name)
|
||||||
const url = getAppUrl(ctx)
|
const url = getAppUrl(ctx)
|
||||||
|
@ -254,7 +249,7 @@ const performAppCreate = async (ctx: any) => {
|
||||||
url: url,
|
url: url,
|
||||||
template: templateKey,
|
template: templateKey,
|
||||||
instance,
|
instance,
|
||||||
tenantId: getTenantId(),
|
tenantId: tenancy.getTenantId(),
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
status: AppStatus.DEV,
|
status: AppStatus.DEV,
|
||||||
|
@ -313,7 +308,7 @@ const performAppCreate = async (ctx: any) => {
|
||||||
await createApp(appId)
|
await createApp(appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
await appCache.invalidateAppMetadata(appId, newApplication)
|
await cache.app.invalidateAppMetadata(appId, newApplication)
|
||||||
return newApplication
|
return newApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +338,7 @@ const creationEvents = async (request: any, app: App) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const appPostCreate = async (ctx: any, app: App) => {
|
const appPostCreate = async (ctx: any, app: App) => {
|
||||||
const tenantId = getTenantId()
|
const tenantId = tenancy.getTenantId()
|
||||||
await migrations.backPopulateMigrations({
|
await migrations.backPopulateMigrations({
|
||||||
type: MigrationType.APP,
|
type: MigrationType.APP,
|
||||||
tenantId,
|
tenantId,
|
||||||
|
@ -356,7 +351,9 @@ const appPostCreate = async (ctx: any, app: App) => {
|
||||||
const rowCount = rows ? rows.length : 0
|
const rowCount = rows ? rows.length : 0
|
||||||
if (rowCount) {
|
if (rowCount) {
|
||||||
try {
|
try {
|
||||||
await quotas.addRows(rowCount)
|
await context.doInAppContext(app.appId, () => {
|
||||||
|
return quotas.addRows(rowCount)
|
||||||
|
})
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (err.code && err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
if (err.code && err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
||||||
// this import resulted in row usage exceeding the quota
|
// this import resulted in row usage exceeding the quota
|
||||||
|
@ -374,7 +371,7 @@ const appPostCreate = async (ctx: any, app: App) => {
|
||||||
export const create = async (ctx: any) => {
|
export const create = async (ctx: any) => {
|
||||||
const newApplication = await quotas.addApp(() => performAppCreate(ctx))
|
const newApplication = await quotas.addApp(() => performAppCreate(ctx))
|
||||||
await appPostCreate(ctx, newApplication)
|
await appPostCreate(ctx, newApplication)
|
||||||
await bustCache(CacheKeys.CHECKLIST)
|
await cache.bustCache(cache.CacheKeys.CHECKLIST)
|
||||||
ctx.body = newApplication
|
ctx.body = newApplication
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
}
|
}
|
||||||
|
@ -382,7 +379,7 @@ export const create = async (ctx: any) => {
|
||||||
// This endpoint currently operates as a PATCH rather than a PUT
|
// This endpoint currently operates as a PATCH rather than a PUT
|
||||||
// Thus name and url fields are handled only if present
|
// Thus name and url fields are handled only if present
|
||||||
export const update = async (ctx: any) => {
|
export const update = async (ctx: any) => {
|
||||||
const apps = await getAllApps({ dev: true })
|
const apps = await dbCore.getAllApps({ dev: true })
|
||||||
// validation
|
// validation
|
||||||
const name = ctx.request.body.name
|
const name = ctx.request.body.name
|
||||||
if (name) {
|
if (name) {
|
||||||
|
@ -455,7 +452,7 @@ const destroyApp = async (ctx: any) => {
|
||||||
let isUnpublish = ctx.query && ctx.query.unpublish
|
let isUnpublish = ctx.query && ctx.query.unpublish
|
||||||
|
|
||||||
if (isUnpublish) {
|
if (isUnpublish) {
|
||||||
appId = getProdAppID(appId)
|
appId = dbCore.getProdAppID(appId)
|
||||||
}
|
}
|
||||||
|
|
||||||
const db = isUnpublish ? context.getProdAppDB() : context.getAppDB()
|
const db = isUnpublish ? context.getProdAppDB() : context.getAppDB()
|
||||||
|
@ -481,7 +478,7 @@ const destroyApp = async (ctx: any) => {
|
||||||
else {
|
else {
|
||||||
await removeAppFromUserRoles(ctx, appId)
|
await removeAppFromUserRoles(ctx, appId)
|
||||||
}
|
}
|
||||||
await appCache.invalidateAppMetadata(appId)
|
await cache.app.invalidateAppMetadata(appId)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -517,19 +514,17 @@ export const sync = async (ctx: any, next: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const appId = ctx.params.appId
|
const appId = ctx.params.appId
|
||||||
if (!isDevAppID(appId)) {
|
if (!dbCore.isDevAppID(appId)) {
|
||||||
ctx.throw(400, "This action cannot be performed for production apps")
|
ctx.throw(400, "This action cannot be performed for production apps")
|
||||||
}
|
}
|
||||||
|
|
||||||
// replicate prod to dev
|
// replicate prod to dev
|
||||||
const prodAppId = getProdAppID(appId)
|
const prodAppId = dbCore.getProdAppID(appId)
|
||||||
|
|
||||||
try {
|
|
||||||
// specific case, want to make sure setup is skipped
|
// specific case, want to make sure setup is skipped
|
||||||
const prodDb = context.getProdAppDB({ skip_setup: true })
|
const prodDb = context.getProdAppDB({ skip_setup: true })
|
||||||
const info = await prodDb.info()
|
const exists = await prodDb.exists()
|
||||||
if (info.error) throw info.error
|
if (!exists) {
|
||||||
} catch (err) {
|
|
||||||
// the database doesn't exist. Don't replicate
|
// the database doesn't exist. Don't replicate
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
|
@ -538,7 +533,7 @@ export const sync = async (ctx: any, next: any) => {
|
||||||
return next()
|
return next()
|
||||||
}
|
}
|
||||||
|
|
||||||
const replication = new Replication({
|
const replication = new dbCore.Replication({
|
||||||
source: prodAppId,
|
source: prodAppId,
|
||||||
target: appId,
|
target: appId,
|
||||||
})
|
})
|
||||||
|
@ -579,7 +574,7 @@ export const updateAppPackage = async (appPackage: any, appId: any) => {
|
||||||
|
|
||||||
await db.put(newAppPackage)
|
await db.put(newAppPackage)
|
||||||
// remove any cached metadata, so that it will be updated
|
// remove any cached metadata, so that it will be updated
|
||||||
await appCache.invalidateAppMetadata(appId)
|
await cache.app.invalidateAppMetadata(appId)
|
||||||
return newAppPackage
|
return newAppPackage
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { InternalTables } from "../../db/utils"
|
||||||
import { getFullUser } from "../../utilities/users"
|
import { getFullUser } from "../../utilities/users"
|
||||||
import { roles, context } from "@budibase/backend-core"
|
import { roles, context } from "@budibase/backend-core"
|
||||||
import { groups } from "@budibase/pro"
|
import { groups } from "@budibase/pro"
|
||||||
|
import { ContextUser, User } from "@budibase/types"
|
||||||
|
|
||||||
const PUBLIC_ROLE = roles.BUILTIN_ROLE_IDS.PUBLIC
|
const PUBLIC_ROLE = roles.BUILTIN_ROLE_IDS.PUBLIC
|
||||||
|
|
||||||
|
@ -24,7 +25,7 @@ export async function fetchSelf(ctx: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const appId = context.getAppId()
|
const appId = context.getAppId()
|
||||||
const user = await getFullUser(ctx, userId)
|
const user: ContextUser = await getFullUser(ctx, userId)
|
||||||
// this shouldn't be returned by the app self
|
// this shouldn't be returned by the app self
|
||||||
delete user.roles
|
delete user.roles
|
||||||
// forward the csrf token from the session
|
// forward the csrf token from the session
|
||||||
|
@ -34,7 +35,7 @@ export async function fetchSelf(ctx: any) {
|
||||||
const db = context.getAppDB()
|
const db = context.getAppDB()
|
||||||
// check for group permissions
|
// check for group permissions
|
||||||
if (!user.roleId || user.roleId === PUBLIC_ROLE) {
|
if (!user.roleId || user.roleId === PUBLIC_ROLE) {
|
||||||
const groupRoleId = await groups.getGroupRoleId(user, appId)
|
const groupRoleId = await groups.getGroupRoleId(user as User, appId)
|
||||||
user.roleId = groupRoleId || user.roleId
|
user.roleId = groupRoleId || user.roleId
|
||||||
}
|
}
|
||||||
// remove the full roles structure
|
// remove the full roles structure
|
||||||
|
|
|
@ -1,23 +1,11 @@
|
||||||
import Deployment from "./Deployment"
|
import Deployment from "./Deployment"
|
||||||
import {
|
import { context, db as dbCore, events, cache } from "@budibase/backend-core"
|
||||||
getDevelopmentAppID,
|
|
||||||
getProdAppID,
|
|
||||||
Replication,
|
|
||||||
} from "@budibase/backend-core/db"
|
|
||||||
import { DocumentType, getAutomationParams } from "../../../db/utils"
|
import { DocumentType, getAutomationParams } from "../../../db/utils"
|
||||||
import {
|
import {
|
||||||
clearMetadata,
|
clearMetadata,
|
||||||
disableAllCrons,
|
disableAllCrons,
|
||||||
enableCronTrigger,
|
enableCronTrigger,
|
||||||
} from "../../../automations/utils"
|
} from "../../../automations/utils"
|
||||||
import { app as appCache } from "@budibase/backend-core/cache"
|
|
||||||
import {
|
|
||||||
getAppDB,
|
|
||||||
getAppId,
|
|
||||||
getDevAppDB,
|
|
||||||
getProdAppDB,
|
|
||||||
} from "@budibase/backend-core/context"
|
|
||||||
import { events } from "@budibase/backend-core"
|
|
||||||
import { backups } from "@budibase/pro"
|
import { backups } from "@budibase/pro"
|
||||||
import { AppBackupTrigger } from "@budibase/types"
|
import { AppBackupTrigger } from "@budibase/types"
|
||||||
|
|
||||||
|
@ -49,7 +37,7 @@ async function checkAllDeployments(deployments: any) {
|
||||||
|
|
||||||
async function storeDeploymentHistory(deployment: any) {
|
async function storeDeploymentHistory(deployment: any) {
|
||||||
const deploymentJSON = deployment.getJSON()
|
const deploymentJSON = deployment.getJSON()
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
||||||
let deploymentDoc
|
let deploymentDoc
|
||||||
try {
|
try {
|
||||||
|
@ -77,7 +65,7 @@ async function storeDeploymentHistory(deployment: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initDeployedApp(prodAppId: any) {
|
async function initDeployedApp(prodAppId: any) {
|
||||||
const db = getProdAppDB()
|
const db = context.getProdAppDB()
|
||||||
console.log("Reading automation docs")
|
console.log("Reading automation docs")
|
||||||
const automations = (
|
const automations = (
|
||||||
await db.allDocs(
|
await db.allDocs(
|
||||||
|
@ -103,9 +91,9 @@ async function initDeployedApp(prodAppId: any) {
|
||||||
async function deployApp(deployment: any, userId: string) {
|
async function deployApp(deployment: any, userId: string) {
|
||||||
let replication
|
let replication
|
||||||
try {
|
try {
|
||||||
const appId = getAppId()
|
const appId = context.getAppId()!
|
||||||
const devAppId = getDevelopmentAppID(appId)
|
const devAppId = dbCore.getDevelopmentAppID(appId)
|
||||||
const productionAppId = getProdAppID(appId)
|
const productionAppId = dbCore.getProdAppID(appId)
|
||||||
|
|
||||||
// don't try this if feature isn't allowed, will error
|
// don't try this if feature isn't allowed, will error
|
||||||
if (await backups.isEnabled()) {
|
if (await backups.isEnabled()) {
|
||||||
|
@ -122,8 +110,8 @@ async function deployApp(deployment: any, userId: string) {
|
||||||
source: devAppId,
|
source: devAppId,
|
||||||
target: productionAppId,
|
target: productionAppId,
|
||||||
}
|
}
|
||||||
replication = new Replication(config)
|
replication = new dbCore.Replication(config)
|
||||||
const devDb = getDevAppDB()
|
const devDb = context.getDevAppDB()
|
||||||
console.log("Compacting development DB")
|
console.log("Compacting development DB")
|
||||||
await devDb.compact()
|
await devDb.compact()
|
||||||
console.log("Replication object created")
|
console.log("Replication object created")
|
||||||
|
@ -131,7 +119,7 @@ async function deployApp(deployment: any, userId: string) {
|
||||||
console.log("replication complete.. replacing app meta doc")
|
console.log("replication complete.. replacing app meta doc")
|
||||||
// app metadata is excluded as it is likely to be in conflict
|
// app metadata is excluded as it is likely to be in conflict
|
||||||
// replicate the app metadata document manually
|
// replicate the app metadata document manually
|
||||||
const db = getProdAppDB()
|
const db = context.getProdAppDB()
|
||||||
const appDoc = await devDb.get(DocumentType.APP_METADATA)
|
const appDoc = await devDb.get(DocumentType.APP_METADATA)
|
||||||
try {
|
try {
|
||||||
const prodAppDoc = await db.get(DocumentType.APP_METADATA)
|
const prodAppDoc = await db.get(DocumentType.APP_METADATA)
|
||||||
|
@ -147,7 +135,7 @@ async function deployApp(deployment: any, userId: string) {
|
||||||
// remove automation errors if they exist
|
// remove automation errors if they exist
|
||||||
delete appDoc.automationErrors
|
delete appDoc.automationErrors
|
||||||
await db.put(appDoc)
|
await db.put(appDoc)
|
||||||
await appCache.invalidateAppMetadata(productionAppId)
|
await cache.app.invalidateAppMetadata(productionAppId)
|
||||||
console.log("New app doc written successfully.")
|
console.log("New app doc written successfully.")
|
||||||
await initDeployedApp(productionAppId)
|
await initDeployedApp(productionAppId)
|
||||||
console.log("Deployed app initialised, setting deployment to successful")
|
console.log("Deployed app initialised, setting deployment to successful")
|
||||||
|
@ -170,7 +158,7 @@ async function deployApp(deployment: any, userId: string) {
|
||||||
|
|
||||||
export async function fetchDeployments(ctx: any) {
|
export async function fetchDeployments(ctx: any) {
|
||||||
try {
|
try {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
||||||
const { updated, deployments } = await checkAllDeployments(deploymentDoc)
|
const { updated, deployments } = await checkAllDeployments(deploymentDoc)
|
||||||
if (updated) {
|
if (updated) {
|
||||||
|
@ -184,7 +172,7 @@ export async function fetchDeployments(ctx: any) {
|
||||||
|
|
||||||
export async function deploymentProgress(ctx: any) {
|
export async function deploymentProgress(ctx: any) {
|
||||||
try {
|
try {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
||||||
ctx.body = deploymentDoc[ctx.params.deploymentId]
|
ctx.body = deploymentDoc[ctx.params.deploymentId]
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -197,7 +185,7 @@ export async function deploymentProgress(ctx: any) {
|
||||||
|
|
||||||
const isFirstDeploy = async () => {
|
const isFirstDeploy = async () => {
|
||||||
try {
|
try {
|
||||||
const db = getProdAppDB()
|
const db = context.getProdAppDB()
|
||||||
await db.get(DocumentType.APP_METADATA)
|
await db.get(DocumentType.APP_METADATA)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.status === 404) {
|
if (e.status === 404) {
|
||||||
|
|
|
@ -1,29 +1,23 @@
|
||||||
const fetch = require("node-fetch")
|
import fetch from "node-fetch"
|
||||||
const env = require("../../environment")
|
import env from "../../environment"
|
||||||
const { checkSlashesInUrl } = require("../../utilities")
|
import { checkSlashesInUrl } from "../../utilities"
|
||||||
const { request } = require("../../utilities/workerRequests")
|
import { request } from "../../utilities/workerRequests"
|
||||||
const { clearLock } = require("../../utilities/redis")
|
import { clearLock as redisClearLock } from "../../utilities/redis"
|
||||||
const { Replication, getProdAppID } = require("@budibase/backend-core/db")
|
import { DocumentType } from "../../db/utils"
|
||||||
const { DocumentType } = require("../../db/utils")
|
import { context } from "@budibase/backend-core"
|
||||||
const { app: appCache } = require("@budibase/backend-core/cache")
|
import { events, db as dbCore, cache } from "@budibase/backend-core"
|
||||||
const { getProdAppDB, getAppDB } = require("@budibase/backend-core/context")
|
|
||||||
const { events } = require("@budibase/backend-core")
|
|
||||||
|
|
||||||
async function redirect(ctx, method, path = "global") {
|
async function redirect(ctx: any, method: string, path: string = "global") {
|
||||||
const { devPath } = ctx.params
|
const { devPath } = ctx.params
|
||||||
const queryString = ctx.originalUrl.split("?")[1] || ""
|
const queryString = ctx.originalUrl.split("?")[1] || ""
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
checkSlashesInUrl(
|
checkSlashesInUrl(
|
||||||
`${env.WORKER_URL}/api/${path}/${devPath}?${queryString}`
|
`${env.WORKER_URL}/api/${path}/${devPath}?${queryString}`
|
||||||
),
|
),
|
||||||
request(
|
request(ctx, {
|
||||||
ctx,
|
|
||||||
{
|
|
||||||
method,
|
method,
|
||||||
body: ctx.request.body,
|
body: ctx.request.body,
|
||||||
},
|
})
|
||||||
true
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
const err = await response.text()
|
const err = await response.text()
|
||||||
|
@ -46,28 +40,28 @@ async function redirect(ctx, method, path = "global") {
|
||||||
ctx.cookies
|
ctx.cookies
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.buildRedirectGet = path => {
|
export function buildRedirectGet(path: string) {
|
||||||
return async ctx => {
|
return async (ctx: any) => {
|
||||||
await redirect(ctx, "GET", path)
|
await redirect(ctx, "GET", path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.buildRedirectPost = path => {
|
export function buildRedirectPost(path: string) {
|
||||||
return async ctx => {
|
return async (ctx: any) => {
|
||||||
await redirect(ctx, "POST", path)
|
await redirect(ctx, "POST", path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.buildRedirectDelete = path => {
|
export function buildRedirectDelete(path: string) {
|
||||||
return async ctx => {
|
return async (ctx: any) => {
|
||||||
await redirect(ctx, "DELETE", path)
|
await redirect(ctx, "DELETE", path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.clearLock = async ctx => {
|
export async function clearLock(ctx: any) {
|
||||||
const { appId } = ctx.params
|
const { appId } = ctx.params
|
||||||
try {
|
try {
|
||||||
await clearLock(appId, ctx.user)
|
await redisClearLock(appId, ctx.user)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.throw(400, `Unable to remove lock. ${err}`)
|
ctx.throw(400, `Unable to remove lock. ${err}`)
|
||||||
}
|
}
|
||||||
|
@ -76,16 +70,16 @@ exports.clearLock = async ctx => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.revert = async ctx => {
|
export async function revert(ctx: any) {
|
||||||
const { appId } = ctx.params
|
const { appId } = ctx.params
|
||||||
const productionAppId = getProdAppID(appId)
|
const productionAppId = dbCore.getProdAppID(appId)
|
||||||
|
|
||||||
// App must have been deployed first
|
// App must have been deployed first
|
||||||
try {
|
try {
|
||||||
const db = getProdAppDB({ skip_setup: true })
|
const db = context.getProdAppDB({ skip_setup: true })
|
||||||
const info = await db.info()
|
const exists = await db.exists()
|
||||||
if (info.error) {
|
if (!exists) {
|
||||||
throw info.error
|
throw new Error("App must be deployed to be reverted.")
|
||||||
}
|
}
|
||||||
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
const deploymentDoc = await db.get(DocumentType.DEPLOYMENTS)
|
||||||
if (
|
if (
|
||||||
|
@ -98,7 +92,7 @@ exports.revert = async ctx => {
|
||||||
return ctx.throw(400, "App has not yet been deployed")
|
return ctx.throw(400, "App has not yet been deployed")
|
||||||
}
|
}
|
||||||
|
|
||||||
const replication = new Replication({
|
const replication = new dbCore.Replication({
|
||||||
source: productionAppId,
|
source: productionAppId,
|
||||||
target: appId,
|
target: appId,
|
||||||
})
|
})
|
||||||
|
@ -109,12 +103,12 @@ exports.revert = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// update appID in reverted app to be dev version again
|
// update appID in reverted app to be dev version again
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
const appDoc = await db.get(DocumentType.APP_METADATA)
|
const appDoc = await db.get(DocumentType.APP_METADATA)
|
||||||
appDoc.appId = appId
|
appDoc.appId = appId
|
||||||
appDoc.instance._id = appId
|
appDoc.instance._id = appId
|
||||||
await db.put(appDoc)
|
await db.put(appDoc)
|
||||||
await appCache.invalidateAppMetadata(appId)
|
await cache.app.invalidateAppMetadata(appId)
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Reverted changes successfully.",
|
message: "Reverted changes successfully.",
|
||||||
}
|
}
|
||||||
|
@ -126,7 +120,7 @@ exports.revert = async ctx => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getBudibaseVersion = async ctx => {
|
export async function getBudibaseVersion(ctx: any) {
|
||||||
const version = require("../../../package.json").version
|
const version = require("../../../package.json").version
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
version,
|
version,
|
|
@ -1,5 +1,4 @@
|
||||||
const { getAllApps } = require("@budibase/backend-core/db")
|
import { db as dbCore, context } from "@budibase/backend-core"
|
||||||
const { doInAppContext } = require("@budibase/backend-core/context")
|
|
||||||
import { search as stringSearch, addRev } from "./utils"
|
import { search as stringSearch, addRev } from "./utils"
|
||||||
import * as controller from "../application"
|
import * as controller from "../application"
|
||||||
import { Application } from "../../../definitions/common"
|
import { Application } from "../../../definitions/common"
|
||||||
|
@ -15,15 +14,22 @@ function fixAppID(app: Application, params: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setResponseApp(ctx: any) {
|
async function setResponseApp(ctx: any) {
|
||||||
if (ctx.body && ctx.body.appId && (!ctx.params || !ctx.params.appId)) {
|
const appId = ctx.body?.appId
|
||||||
ctx.params = { appId: ctx.body.appId }
|
if (appId && (!ctx.params || !ctx.params.appId)) {
|
||||||
|
ctx.params = { appId }
|
||||||
|
}
|
||||||
|
if (appId) {
|
||||||
|
await context.doInContext(appId, () => {
|
||||||
|
return controller.fetchAppPackage(ctx)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
return controller.fetchAppPackage(ctx)
|
||||||
}
|
}
|
||||||
await controller.fetchAppPackage(ctx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function search(ctx: any, next: any) {
|
export async function search(ctx: any, next: any) {
|
||||||
const { name } = ctx.request.body
|
const { name } = ctx.request.body
|
||||||
const apps = await getAllApps({ all: true })
|
const apps = await dbCore.getAllApps({ all: true })
|
||||||
ctx.body = stringSearch(apps, name)
|
ctx.body = stringSearch(apps, name)
|
||||||
await next()
|
await next()
|
||||||
}
|
}
|
||||||
|
@ -41,7 +47,7 @@ export async function create(ctx: any, next: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function read(ctx: any, next: any) {
|
export async function read(ctx: any, next: any) {
|
||||||
await doInAppContext(ctx.params.appId, async () => {
|
await context.doInAppContext(ctx.params.appId, async () => {
|
||||||
await setResponseApp(ctx)
|
await setResponseApp(ctx)
|
||||||
await next()
|
await next()
|
||||||
})
|
})
|
||||||
|
@ -49,7 +55,7 @@ export async function read(ctx: any, next: any) {
|
||||||
|
|
||||||
export async function update(ctx: any, next: any) {
|
export async function update(ctx: any, next: any) {
|
||||||
ctx.request.body = await addRev(fixAppID(ctx.request.body, ctx.params))
|
ctx.request.body = await addRev(fixAppID(ctx.request.body, ctx.params))
|
||||||
await doInAppContext(ctx.params.appId, async () => {
|
await context.doInAppContext(ctx.params.appId, async () => {
|
||||||
await controller.update(ctx)
|
await controller.update(ctx)
|
||||||
await setResponseApp(ctx)
|
await setResponseApp(ctx)
|
||||||
await next()
|
await next()
|
||||||
|
@ -57,7 +63,7 @@ export async function update(ctx: any, next: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(ctx: any, next: any) {
|
export async function destroy(ctx: any, next: any) {
|
||||||
await doInAppContext(ctx.params.appId, async () => {
|
await context.doInAppContext(ctx.params.appId, async () => {
|
||||||
// get the app before deleting it
|
// get the app before deleting it
|
||||||
await setResponseApp(ctx)
|
await setResponseApp(ctx)
|
||||||
const body = ctx.body
|
const body = ctx.body
|
||||||
|
|
|
@ -5,11 +5,8 @@ import { save as saveDatasource } from "../datasource"
|
||||||
import { RestImporter } from "./import"
|
import { RestImporter } from "./import"
|
||||||
import { invalidateDynamicVariables } from "../../../threads/utils"
|
import { invalidateDynamicVariables } from "../../../threads/utils"
|
||||||
import { QUERY_THREAD_TIMEOUT } from "../../../environment"
|
import { QUERY_THREAD_TIMEOUT } from "../../../environment"
|
||||||
import { getAppDB } from "@budibase/backend-core/context"
|
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { events } from "@budibase/backend-core"
|
import { events, context, utils, constants } from "@budibase/backend-core"
|
||||||
import { getCookie } from "@budibase/backend-core/utils"
|
|
||||||
import { Cookies, Configs } from "@budibase/backend-core/constants"
|
|
||||||
|
|
||||||
const Runner = new Thread(ThreadType.QUERY, {
|
const Runner = new Thread(ThreadType.QUERY, {
|
||||||
timeoutMs: QUERY_THREAD_TIMEOUT || 10000,
|
timeoutMs: QUERY_THREAD_TIMEOUT || 10000,
|
||||||
|
@ -28,7 +25,7 @@ function enrichQueries(input: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetch(ctx: any) {
|
export async function fetch(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
||||||
const body = await db.allDocs(
|
const body = await db.allDocs(
|
||||||
getQueryParams(null, {
|
getQueryParams(null, {
|
||||||
|
@ -81,7 +78,7 @@ const _import = async (ctx: any) => {
|
||||||
export { _import as import }
|
export { _import as import }
|
||||||
|
|
||||||
export async function save(ctx: any) {
|
export async function save(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
const query = ctx.request.body
|
const query = ctx.request.body
|
||||||
|
|
||||||
const datasource = await db.get(query.datasourceId)
|
const datasource = await db.get(query.datasourceId)
|
||||||
|
@ -103,7 +100,7 @@ export async function save(ctx: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function find(ctx: any) {
|
export async function find(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
const query = enrichQueries(await db.get(ctx.params.queryId))
|
const query = enrichQueries(await db.get(ctx.params.queryId))
|
||||||
// remove properties that could be dangerous in real app
|
// remove properties that could be dangerous in real app
|
||||||
if (isProdAppID(ctx.appId)) {
|
if (isProdAppID(ctx.appId)) {
|
||||||
|
@ -115,13 +112,13 @@ export async function find(ctx: any) {
|
||||||
|
|
||||||
//Required to discern between OIDC OAuth config entries
|
//Required to discern between OIDC OAuth config entries
|
||||||
function getOAuthConfigCookieId(ctx: any) {
|
function getOAuthConfigCookieId(ctx: any) {
|
||||||
if (ctx.user.providerType === Configs.OIDC) {
|
if (ctx.user.providerType === constants.Config.OIDC) {
|
||||||
return getCookie(ctx, Cookies.OIDC_CONFIG)
|
return utils.getCookie(ctx, constants.Cookie.OIDC_CONFIG)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAuthConfig(ctx: any) {
|
function getAuthConfig(ctx: any) {
|
||||||
const authCookie = getCookie(ctx, Cookies.Auth)
|
const authCookie = utils.getCookie(ctx, constants.Cookie.Auth)
|
||||||
let authConfigCtx: any = {}
|
let authConfigCtx: any = {}
|
||||||
authConfigCtx["configId"] = getOAuthConfigCookieId(ctx)
|
authConfigCtx["configId"] = getOAuthConfigCookieId(ctx)
|
||||||
authConfigCtx["sessionId"] = authCookie ? authCookie.sessionId : null
|
authConfigCtx["sessionId"] = authCookie ? authCookie.sessionId : null
|
||||||
|
@ -129,7 +126,7 @@ function getAuthConfig(ctx: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function preview(ctx: any) {
|
export async function preview(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
||||||
const datasource = await db.get(ctx.request.body.datasourceId)
|
const datasource = await db.get(ctx.request.body.datasourceId)
|
||||||
const query = ctx.request.body
|
const query = ctx.request.body
|
||||||
|
@ -201,7 +198,7 @@ async function execute(
|
||||||
ctx: any,
|
ctx: any,
|
||||||
opts: any = { rowsOnly: false, isAutomation: false }
|
opts: any = { rowsOnly: false, isAutomation: false }
|
||||||
) {
|
) {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
|
|
||||||
const query = await db.get(ctx.params.queryId)
|
const query = await db.get(ctx.params.queryId)
|
||||||
const datasource = await db.get(query.datasourceId)
|
const datasource = await db.get(query.datasourceId)
|
||||||
|
@ -267,7 +264,7 @@ export async function executeV2(
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeDynamicVariables = async (queryId: any) => {
|
const removeDynamicVariables = async (queryId: any) => {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
const query = await db.get(queryId)
|
const query = await db.get(queryId)
|
||||||
const datasource = await db.get(query.datasourceId)
|
const datasource = await db.get(query.datasourceId)
|
||||||
const dynamicVariables = datasource.config.dynamicVariables
|
const dynamicVariables = datasource.config.dynamicVariables
|
||||||
|
@ -288,7 +285,7 @@ const removeDynamicVariables = async (queryId: any) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(ctx: any) {
|
export async function destroy(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
const queryId = ctx.params.queryId
|
const queryId = ctx.params.queryId
|
||||||
await removeDynamicVariables(queryId)
|
await removeDynamicVariables(queryId)
|
||||||
const query = await db.get(queryId)
|
const query = await db.get(queryId)
|
||||||
|
|
|
@ -6,7 +6,7 @@ const {
|
||||||
DocumentType,
|
DocumentType,
|
||||||
InternalTables,
|
InternalTables,
|
||||||
} = require("../../../db/utils")
|
} = require("../../../db/utils")
|
||||||
const { dangerousGetDB } = require("@budibase/backend-core/db")
|
const { getDB } = require("@budibase/backend-core/db")
|
||||||
const userController = require("../user")
|
const userController = require("../user")
|
||||||
const {
|
const {
|
||||||
inputProcessing,
|
inputProcessing,
|
||||||
|
@ -251,7 +251,7 @@ exports.fetch = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async ctx => {
|
exports.find = async ctx => {
|
||||||
const db = dangerousGetDB(ctx.appId)
|
const db = getDB(ctx.appId)
|
||||||
const table = await db.get(ctx.params.tableId)
|
const table = await db.get(ctx.params.tableId)
|
||||||
let row = await findRow(ctx, ctx.params.tableId, ctx.params.rowId)
|
let row = await findRow(ctx, ctx.params.tableId, ctx.params.rowId)
|
||||||
row = await outputProcessing(table, row)
|
row = await outputProcessing(table, row)
|
||||||
|
|
|
@ -62,6 +62,7 @@ exports.updateRelatedFormula = async (table, enrichedRows) => {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,7 +125,6 @@ exports.finaliseRow = async (
|
||||||
dynamic: false,
|
dynamic: false,
|
||||||
contextRows: enrichedRow,
|
contextRows: enrichedRow,
|
||||||
})
|
})
|
||||||
|
|
||||||
// don't worry about rev, tables handle rev/lastID updates
|
// don't worry about rev, tables handle rev/lastID updates
|
||||||
// if another row has been written since processing this will
|
// if another row has been written since processing this will
|
||||||
// handle the auto ID clash
|
// handle the auto ID clash
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { updateLinks, EventType } from "../../../db/linkedRows"
|
||||||
import { getRowParams, generateTableID } from "../../../db/utils"
|
import { getRowParams, generateTableID } from "../../../db/utils"
|
||||||
import { FieldTypes } from "../../../constants"
|
import { FieldTypes } from "../../../constants"
|
||||||
import { TableSaveFunctions, hasTypeChanged, handleDataImport } from "./utils"
|
import { TableSaveFunctions, hasTypeChanged, handleDataImport } from "./utils"
|
||||||
const { getAppDB } = require("@budibase/backend-core/context")
|
import { context } from "@budibase/backend-core"
|
||||||
import { isTest } from "../../../environment"
|
import { isTest } from "../../../environment"
|
||||||
import {
|
import {
|
||||||
cleanupAttachments,
|
cleanupAttachments,
|
||||||
|
@ -35,7 +35,7 @@ function checkAutoColumns(table: Table, oldTable: Table) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function save(ctx: any) {
|
export async function save(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
const { dataImport, ...rest } = ctx.request.body
|
const { dataImport, ...rest } = ctx.request.body
|
||||||
let tableToSave = {
|
let tableToSave = {
|
||||||
type: "table",
|
type: "table",
|
||||||
|
@ -138,19 +138,19 @@ export async function save(ctx: any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function destroy(ctx: any) {
|
export async function destroy(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
const tableToDelete = await db.get(ctx.params.tableId)
|
const tableToDelete = await db.get(ctx.params.tableId)
|
||||||
|
|
||||||
// Delete all rows for that table
|
// Delete all rows for that table
|
||||||
const rows = await db.allDocs(
|
const rowsData = await db.allDocs(
|
||||||
getRowParams(ctx.params.tableId, null, {
|
getRowParams(ctx.params.tableId, null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
await db.bulkDocs(
|
await db.bulkDocs(
|
||||||
rows.rows.map((row: any) => ({ ...row.doc, _deleted: true }))
|
rowsData.rows.map((row: any) => ({ ...row.doc, _deleted: true }))
|
||||||
)
|
)
|
||||||
await quotas.removeRows(rows.rows.length, {
|
await quotas.removeRows(rowsData.rows.length, {
|
||||||
tableId: ctx.params.tableId,
|
tableId: ctx.params.tableId,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ export async function destroy(ctx: any) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// don't remove the table itself until very end
|
// don't remove the table itself until very end
|
||||||
await db.remove(tableToDelete)
|
await db.remove(tableToDelete._id, tableToDelete._rev)
|
||||||
|
|
||||||
// remove table search index
|
// remove table search index
|
||||||
if (!isTest() || env.COUCH_DB_URL) {
|
if (!isTest() || env.COUCH_DB_URL) {
|
||||||
|
@ -179,7 +179,9 @@ export async function destroy(ctx: any) {
|
||||||
oldTable: null,
|
oldTable: null,
|
||||||
deletion: true,
|
deletion: true,
|
||||||
})
|
})
|
||||||
await cleanupAttachments(tableToDelete, { rows })
|
await cleanupAttachments(tableToDelete, {
|
||||||
|
rows: rowsData.rows.map((row: any) => row.doc),
|
||||||
|
})
|
||||||
return tableToDelete
|
return tableToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@ const controller = require("../controllers/automation")
|
||||||
const authorized = require("../../middleware/authorized")
|
const authorized = require("../../middleware/authorized")
|
||||||
const {
|
const {
|
||||||
BUILDER,
|
BUILDER,
|
||||||
PermissionLevels,
|
PermissionLevel,
|
||||||
PermissionTypes,
|
PermissionType,
|
||||||
} = require("@budibase/backend-core/permissions")
|
} = require("@budibase/backend-core/permissions")
|
||||||
const { bodyResource, paramResource } = require("../../middleware/resourceId")
|
const { bodyResource, paramResource } = require("../../middleware/resourceId")
|
||||||
const {
|
const {
|
||||||
|
@ -71,14 +71,14 @@ router
|
||||||
"/api/automations/:id/trigger",
|
"/api/automations/:id/trigger",
|
||||||
appInfoMiddleware({ appType: AppType.PROD }),
|
appInfoMiddleware({ appType: AppType.PROD }),
|
||||||
paramResource("id"),
|
paramResource("id"),
|
||||||
authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE),
|
authorized(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
||||||
controller.trigger
|
controller.trigger
|
||||||
)
|
)
|
||||||
.post(
|
.post(
|
||||||
"/api/automations/:id/test",
|
"/api/automations/:id/test",
|
||||||
appInfoMiddleware({ appType: AppType.DEV }),
|
appInfoMiddleware({ appType: AppType.DEV }),
|
||||||
paramResource("id"),
|
paramResource("id"),
|
||||||
authorized(PermissionTypes.AUTOMATION, PermissionLevels.EXECUTE),
|
authorized(PermissionType.AUTOMATION, PermissionLevel.EXECUTE),
|
||||||
controller.test
|
controller.test
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue