Published apps, automations and query count quotas
This commit is contained in:
parent
62b360b9ea
commit
336f0ff4a0
|
@ -21,7 +21,7 @@ const getPublicError = err => {
|
||||||
type: err.type,
|
type: err.type,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err.code) {
|
if (err.code && context[err.code]) {
|
||||||
error = {
|
error = {
|
||||||
...error,
|
...error,
|
||||||
// get any additional context from this error
|
// get any additional context from this error
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
aria-valuenow={$progress}
|
aria-valuenow={$progress}
|
||||||
aria-valuemin="0"
|
aria-valuemin="0"
|
||||||
aria-valuemax="100"
|
aria-valuemax="100"
|
||||||
style={width ? `width: ${width}px;` : ""}
|
style={width ? `width: ${width};` : ""}
|
||||||
>
|
>
|
||||||
{#if $$slots}
|
{#if $$slots}
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -1,29 +1,29 @@
|
||||||
const env = require("../../environment")
|
import env from "../../environment"
|
||||||
const packageJson = require("../../../package.json")
|
import packageJson from "../../../package.json"
|
||||||
const {
|
import {
|
||||||
createLinkView,
|
createLinkView,
|
||||||
createRoutingView,
|
createRoutingView,
|
||||||
createAllSearchIndex,
|
createAllSearchIndex,
|
||||||
} = require("../../db/views/staticViews")
|
} from "../../db/views/staticViews"
|
||||||
const {
|
import {
|
||||||
getTemplateStream,
|
getTemplateStream,
|
||||||
createApp,
|
createApp,
|
||||||
deleteApp,
|
deleteApp,
|
||||||
} = require("../../utilities/fileSystem")
|
} from "../../utilities/fileSystem"
|
||||||
const {
|
import {
|
||||||
generateAppID,
|
generateAppID,
|
||||||
getLayoutParams,
|
getLayoutParams,
|
||||||
getScreenParams,
|
getScreenParams,
|
||||||
generateDevAppID,
|
generateDevAppID,
|
||||||
DocumentTypes,
|
DocumentTypes,
|
||||||
AppStatus,
|
AppStatus,
|
||||||
} = require("../../db/utils")
|
} from "../../db/utils"
|
||||||
const {
|
const {
|
||||||
BUILTIN_ROLE_IDS,
|
BUILTIN_ROLE_IDS,
|
||||||
AccessController,
|
AccessController,
|
||||||
} = require("@budibase/backend-core/roles")
|
} = require("@budibase/backend-core/roles")
|
||||||
const { BASE_LAYOUTS } = require("../../constants/layouts")
|
import { BASE_LAYOUTS } from "../../constants/layouts"
|
||||||
const { cloneDeep } = require("lodash/fp")
|
import { cloneDeep } from "lodash/fp"
|
||||||
const { processObject } = require("@budibase/string-templates")
|
const { processObject } = require("@budibase/string-templates")
|
||||||
const {
|
const {
|
||||||
getAllApps,
|
getAllApps,
|
||||||
|
@ -31,24 +31,27 @@ const {
|
||||||
getProdAppID,
|
getProdAppID,
|
||||||
Replication,
|
Replication,
|
||||||
} = require("@budibase/backend-core/db")
|
} = require("@budibase/backend-core/db")
|
||||||
const { USERS_TABLE_SCHEMA } = require("../../constants")
|
import { USERS_TABLE_SCHEMA } from "../../constants"
|
||||||
const { removeAppFromUserRoles } = require("../../utilities/workerRequests")
|
import { removeAppFromUserRoles } from "../../utilities/workerRequests"
|
||||||
const { clientLibraryPath, stringToReadStream } = require("../../utilities")
|
import { clientLibraryPath, stringToReadStream } from "../../utilities"
|
||||||
const { getAllLocks } = require("../../utilities/redis")
|
import { getAllLocks } from "../../utilities/redis"
|
||||||
const {
|
import {
|
||||||
updateClientLibrary,
|
updateClientLibrary,
|
||||||
backupClientLibrary,
|
backupClientLibrary,
|
||||||
revertClientLibrary,
|
revertClientLibrary,
|
||||||
} = require("../../utilities/fileSystem/clientLibrary")
|
} from "../../utilities/fileSystem/clientLibrary"
|
||||||
const { getTenantId, isMultiTenant } = require("@budibase/backend-core/tenancy")
|
const { getTenantId, isMultiTenant } = require("@budibase/backend-core/tenancy")
|
||||||
const { syncGlobalUsers } = require("./user")
|
import { syncGlobalUsers } from "./user"
|
||||||
const { app: appCache } = require("@budibase/backend-core/cache")
|
const { app: appCache } = require("@budibase/backend-core/cache")
|
||||||
const { cleanupAutomations } = require("../../automations/utils")
|
import { cleanupAutomations } from "../../automations/utils"
|
||||||
const {
|
const {
|
||||||
getAppDB,
|
getAppDB,
|
||||||
getProdAppDB,
|
getProdAppDB,
|
||||||
updateAppId,
|
updateAppId,
|
||||||
} = require("@budibase/backend-core/context")
|
} = require("@budibase/backend-core/context")
|
||||||
|
import { getUniqueRows } from "../../utilities/usageQuota/rows"
|
||||||
|
import { quotas } from "@budibase/pro"
|
||||||
|
import { errors } from "@budibase/backend-core"
|
||||||
|
|
||||||
const URL_REGEX_SLASH = /\/|\\/g
|
const URL_REGEX_SLASH = /\/|\\/g
|
||||||
|
|
||||||
|
@ -61,7 +64,7 @@ async function getLayouts() {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
).rows.map(row => row.doc)
|
).rows.map((row: any) => row.doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getScreens() {
|
async function getScreens() {
|
||||||
|
@ -72,16 +75,16 @@ async function getScreens() {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
).rows.map(row => row.doc)
|
).rows.map((row: any) => row.doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUserRoleId(ctx) {
|
function getUserRoleId(ctx: any) {
|
||||||
return !ctx.user.role || !ctx.user.role._id
|
return !ctx.user.role || !ctx.user.role._id
|
||||||
? BUILTIN_ROLE_IDS.PUBLIC
|
? BUILTIN_ROLE_IDS.PUBLIC
|
||||||
: ctx.user.role._id
|
: ctx.user.role._id
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.getAppUrl = ctx => {
|
export const getAppUrl = (ctx: any) => {
|
||||||
// construct the url
|
// construct the url
|
||||||
let url
|
let url
|
||||||
if (ctx.request.body.url) {
|
if (ctx.request.body.url) {
|
||||||
|
@ -97,29 +100,34 @@ exports.getAppUrl = ctx => {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkAppUrl = (ctx, apps, url, currentAppId) => {
|
const checkAppUrl = (ctx: any, apps: any, url: any, currentAppId?: string) => {
|
||||||
if (currentAppId) {
|
if (currentAppId) {
|
||||||
apps = apps.filter(app => app.appId !== currentAppId)
|
apps = apps.filter((app: any) => app.appId !== currentAppId)
|
||||||
}
|
}
|
||||||
if (apps.some(app => app.url === url)) {
|
if (apps.some((app: any) => app.url === url)) {
|
||||||
ctx.throw(400, "App URL is already in use.")
|
ctx.throw(400, "App URL is already in use.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkAppName = (ctx, apps, name, currentAppId) => {
|
const checkAppName = (
|
||||||
|
ctx: any,
|
||||||
|
apps: any,
|
||||||
|
name: any,
|
||||||
|
currentAppId?: string
|
||||||
|
) => {
|
||||||
// TODO: Replace with Joi
|
// TODO: Replace with Joi
|
||||||
if (!name) {
|
if (!name) {
|
||||||
ctx.throw(400, "Name is required")
|
ctx.throw(400, "Name is required")
|
||||||
}
|
}
|
||||||
if (currentAppId) {
|
if (currentAppId) {
|
||||||
apps = apps.filter(app => app.appId !== currentAppId)
|
apps = apps.filter((app: any) => app.appId !== currentAppId)
|
||||||
}
|
}
|
||||||
if (apps.some(app => app.name === name)) {
|
if (apps.some((app: any) => app.name === name)) {
|
||||||
ctx.throw(400, "App name is already in use.")
|
ctx.throw(400, "App name is already in use.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createInstance(template) {
|
async function createInstance(template: any) {
|
||||||
const tenantId = isMultiTenant() ? getTenantId() : null
|
const tenantId = isMultiTenant() ? getTenantId() : null
|
||||||
const baseAppId = generateAppID(tenantId)
|
const baseAppId = generateAppID(tenantId)
|
||||||
const appId = generateDevAppID(baseAppId)
|
const appId = generateDevAppID(baseAppId)
|
||||||
|
@ -160,7 +168,7 @@ async function createInstance(template) {
|
||||||
return { _id: appId }
|
return { _id: appId }
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
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 getAllApps({ dev, all })
|
||||||
|
@ -172,7 +180,7 @@ exports.fetch = async ctx => {
|
||||||
if (app.status !== "development") {
|
if (app.status !== "development") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const lock = locks.find(lock => lock.appId === app.appId)
|
const lock = locks.find((lock: any) => lock.appId === app.appId)
|
||||||
if (lock) {
|
if (lock) {
|
||||||
app.lockedBy = lock.user
|
app.lockedBy = lock.user
|
||||||
} else {
|
} else {
|
||||||
|
@ -185,7 +193,7 @@ exports.fetch = async ctx => {
|
||||||
ctx.body = apps
|
ctx.body = apps
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchAppDefinition = async ctx => {
|
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 AccessController()
|
||||||
|
@ -200,7 +208,7 @@ exports.fetchAppDefinition = async ctx => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchAppPackage = async ctx => {
|
export const fetchAppPackage = async (ctx: any) => {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const application = await db.get(DocumentTypes.APP_METADATA)
|
const application = await db.get(DocumentTypes.APP_METADATA)
|
||||||
const layouts = await getLayouts()
|
const layouts = await getLayouts()
|
||||||
|
@ -221,7 +229,7 @@ exports.fetchAppPackage = async ctx => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.create = async ctx => {
|
const performAppCreate = async (ctx: any) => {
|
||||||
const apps = await getAllApps({ dev: true })
|
const apps = await getAllApps({ dev: true })
|
||||||
const name = ctx.request.body.name
|
const name = ctx.request.body.name
|
||||||
checkAppName(ctx, apps, name)
|
checkAppName(ctx, apps, name)
|
||||||
|
@ -229,7 +237,7 @@ exports.create = async ctx => {
|
||||||
checkAppUrl(ctx, apps, url)
|
checkAppUrl(ctx, apps, url)
|
||||||
|
|
||||||
const { useTemplate, templateKey, templateString } = ctx.request.body
|
const { useTemplate, templateKey, templateString } = ctx.request.body
|
||||||
const instanceConfig = {
|
const instanceConfig: any = {
|
||||||
useTemplate,
|
useTemplate,
|
||||||
key: templateKey,
|
key: templateKey,
|
||||||
templateString,
|
templateString,
|
||||||
|
@ -279,13 +287,41 @@ exports.create = async ctx => {
|
||||||
}
|
}
|
||||||
|
|
||||||
await appCache.invalidateAppMetadata(appId, newApplication)
|
await appCache.invalidateAppMetadata(appId, newApplication)
|
||||||
ctx.status = 200
|
return newApplication
|
||||||
|
}
|
||||||
|
|
||||||
|
const appPostCreate = async (ctx: any, appId: string) => {
|
||||||
|
// app import & template creation
|
||||||
|
if (ctx.request.body.useTemplate === "true") {
|
||||||
|
const rows = await getUniqueRows([appId])
|
||||||
|
const rowCount = rows ? rows.length : 0
|
||||||
|
if (rowCount) {
|
||||||
|
try {
|
||||||
|
await quotas.addRows(rowCount)
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.code && err.code === errors.codes.USAGE_LIMIT_EXCEEDED) {
|
||||||
|
// this import resulted in row usage exceeding the quota
|
||||||
|
// delete the app
|
||||||
|
// skip pre and post steps as no rows have been added to quotas yet
|
||||||
|
ctx.params.appId = appId
|
||||||
|
await destroyApp(ctx)
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const create = async (ctx: any) => {
|
||||||
|
const newApplication = await quotas.addApp(() => performAppCreate(ctx))
|
||||||
|
await appPostCreate(ctx, newApplication.appId)
|
||||||
ctx.body = newApplication
|
ctx.body = newApplication
|
||||||
|
ctx.status = 200
|
||||||
}
|
}
|
||||||
|
|
||||||
// This endpoint currently operates as a PATCH rather than a PUT
|
// This endpoint currently operates as a PATCH rather than a PUT
|
||||||
// Thus name and url fields are handled only if present
|
// Thus name and url fields are handled only if present
|
||||||
exports.update = async ctx => {
|
export const update = async (ctx: any) => {
|
||||||
const apps = await getAllApps({ dev: true })
|
const apps = await getAllApps({ dev: true })
|
||||||
// validation
|
// validation
|
||||||
const name = ctx.request.body.name
|
const name = ctx.request.body.name
|
||||||
|
@ -303,7 +339,7 @@ exports.update = async ctx => {
|
||||||
ctx.body = data
|
ctx.body = data
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.updateClient = async ctx => {
|
export const updateClient = async (ctx: any) => {
|
||||||
// Get current app version
|
// Get current app version
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const application = await db.get(DocumentTypes.APP_METADATA)
|
const application = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
@ -325,7 +361,7 @@ exports.updateClient = async ctx => {
|
||||||
ctx.body = data
|
ctx.body = data
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.revertClient = async ctx => {
|
export const revertClient = async (ctx: any) => {
|
||||||
// Check app can be reverted
|
// Check app can be reverted
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const application = await db.get(DocumentTypes.APP_METADATA)
|
const application = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
@ -348,10 +384,15 @@ exports.revertClient = async ctx => {
|
||||||
ctx.body = data
|
ctx.body = data
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.delete = async ctx => {
|
const destroyApp = async (ctx: any) => {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
|
|
||||||
const result = await db.destroy()
|
const result = await db.destroy()
|
||||||
|
if (ctx.query.unpublish) {
|
||||||
|
await quotas.removePublishedApp()
|
||||||
|
} else {
|
||||||
|
await quotas.removeApp()
|
||||||
|
}
|
||||||
/* istanbul ignore next */
|
/* istanbul ignore next */
|
||||||
if (!env.isTest() && !ctx.query.unpublish) {
|
if (!env.isTest() && !ctx.query.unpublish) {
|
||||||
await deleteApp(ctx.params.appId)
|
await deleteApp(ctx.params.appId)
|
||||||
|
@ -362,12 +403,30 @@ exports.delete = async ctx => {
|
||||||
// make sure the app/role doesn't stick around after the app has been deleted
|
// make sure the app/role doesn't stick around after the app has been deleted
|
||||||
await removeAppFromUserRoles(ctx, ctx.params.appId)
|
await removeAppFromUserRoles(ctx, ctx.params.appId)
|
||||||
await appCache.invalidateAppMetadata(ctx.params.appId)
|
await appCache.invalidateAppMetadata(ctx.params.appId)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const preDestroyApp = async (ctx: any) => {
|
||||||
|
const rows = await getUniqueRows([ctx.appId])
|
||||||
|
ctx.rowCount = rows.length
|
||||||
|
}
|
||||||
|
|
||||||
|
const postDestroyApp = async (ctx: any) => {
|
||||||
|
const rowCount = ctx.rowCount
|
||||||
|
if (rowCount) {
|
||||||
|
await quotas.removeRows(rowCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const destroy = async (ctx: any) => {
|
||||||
|
await preDestroyApp(ctx)
|
||||||
|
const result = await destroyApp(ctx)
|
||||||
|
await postDestroyApp(ctx)
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = result
|
ctx.body = result
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.sync = async (ctx, next) => {
|
export const sync = async (ctx: any, next: any) => {
|
||||||
const appId = ctx.params.appId
|
const appId = ctx.params.appId
|
||||||
if (!isDevAppID(appId)) {
|
if (!isDevAppID(appId)) {
|
||||||
ctx.throw(400, "This action cannot be performed for production apps")
|
ctx.throw(400, "This action cannot be performed for production apps")
|
||||||
|
@ -397,7 +456,7 @@ exports.sync = async (ctx, next) => {
|
||||||
let error
|
let error
|
||||||
try {
|
try {
|
||||||
await replication.replicate({
|
await replication.replicate({
|
||||||
filter: function (doc) {
|
filter: function (doc: any) {
|
||||||
return doc._id !== DocumentTypes.APP_METADATA
|
return doc._id !== DocumentTypes.APP_METADATA
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -417,7 +476,7 @@ exports.sync = async (ctx, next) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateAppPackage = async (appPackage, appId) => {
|
const updateAppPackage = async (appPackage: any, appId: any) => {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const application = await db.get(DocumentTypes.APP_METADATA)
|
const application = await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
|
||||||
|
@ -436,7 +495,7 @@ const updateAppPackage = async (appPackage, appId) => {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
const createEmptyAppPackage = async (ctx, app) => {
|
const createEmptyAppPackage = async (ctx: any, app: any) => {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
|
|
||||||
let screensAndLayouts = []
|
let screensAndLayouts = []
|
|
@ -1,20 +1,18 @@
|
||||||
const Deployment = require("./Deployment")
|
import Deployment from "./Deployment"
|
||||||
const {
|
import {
|
||||||
Replication,
|
Replication,
|
||||||
getProdAppID,
|
getProdAppID,
|
||||||
getDevelopmentAppID,
|
getDevelopmentAppID,
|
||||||
} = require("@budibase/backend-core/db")
|
} from "@budibase/backend-core/db"
|
||||||
const { DocumentTypes, getAutomationParams } = require("../../../db/utils")
|
import { DocumentTypes, getAutomationParams } from "../../../db/utils"
|
||||||
const {
|
import { disableAllCrons, enableCronTrigger } from "../../../automations/utils"
|
||||||
disableAllCrons,
|
import { app as appCache } from "@budibase/backend-core/cache"
|
||||||
enableCronTrigger,
|
import {
|
||||||
} = require("../../../automations/utils")
|
|
||||||
const { app: appCache } = require("@budibase/backend-core/cache")
|
|
||||||
const {
|
|
||||||
getAppId,
|
getAppId,
|
||||||
getAppDB,
|
getAppDB,
|
||||||
getProdAppDB,
|
getProdAppDB,
|
||||||
} = require("@budibase/backend-core/context")
|
} from "@budibase/backend-core/context"
|
||||||
|
import { quotas } from "@budibase/pro"
|
||||||
|
|
||||||
// the max time we can wait for an invalidation to complete before considering it failed
|
// the max time we can wait for an invalidation to complete before considering it failed
|
||||||
const MAX_PENDING_TIME_MS = 30 * 60000
|
const MAX_PENDING_TIME_MS = 30 * 60000
|
||||||
|
@ -25,9 +23,10 @@ const DeploymentStatus = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks that deployments are in a good state, any pending will be updated
|
// checks that deployments are in a good state, any pending will be updated
|
||||||
async function checkAllDeployments(deployments) {
|
async function checkAllDeployments(deployments: any) {
|
||||||
let updated = false
|
let updated = false
|
||||||
for (let deployment of Object.values(deployments.history)) {
|
let deployment: any
|
||||||
|
for (deployment of Object.values(deployments.history)) {
|
||||||
// check that no deployments have crashed etc and are now stuck
|
// check that no deployments have crashed etc and are now stuck
|
||||||
if (
|
if (
|
||||||
deployment.status === DeploymentStatus.PENDING &&
|
deployment.status === DeploymentStatus.PENDING &&
|
||||||
|
@ -41,7 +40,7 @@ async function checkAllDeployments(deployments) {
|
||||||
return { updated, deployments }
|
return { updated, deployments }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeDeploymentHistory(deployment) {
|
async function storeDeploymentHistory(deployment: any) {
|
||||||
const deploymentJSON = deployment.getJSON()
|
const deploymentJSON = deployment.getJSON()
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
|
|
||||||
|
@ -70,7 +69,7 @@ async function storeDeploymentHistory(deployment) {
|
||||||
return deployment
|
return deployment
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initDeployedApp(prodAppId) {
|
async function initDeployedApp(prodAppId: any) {
|
||||||
const db = getProdAppDB()
|
const db = getProdAppDB()
|
||||||
console.log("Reading automation docs")
|
console.log("Reading automation docs")
|
||||||
const automations = (
|
const automations = (
|
||||||
|
@ -79,7 +78,7 @@ async function initDeployedApp(prodAppId) {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
).rows.map(row => row.doc)
|
).rows.map((row: any) => row.doc)
|
||||||
console.log("You have " + automations.length + " automations")
|
console.log("You have " + automations.length + " automations")
|
||||||
const promises = []
|
const promises = []
|
||||||
console.log("Disabling prod crons..")
|
console.log("Disabling prod crons..")
|
||||||
|
@ -93,16 +92,17 @@ async function initDeployedApp(prodAppId) {
|
||||||
console.log("Enabled cron triggers for deployed app..")
|
console.log("Enabled cron triggers for deployed app..")
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deployApp(deployment) {
|
async function deployApp(deployment: any) {
|
||||||
try {
|
try {
|
||||||
const appId = getAppId()
|
const appId = getAppId()
|
||||||
const devAppId = getDevelopmentAppID(appId)
|
const devAppId = getDevelopmentAppID(appId)
|
||||||
const productionAppId = getProdAppID(appId)
|
const productionAppId = getProdAppID(appId)
|
||||||
|
|
||||||
const replication = new Replication({
|
const config: any = {
|
||||||
source: devAppId,
|
source: devAppId,
|
||||||
target: productionAppId,
|
target: productionAppId,
|
||||||
})
|
}
|
||||||
|
const replication = new Replication(config)
|
||||||
|
|
||||||
console.log("Replication object created")
|
console.log("Replication object created")
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ async function deployApp(deployment) {
|
||||||
console.log("Deployed app initialised, setting deployment to successful")
|
console.log("Deployed app initialised, setting deployment to successful")
|
||||||
deployment.setStatus(DeploymentStatus.SUCCESS)
|
deployment.setStatus(DeploymentStatus.SUCCESS)
|
||||||
await storeDeploymentHistory(deployment)
|
await storeDeploymentHistory(deployment)
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
deployment.setStatus(DeploymentStatus.FAILURE, err.message)
|
deployment.setStatus(DeploymentStatus.FAILURE, err.message)
|
||||||
await storeDeploymentHistory(deployment)
|
await storeDeploymentHistory(deployment)
|
||||||
throw {
|
throw {
|
||||||
|
@ -129,14 +129,11 @@ async function deployApp(deployment) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchDeployments = async function (ctx) {
|
export async function fetchDeployments(ctx: any) {
|
||||||
try {
|
try {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
||||||
const { updated, deployments } = await checkAllDeployments(
|
const { updated, deployments } = await checkAllDeployments(deploymentDoc)
|
||||||
deploymentDoc,
|
|
||||||
ctx.user
|
|
||||||
)
|
|
||||||
if (updated) {
|
if (updated) {
|
||||||
await db.put(deployments)
|
await db.put(deployments)
|
||||||
}
|
}
|
||||||
|
@ -146,7 +143,7 @@ exports.fetchDeployments = async function (ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.deploymentProgress = async function (ctx) {
|
export async function deploymentProgress(ctx: any) {
|
||||||
try {
|
try {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
const deploymentDoc = await db.get(DocumentTypes.DEPLOYMENTS)
|
||||||
|
@ -159,7 +156,20 @@ exports.deploymentProgress = async function (ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.deployApp = async function (ctx) {
|
const isFirstDeploy = async () => {
|
||||||
|
try {
|
||||||
|
const db = getProdAppDB()
|
||||||
|
await db.get(DocumentTypes.APP_METADATA)
|
||||||
|
} catch (e: any) {
|
||||||
|
if (e.status === 404) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const _deployApp = async function (ctx: any) {
|
||||||
let deployment = new Deployment()
|
let deployment = new Deployment()
|
||||||
console.log("Deployment object created")
|
console.log("Deployment object created")
|
||||||
deployment.setStatus(DeploymentStatus.PENDING)
|
deployment.setStatus(DeploymentStatus.PENDING)
|
||||||
|
@ -168,7 +178,14 @@ exports.deployApp = async function (ctx) {
|
||||||
console.log("Stored deployment history")
|
console.log("Stored deployment history")
|
||||||
|
|
||||||
console.log("Deploying app...")
|
console.log("Deploying app...")
|
||||||
await deployApp(deployment)
|
|
||||||
|
if (await isFirstDeploy()) {
|
||||||
|
await quotas.addPublishedApp(() => deployApp(deployment))
|
||||||
|
} else {
|
||||||
|
await deployApp(deployment)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.body = deployment
|
ctx.body = deployment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export { _deployApp as deployApp }
|
|
@ -1,22 +1,19 @@
|
||||||
const {
|
import { generateQueryID, getQueryParams, isProdAppID } from "../../../db/utils"
|
||||||
generateQueryID,
|
import { BaseQueryVerbs } from "../../../constants"
|
||||||
getQueryParams,
|
import { Thread, ThreadType } from "../../../threads"
|
||||||
isProdAppID,
|
import { save as saveDatasource } from "../datasource"
|
||||||
} = require("../../../db/utils")
|
import { RestImporter } from "./import"
|
||||||
const { BaseQueryVerbs } = require("../../../constants")
|
import { invalidateDynamicVariables } from "../../../threads/utils"
|
||||||
const { Thread, ThreadType } = require("../../../threads")
|
import { QUERY_THREAD_TIMEOUT } from "../../../environment"
|
||||||
const { save: saveDatasource } = require("../datasource")
|
import { getAppDB } from "@budibase/backend-core/context"
|
||||||
const { RestImporter } = require("./import")
|
import { quotas } from "@budibase/pro"
|
||||||
const { invalidateDynamicVariables } = require("../../../threads/utils")
|
|
||||||
const environment = require("../../../environment")
|
|
||||||
const { getAppDB } = require("@budibase/backend-core/context")
|
|
||||||
|
|
||||||
const Runner = new Thread(ThreadType.QUERY, {
|
const Runner = new Thread(ThreadType.QUERY, {
|
||||||
timeoutMs: environment.QUERY_THREAD_TIMEOUT || 10000,
|
timeoutMs: QUERY_THREAD_TIMEOUT || 10000,
|
||||||
})
|
})
|
||||||
|
|
||||||
// simple function to append "readable" to all read queries
|
// simple function to append "readable" to all read queries
|
||||||
function enrichQueries(input) {
|
function enrichQueries(input: any) {
|
||||||
const wasArray = Array.isArray(input)
|
const wasArray = Array.isArray(input)
|
||||||
const queries = wasArray ? input : [input]
|
const queries = wasArray ? input : [input]
|
||||||
for (let query of queries) {
|
for (let query of queries) {
|
||||||
|
@ -27,7 +24,7 @@ function enrichQueries(input) {
|
||||||
return wasArray ? queries : queries[0]
|
return wasArray ? queries : queries[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async function (ctx) {
|
export async function fetch(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
|
|
||||||
const body = await db.allDocs(
|
const body = await db.allDocs(
|
||||||
|
@ -36,10 +33,10 @@ exports.fetch = async function (ctx) {
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
ctx.body = enrichQueries(body.rows.map(row => row.doc))
|
ctx.body = enrichQueries(body.rows.map((row: any) => row.doc))
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.import = async ctx => {
|
const _import = async (ctx: any) => {
|
||||||
const body = ctx.request.body
|
const body = ctx.request.body
|
||||||
const data = body.data
|
const data = body.data
|
||||||
|
|
||||||
|
@ -49,7 +46,7 @@ exports.import = async ctx => {
|
||||||
let datasourceId
|
let datasourceId
|
||||||
if (!body.datasourceId) {
|
if (!body.datasourceId) {
|
||||||
// construct new datasource
|
// construct new datasource
|
||||||
const info = await importer.getInfo()
|
const info: any = await importer.getInfo()
|
||||||
let datasource = {
|
let datasource = {
|
||||||
type: "datasource",
|
type: "datasource",
|
||||||
source: "REST",
|
source: "REST",
|
||||||
|
@ -77,8 +74,9 @@ exports.import = async ctx => {
|
||||||
}
|
}
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
}
|
}
|
||||||
|
export { _import as import }
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
export async function save(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const query = ctx.request.body
|
const query = ctx.request.body
|
||||||
|
|
||||||
|
@ -93,7 +91,7 @@ exports.save = async function (ctx) {
|
||||||
ctx.message = `Query ${query.name} saved successfully.`
|
ctx.message = `Query ${query.name} saved successfully.`
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async function (ctx) {
|
export async function find(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const query = enrichQueries(await db.get(ctx.params.queryId))
|
const query = enrichQueries(await db.get(ctx.params.queryId))
|
||||||
// remove properties that could be dangerous in real app
|
// remove properties that could be dangerous in real app
|
||||||
|
@ -104,7 +102,7 @@ exports.find = async function (ctx) {
|
||||||
ctx.body = query
|
ctx.body = query
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.preview = async function (ctx) {
|
export async function preview(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
|
|
||||||
const datasource = await db.get(ctx.request.body.datasourceId)
|
const datasource = await db.get(ctx.request.body.datasourceId)
|
||||||
|
@ -114,16 +112,18 @@ exports.preview = async function (ctx) {
|
||||||
ctx.request.body
|
ctx.request.body
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { rows, keys, info, extra } = await Runner.run({
|
const runFn = () =>
|
||||||
appId: ctx.appId,
|
Runner.run({
|
||||||
datasource,
|
appId: ctx.appId,
|
||||||
queryVerb,
|
datasource,
|
||||||
fields,
|
queryVerb,
|
||||||
parameters,
|
fields,
|
||||||
transformer,
|
parameters,
|
||||||
queryId,
|
transformer,
|
||||||
})
|
queryId,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { rows, keys, info, extra } = await quotas.addQuery(runFn)
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
rows,
|
rows,
|
||||||
schemaFields: [...new Set(keys)],
|
schemaFields: [...new Set(keys)],
|
||||||
|
@ -135,7 +135,7 @@ exports.preview = async function (ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function execute(ctx, opts = { rowsOnly: false }) {
|
async function execute(ctx: any, opts = { rowsOnly: false }) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
|
|
||||||
const query = await db.get(ctx.params.queryId)
|
const query = await db.get(ctx.params.queryId)
|
||||||
|
@ -153,16 +153,19 @@ async function execute(ctx, opts = { rowsOnly: false }) {
|
||||||
|
|
||||||
// call the relevant CRUD method on the integration class
|
// call the relevant CRUD method on the integration class
|
||||||
try {
|
try {
|
||||||
const { rows, pagination, extra } = await Runner.run({
|
const runFn = () =>
|
||||||
appId: ctx.appId,
|
Runner.run({
|
||||||
datasource,
|
appId: ctx.appId,
|
||||||
queryVerb: query.queryVerb,
|
datasource,
|
||||||
fields: query.fields,
|
queryVerb: query.queryVerb,
|
||||||
pagination: ctx.request.body.pagination,
|
fields: query.fields,
|
||||||
parameters: enrichedParameters,
|
pagination: ctx.request.body.pagination,
|
||||||
transformer: query.transformer,
|
parameters: enrichedParameters,
|
||||||
queryId: ctx.params.queryId,
|
transformer: query.transformer,
|
||||||
})
|
queryId: ctx.params.queryId,
|
||||||
|
})
|
||||||
|
|
||||||
|
const { rows, pagination, extra } = await quotas.addQuery(runFn)
|
||||||
if (opts && opts.rowsOnly) {
|
if (opts && opts.rowsOnly) {
|
||||||
ctx.body = rows
|
ctx.body = rows
|
||||||
} else {
|
} else {
|
||||||
|
@ -173,15 +176,15 @@ async function execute(ctx, opts = { rowsOnly: false }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.executeV1 = async function (ctx) {
|
export async function executeV1(ctx: any) {
|
||||||
return execute(ctx, { rowsOnly: true })
|
return execute(ctx, { rowsOnly: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.executeV2 = async function (ctx) {
|
export async function executeV2(ctx: any) {
|
||||||
return execute(ctx, { rowsOnly: false })
|
return execute(ctx, { rowsOnly: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeDynamicVariables = async queryId => {
|
const removeDynamicVariables = async (queryId: any) => {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
const query = await db.get(queryId)
|
const query = await db.get(queryId)
|
||||||
const datasource = await db.get(query.datasourceId)
|
const datasource = await db.get(query.datasourceId)
|
||||||
|
@ -190,19 +193,19 @@ const removeDynamicVariables = async queryId => {
|
||||||
if (dynamicVariables) {
|
if (dynamicVariables) {
|
||||||
// delete dynamic variables from the datasource
|
// delete dynamic variables from the datasource
|
||||||
datasource.config.dynamicVariables = dynamicVariables.filter(
|
datasource.config.dynamicVariables = dynamicVariables.filter(
|
||||||
dv => dv.queryId !== queryId
|
(dv: any) => dv.queryId !== queryId
|
||||||
)
|
)
|
||||||
await db.put(datasource)
|
await db.put(datasource)
|
||||||
|
|
||||||
// invalidate the deleted variables
|
// invalidate the deleted variables
|
||||||
const variablesToDelete = dynamicVariables.filter(
|
const variablesToDelete = dynamicVariables.filter(
|
||||||
dv => dv.queryId === queryId
|
(dv: any) => dv.queryId === queryId
|
||||||
)
|
)
|
||||||
await invalidateDynamicVariables(variablesToDelete)
|
await invalidateDynamicVariables(variablesToDelete)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function (ctx) {
|
export async function destroy(ctx: any) {
|
||||||
const db = getAppDB()
|
const db = getAppDB()
|
||||||
await removeDynamicVariables(ctx.params.queryId)
|
await removeDynamicVariables(ctx.params.queryId)
|
||||||
await db.remove(ctx.params.queryId, ctx.params.revId)
|
await db.remove(ctx.params.queryId, ctx.params.revId)
|
|
@ -1,15 +1,16 @@
|
||||||
const internal = require("./internal")
|
import { quotas } from "@budibase/pro"
|
||||||
const external = require("./external")
|
import internal from "./internal"
|
||||||
const { isExternalTable } = require("../../../integrations/utils")
|
import external from "./external"
|
||||||
|
import { isExternalTable } from "../../../integrations/utils"
|
||||||
|
|
||||||
function pickApi(tableId) {
|
function pickApi(tableId: any) {
|
||||||
if (isExternalTable(tableId)) {
|
if (isExternalTable(tableId)) {
|
||||||
return external
|
return external
|
||||||
}
|
}
|
||||||
return internal
|
return internal
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTableId(ctx) {
|
function getTableId(ctx: any) {
|
||||||
if (ctx.request.body && ctx.request.body.tableId) {
|
if (ctx.request.body && ctx.request.body.tableId) {
|
||||||
return ctx.request.body.tableId
|
return ctx.request.body.tableId
|
||||||
}
|
}
|
||||||
|
@ -21,13 +22,13 @@ function getTableId(ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.patch = async ctx => {
|
export async function patch(ctx: any): Promise<any> {
|
||||||
const appId = ctx.appId
|
const appId = ctx.appId
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
const body = ctx.request.body
|
const body = ctx.request.body
|
||||||
// if it doesn't have an _id then its save
|
// if it doesn't have an _id then its save
|
||||||
if (body && !body._id) {
|
if (body && !body._id) {
|
||||||
return exports.save(ctx)
|
return save(ctx)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { row, table } = await pickApi(tableId).patch(ctx)
|
const { row, table } = await pickApi(tableId).patch(ctx)
|
||||||
|
@ -41,13 +42,13 @@ exports.patch = async ctx => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async function (ctx) {
|
const saveRow = async (ctx: any) => {
|
||||||
const appId = ctx.appId
|
const appId = ctx.appId
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
const body = ctx.request.body
|
const body = ctx.request.body
|
||||||
// if it has an ID already then its a patch
|
// if it has an ID already then its a patch
|
||||||
if (body && body._id) {
|
if (body && body._id) {
|
||||||
return exports.patch(ctx)
|
return patch(ctx)
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const { row, table } = await pickApi(tableId).save(ctx)
|
const { row, table } = await pickApi(tableId).save(ctx)
|
||||||
|
@ -60,7 +61,11 @@ exports.save = async function (ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchView = async function (ctx) {
|
export async function save(ctx: any) {
|
||||||
|
await quotas.addRow(() => saveRow(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchView(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await pickApi(tableId).fetchView(ctx)
|
ctx.body = await pickApi(tableId).fetchView(ctx)
|
||||||
|
@ -69,7 +74,7 @@ exports.fetchView = async function (ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = async function (ctx) {
|
export async function fetch(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await pickApi(tableId).fetch(ctx)
|
ctx.body = await pickApi(tableId).fetch(ctx)
|
||||||
|
@ -78,7 +83,7 @@ exports.fetch = async function (ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.find = async function (ctx) {
|
export async function find(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await pickApi(tableId).find(ctx)
|
ctx.body = await pickApi(tableId).find(ctx)
|
||||||
|
@ -87,19 +92,21 @@ exports.find = async function (ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async function (ctx) {
|
export async function destroy(ctx: any) {
|
||||||
const appId = ctx.appId
|
const appId = ctx.appId
|
||||||
const inputs = ctx.request.body
|
const inputs = ctx.request.body
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
let response, row
|
let response, row
|
||||||
if (inputs.rows) {
|
if (inputs.rows) {
|
||||||
let { rows } = await pickApi(tableId).bulkDestroy(ctx)
|
let { rows } = await pickApi(tableId).bulkDestroy(ctx)
|
||||||
|
await quotas.removeRows(rows.length)
|
||||||
response = rows
|
response = rows
|
||||||
for (let row of rows) {
|
for (let row of rows) {
|
||||||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
|
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let resp = await pickApi(tableId).destroy(ctx)
|
let resp = await pickApi(tableId).destroy(ctx)
|
||||||
|
await quotas.removeRow()
|
||||||
response = resp.response
|
response = resp.response
|
||||||
row = resp.row
|
row = resp.row
|
||||||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
|
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, appId, row)
|
||||||
|
@ -110,7 +117,7 @@ exports.destroy = async function (ctx) {
|
||||||
ctx.body = response
|
ctx.body = response
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.search = async ctx => {
|
export async function search(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
|
@ -120,7 +127,7 @@ exports.search = async ctx => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.validate = async function (ctx) {
|
export async function validate(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await pickApi(tableId).validate(ctx)
|
ctx.body = await pickApi(tableId).validate(ctx)
|
||||||
|
@ -129,7 +136,7 @@ exports.validate = async function (ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetchEnrichedRow = async function (ctx) {
|
export async function fetchEnrichedRow(ctx: any) {
|
||||||
const tableId = getTableId(ctx)
|
const tableId = getTableId(ctx)
|
||||||
try {
|
try {
|
||||||
ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx)
|
ctx.body = await pickApi(tableId).fetchEnrichedRow(ctx)
|
|
@ -120,11 +120,7 @@ export async function destroy(ctx: any) {
|
||||||
await db.bulkDocs(
|
await db.bulkDocs(
|
||||||
rows.rows.map((row: any) => ({ ...row.doc, _deleted: true }))
|
rows.rows.map((row: any) => ({ ...row.doc, _deleted: true }))
|
||||||
)
|
)
|
||||||
await quotas.updateUsage(
|
await quotas.removeRows(rows.rows.length)
|
||||||
-rows.rows.length,
|
|
||||||
StaticQuotaName.ROWS,
|
|
||||||
QuotaUsageType.STATIC
|
|
||||||
)
|
|
||||||
|
|
||||||
// update linked rows
|
// update linked rows
|
||||||
await updateLinks({
|
await updateLinks({
|
||||||
|
|
|
@ -147,12 +147,7 @@ export async function handleDataImport(user: any, table: any, dataImport: any) {
|
||||||
finalData.push(row)
|
finalData.push(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
await quotas.tryUpdateUsage(
|
await quotas.addRows(finalData.length, () => db.bulkDocs(finalData))
|
||||||
() => db.bulkDocs(finalData),
|
|
||||||
finalData.length,
|
|
||||||
StaticQuotaName.ROWS,
|
|
||||||
QuotaUsageType.STATIC
|
|
||||||
)
|
|
||||||
let response = await db.put(table)
|
let response = await db.put(table)
|
||||||
table._rev = response._rev
|
table._rev = response._rev
|
||||||
return table
|
return table
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
const Router = require("@koa/router")
|
const Router = require("@koa/router")
|
||||||
const controller = require("../controllers/application")
|
import * as controller from "../controllers/application"
|
||||||
const authorized = require("../../middleware/authorized")
|
import authorized from "../../middleware/authorized"
|
||||||
const { BUILDER } = require("@budibase/backend-core/permissions")
|
const { BUILDER } = require("@budibase/backend-core/permissions")
|
||||||
const usage = require("../../middleware/usageQuota")
|
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
router
|
router
|
||||||
.post("/api/applications/:appId/sync", authorized(BUILDER), controller.sync)
|
.post("/api/applications/:appId/sync", authorized(BUILDER), controller.sync)
|
||||||
.post("/api/applications", authorized(BUILDER), usage, controller.create)
|
.post("/api/applications", authorized(BUILDER), controller.create)
|
||||||
.get("/api/applications/:appId/definition", controller.fetchAppDefinition)
|
.get("/api/applications/:appId/definition", controller.fetchAppDefinition)
|
||||||
.get("/api/applications", controller.fetch)
|
.get("/api/applications", controller.fetch)
|
||||||
.get("/api/applications/:appId/appPackage", controller.fetchAppPackage)
|
.get("/api/applications/:appId/appPackage", controller.fetchAppPackage)
|
||||||
|
@ -23,11 +22,6 @@ router
|
||||||
authorized(BUILDER),
|
authorized(BUILDER),
|
||||||
controller.revertClient
|
controller.revertClient
|
||||||
)
|
)
|
||||||
.delete(
|
.delete("/api/applications/:appId", authorized(BUILDER), controller.destroy)
|
||||||
"/api/applications/:appId",
|
|
||||||
authorized(BUILDER),
|
|
||||||
usage,
|
|
||||||
controller.delete
|
|
||||||
)
|
|
||||||
|
|
||||||
module.exports = router
|
export default router
|
|
@ -1,32 +1,33 @@
|
||||||
const authRoutes = require("./auth")
|
import authRoutes from "./auth"
|
||||||
const layoutRoutes = require("./layout")
|
import layoutRoutes from "./layout"
|
||||||
const screenRoutes = require("./screen")
|
import screenRoutes from "./screen"
|
||||||
const userRoutes = require("./user")
|
import userRoutes from "./user"
|
||||||
const applicationRoutes = require("./application")
|
import applicationRoutes from "./application"
|
||||||
const tableRoutes = require("./table")
|
import tableRoutes from "./table"
|
||||||
const rowRoutes = require("./row")
|
import rowRoutes from "./row"
|
||||||
const viewRoutes = require("./view")
|
import viewRoutes from "./view"
|
||||||
const staticRoutes = require("./static")
|
import componentRoutes from "./component"
|
||||||
const componentRoutes = require("./component")
|
import automationRoutes from "./automation"
|
||||||
const automationRoutes = require("./automation")
|
import webhookRoutes from "./webhook"
|
||||||
const webhookRoutes = require("./webhook")
|
import roleRoutes from "./role"
|
||||||
const roleRoutes = require("./role")
|
import deployRoutes from "./deploy"
|
||||||
const deployRoutes = require("./deploy")
|
import apiKeysRoutes from "./apikeys"
|
||||||
const apiKeysRoutes = require("./apikeys")
|
import templatesRoutes from "./templates"
|
||||||
const templatesRoutes = require("./templates")
|
import analyticsRoutes from "./analytics"
|
||||||
const analyticsRoutes = require("./analytics")
|
import routingRoutes from "./routing"
|
||||||
const routingRoutes = require("./routing")
|
import integrationRoutes from "./integration"
|
||||||
const integrationRoutes = require("./integration")
|
import permissionRoutes from "./permission"
|
||||||
const permissionRoutes = require("./permission")
|
import datasourceRoutes from "./datasource"
|
||||||
const datasourceRoutes = require("./datasource")
|
import queryRoutes from "./query"
|
||||||
const queryRoutes = require("./query")
|
import backupRoutes from "./backup"
|
||||||
const backupRoutes = require("./backup")
|
import metadataRoutes from "./metadata"
|
||||||
const metadataRoutes = require("./metadata")
|
import devRoutes from "./dev"
|
||||||
const devRoutes = require("./dev")
|
import cloudRoutes from "./cloud"
|
||||||
const cloudRoutes = require("./cloud")
|
import migrationRoutes from "./migrations"
|
||||||
const migrationRoutes = require("./migrations")
|
|
||||||
|
|
||||||
exports.mainRoutes = [
|
export { default as staticRoutes } from "./static"
|
||||||
|
|
||||||
|
export const mainRoutes = [
|
||||||
authRoutes,
|
authRoutes,
|
||||||
deployRoutes,
|
deployRoutes,
|
||||||
layoutRoutes,
|
layoutRoutes,
|
||||||
|
@ -56,5 +57,3 @@ exports.mainRoutes = [
|
||||||
rowRoutes,
|
rowRoutes,
|
||||||
migrationRoutes,
|
migrationRoutes,
|
||||||
]
|
]
|
||||||
|
|
||||||
exports.staticRoutes = staticRoutes
|
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
const Router = require("@koa/router")
|
const Router = require("@koa/router")
|
||||||
const rowController = require("../controllers/row")
|
import * as rowController from "../controllers/row"
|
||||||
const authorized = require("../../middleware/authorized")
|
import authorized from "../../middleware/authorized"
|
||||||
const usage = require("../../middleware/usageQuota")
|
import { paramResource, paramSubResource } from "../../middleware/resourceId"
|
||||||
const {
|
|
||||||
paramResource,
|
|
||||||
paramSubResource,
|
|
||||||
} = require("../../middleware/resourceId")
|
|
||||||
const {
|
const {
|
||||||
PermissionLevels,
|
PermissionLevels,
|
||||||
PermissionTypes,
|
PermissionTypes,
|
||||||
|
@ -180,7 +176,6 @@ router
|
||||||
"/api/:tableId/rows",
|
"/api/:tableId/rows",
|
||||||
paramResource("tableId"),
|
paramResource("tableId"),
|
||||||
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||||
usage,
|
|
||||||
rowController.save
|
rowController.save
|
||||||
)
|
)
|
||||||
/**
|
/**
|
||||||
|
@ -247,8 +242,7 @@ router
|
||||||
"/api/:tableId/rows",
|
"/api/:tableId/rows",
|
||||||
paramResource("tableId"),
|
paramResource("tableId"),
|
||||||
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
authorized(PermissionTypes.TABLE, PermissionLevels.WRITE),
|
||||||
usage,
|
|
||||||
rowController.destroy
|
rowController.destroy
|
||||||
)
|
)
|
||||||
|
|
||||||
module.exports = router
|
export default router
|
|
@ -1,14 +1,14 @@
|
||||||
const Router = require("@koa/router")
|
const Router = require("@koa/router")
|
||||||
const controller = require("../controllers/static")
|
import * as controller from "../controllers/static"
|
||||||
const { budibaseTempDir } = require("../../utilities/budibaseDir")
|
import { budibaseTempDir } from "../../utilities/budibaseDir"
|
||||||
const authorized = require("../../middleware/authorized")
|
import authorized from "../../middleware/authorized"
|
||||||
const {
|
import {
|
||||||
BUILDER,
|
BUILDER,
|
||||||
PermissionTypes,
|
PermissionTypes,
|
||||||
PermissionLevels,
|
PermissionLevels,
|
||||||
} = require("@budibase/backend-core/permissions")
|
} from "@budibase/backend-core/permissions"
|
||||||
const env = require("../../environment")
|
import * as env from "../../environment"
|
||||||
const { paramResource } = require("../../middleware/resourceId")
|
import { paramResource } from "../../middleware/resourceId"
|
||||||
|
|
||||||
const router = Router()
|
const router = Router()
|
||||||
|
|
||||||
|
@ -52,4 +52,4 @@ router
|
||||||
controller.getSignedUploadURL
|
controller.getSignedUploadURL
|
||||||
)
|
)
|
||||||
|
|
||||||
module.exports = router
|
export default router
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { save } from "../../api/controllers/row"
|
import { save } from "../../api/controllers/row"
|
||||||
import { cleanUpRow, getError } from "../automationUtils"
|
import { cleanUpRow, getError } from "../automationUtils"
|
||||||
import { buildCtx } from "./utils"
|
import { buildCtx } from "./utils"
|
||||||
|
@ -78,12 +78,7 @@ export async function run({ inputs, appId, emitter }: any) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
inputs.row = await cleanUpRow(inputs.row.tableId, inputs.row)
|
inputs.row = await cleanUpRow(inputs.row.tableId, inputs.row)
|
||||||
await quotas.tryUpdateUsage(
|
await quotas.addRow(() => save(ctx))
|
||||||
() => save(ctx),
|
|
||||||
1,
|
|
||||||
StaticQuotaName.ROWS,
|
|
||||||
QuotaUsageType.STATIC
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
row: inputs.row,
|
row: inputs.row,
|
||||||
response: ctx.body,
|
response: ctx.body,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { destroy } from "../../api/controllers/row"
|
import { destroy } from "../../api/controllers/row"
|
||||||
import { buildCtx } from "./utils"
|
import { buildCtx } from "./utils"
|
||||||
import { getError } from "../automationUtils"
|
import { getError } from "../automationUtils"
|
||||||
import { quotas, QuotaUsageType, StaticQuotaName } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
|
|
||||||
export const definition = {
|
export const definition = {
|
||||||
description: "Delete a row from your database",
|
description: "Delete a row from your database",
|
||||||
|
@ -73,8 +73,8 @@ export async function run({ inputs, appId, emitter }: any) {
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await quotas.updateUsage(-1, StaticQuotaName.ROWS, QuotaUsageType.STATIC)
|
|
||||||
await destroy(ctx)
|
await destroy(ctx)
|
||||||
|
await quotas.removeRow()
|
||||||
return {
|
return {
|
||||||
response: ctx.body,
|
response: ctx.body,
|
||||||
row: ctx.row,
|
row: ctx.row,
|
||||||
|
|
|
@ -1,23 +1,29 @@
|
||||||
const { Thread, ThreadType } = require("../threads")
|
import { Thread, ThreadType } from "../threads"
|
||||||
const { definitions } = require("./triggerInfo")
|
import { definitions } from "./triggerInfo"
|
||||||
const webhooks = require("../api/controllers/webhook")
|
import { destroy, Webhook, WebhookType, save } from "../api/controllers/webhook"
|
||||||
const CouchDB = require("../db")
|
import CouchDB from "../db"
|
||||||
const { queue } = require("./bullboard")
|
import { queue } from "./bullboard"
|
||||||
const newid = require("../db/newid")
|
import newid from "../db/newid"
|
||||||
const { updateEntityMetadata } = require("../utilities")
|
import { updateEntityMetadata } from "../utilities"
|
||||||
const { MetadataTypes } = require("../constants")
|
import { MetadataTypes } from "../constants"
|
||||||
const { getProdAppID } = require("@budibase/backend-core/db")
|
import { getProdAppID } from "@budibase/backend-core/db"
|
||||||
const { cloneDeep } = require("lodash/fp")
|
import { cloneDeep } from "lodash/fp"
|
||||||
const { getAppDB, getAppId } = require("@budibase/backend-core/context")
|
import { getAppDB, getAppId } from "@budibase/backend-core/context"
|
||||||
|
import { tenancy } from "@budibase/backend-core"
|
||||||
|
import { quotas } from "@budibase/pro"
|
||||||
|
|
||||||
const WH_STEP_ID = definitions.WEBHOOK.stepId
|
const WH_STEP_ID = definitions.WEBHOOK.stepId
|
||||||
const CRON_STEP_ID = definitions.CRON.stepId
|
const CRON_STEP_ID = definitions.CRON.stepId
|
||||||
const Runner = new Thread(ThreadType.AUTOMATION)
|
const Runner = new Thread(ThreadType.AUTOMATION)
|
||||||
|
|
||||||
exports.processEvent = async job => {
|
export async function processEvent(job: any) {
|
||||||
try {
|
try {
|
||||||
// need to actually await these so that an error can be captured properly
|
const tenantId = tenancy.getTenantIDFromAppID(job.data.event.appId)
|
||||||
return await Runner.run(job)
|
return await tenancy.doInTenant(tenantId, async () => {
|
||||||
|
// need to actually await these so that an error can be captured properly
|
||||||
|
const runFn = () => Runner.run(job)
|
||||||
|
return quotas.addAutomation(runFn)
|
||||||
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(
|
console.error(
|
||||||
`${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}`
|
`${job.data.automation.appId} automation ${job.data.automation._id} was unable to run - ${err}`
|
||||||
|
@ -26,11 +32,15 @@ exports.processEvent = async job => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.updateTestHistory = async (appId, automation, history) => {
|
export async function updateTestHistory(
|
||||||
|
appId: any,
|
||||||
|
automation: any,
|
||||||
|
history: any
|
||||||
|
) {
|
||||||
return updateEntityMetadata(
|
return updateEntityMetadata(
|
||||||
MetadataTypes.AUTOMATION_TEST_HISTORY,
|
MetadataTypes.AUTOMATION_TEST_HISTORY,
|
||||||
automation._id,
|
automation._id,
|
||||||
metadata => {
|
(metadata: any) => {
|
||||||
if (metadata && Array.isArray(metadata.history)) {
|
if (metadata && Array.isArray(metadata.history)) {
|
||||||
metadata.history.push(history)
|
metadata.history.push(history)
|
||||||
} else {
|
} else {
|
||||||
|
@ -43,7 +53,7 @@ exports.updateTestHistory = async (appId, automation, history) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.removeDeprecated = definitions => {
|
export function removeDeprecated(definitions: any) {
|
||||||
const base = cloneDeep(definitions)
|
const base = cloneDeep(definitions)
|
||||||
for (let key of Object.keys(base)) {
|
for (let key of Object.keys(base)) {
|
||||||
if (base[key].deprecated) {
|
if (base[key].deprecated) {
|
||||||
|
@ -54,13 +64,15 @@ exports.removeDeprecated = definitions => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// end the repetition and the job itself
|
// end the repetition and the job itself
|
||||||
exports.disableAllCrons = async appId => {
|
export async function disableAllCrons(appId: any) {
|
||||||
const promises = []
|
const promises = []
|
||||||
const jobs = await queue.getRepeatableJobs()
|
const jobs = await queue.getRepeatableJobs()
|
||||||
for (let job of jobs) {
|
for (let job of jobs) {
|
||||||
if (job.key.includes(`${appId}_cron`)) {
|
if (job.key.includes(`${appId}_cron`)) {
|
||||||
promises.push(queue.removeRepeatableByKey(job.key))
|
promises.push(queue.removeRepeatableByKey(job.key))
|
||||||
promises.push(queue.removeJobs(job.id))
|
if (job.id) {
|
||||||
|
promises.push(queue.removeJobs(job.id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.all(promises)
|
return Promise.all(promises)
|
||||||
|
@ -71,9 +83,9 @@ exports.disableAllCrons = async appId => {
|
||||||
* @param {string} appId The ID of the app in which we are checking for webhooks
|
* @param {string} appId The ID of the app in which we are checking for webhooks
|
||||||
* @param {object|undefined} automation The automation object to be updated.
|
* @param {object|undefined} automation The automation object to be updated.
|
||||||
*/
|
*/
|
||||||
exports.enableCronTrigger = async (appId, automation) => {
|
export async function enableCronTrigger(appId: any, automation: any) {
|
||||||
const trigger = automation ? automation.definition.trigger : null
|
const trigger = automation ? automation.definition.trigger : null
|
||||||
function isCronTrigger(auto) {
|
function isCronTrigger(auto: any) {
|
||||||
return (
|
return (
|
||||||
auto &&
|
auto &&
|
||||||
auto.definition.trigger &&
|
auto.definition.trigger &&
|
||||||
|
@ -84,7 +96,7 @@ exports.enableCronTrigger = async (appId, automation) => {
|
||||||
if (isCronTrigger(automation)) {
|
if (isCronTrigger(automation)) {
|
||||||
// make a job id rather than letting Bull decide, makes it easier to handle on way out
|
// make a job id rather than letting Bull decide, makes it easier to handle on way out
|
||||||
const jobId = `${appId}_cron_${newid()}`
|
const jobId = `${appId}_cron_${newid()}`
|
||||||
const job = await queue.add(
|
const job: any = await queue.add(
|
||||||
{
|
{
|
||||||
automation,
|
automation,
|
||||||
event: { appId, timestamp: Date.now() },
|
event: { appId, timestamp: Date.now() },
|
||||||
|
@ -112,13 +124,13 @@ exports.enableCronTrigger = async (appId, automation) => {
|
||||||
* @returns {Promise<object|undefined>} After this is complete the new automation object may have been updated and should be
|
* @returns {Promise<object|undefined>} After this is complete the new automation object may have been updated and should be
|
||||||
* written to DB (this does not write to DB as it would be wasteful to repeat).
|
* written to DB (this does not write to DB as it would be wasteful to repeat).
|
||||||
*/
|
*/
|
||||||
exports.checkForWebhooks = async ({ oldAuto, newAuto }) => {
|
export async function checkForWebhooks({ oldAuto, newAuto }: any) {
|
||||||
const appId = getAppId()
|
const appId = getAppId()
|
||||||
const oldTrigger = oldAuto ? oldAuto.definition.trigger : null
|
const oldTrigger = oldAuto ? oldAuto.definition.trigger : null
|
||||||
const newTrigger = newAuto ? newAuto.definition.trigger : null
|
const newTrigger = newAuto ? newAuto.definition.trigger : null
|
||||||
const triggerChanged =
|
const triggerChanged =
|
||||||
oldTrigger && newTrigger && oldTrigger.id !== newTrigger.id
|
oldTrigger && newTrigger && oldTrigger.id !== newTrigger.id
|
||||||
function isWebhookTrigger(auto) {
|
function isWebhookTrigger(auto: any) {
|
||||||
return (
|
return (
|
||||||
auto &&
|
auto &&
|
||||||
auto.definition.trigger &&
|
auto.definition.trigger &&
|
||||||
|
@ -144,7 +156,7 @@ exports.checkForWebhooks = async ({ oldAuto, newAuto }) => {
|
||||||
delete newTrigger.webhookId
|
delete newTrigger.webhookId
|
||||||
newTrigger.inputs = {}
|
newTrigger.inputs = {}
|
||||||
}
|
}
|
||||||
await webhooks.destroy(ctx)
|
await destroy(ctx)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// don't worry about not being able to delete, if it doesn't exist all good
|
// don't worry about not being able to delete, if it doesn't exist all good
|
||||||
}
|
}
|
||||||
|
@ -154,17 +166,17 @@ exports.checkForWebhooks = async ({ oldAuto, newAuto }) => {
|
||||||
(!isWebhookTrigger(oldAuto) || triggerChanged) &&
|
(!isWebhookTrigger(oldAuto) || triggerChanged) &&
|
||||||
isWebhookTrigger(newAuto)
|
isWebhookTrigger(newAuto)
|
||||||
) {
|
) {
|
||||||
const ctx = {
|
const ctx: any = {
|
||||||
appId,
|
appId,
|
||||||
request: {
|
request: {
|
||||||
body: new webhooks.Webhook(
|
body: new Webhook(
|
||||||
"Automation webhook",
|
"Automation webhook",
|
||||||
webhooks.WebhookType.AUTOMATION,
|
WebhookType.AUTOMATION,
|
||||||
newAuto._id
|
newAuto._id
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
await webhooks.save(ctx)
|
await save(ctx)
|
||||||
const id = ctx.body.webhook._id
|
const id = ctx.body.webhook._id
|
||||||
newTrigger.webhookId = id
|
newTrigger.webhookId = id
|
||||||
// the app ID has to be development for this endpoint
|
// the app ID has to be development for this endpoint
|
||||||
|
@ -184,6 +196,6 @@ exports.checkForWebhooks = async ({ oldAuto, newAuto }) => {
|
||||||
* @param appId {string} the app that is being removed.
|
* @param appId {string} the app that is being removed.
|
||||||
* @return {Promise<void>} clean is complete if this succeeds.
|
* @return {Promise<void>} clean is complete if this succeeds.
|
||||||
*/
|
*/
|
||||||
exports.cleanupAutomations = async appId => {
|
export async function cleanupAutomations(appId: any) {
|
||||||
await exports.disableAllCrons(appId)
|
await disableAllCrons(appId)
|
||||||
}
|
}
|
|
@ -1,172 +0,0 @@
|
||||||
import { quotas, StaticQuotaName, QuotaUsageType } from "@budibase/pro"
|
|
||||||
const { getUniqueRows } = require("../utilities/usageQuota/rows")
|
|
||||||
const {
|
|
||||||
isExternalTable,
|
|
||||||
isRowId: isExternalRowId,
|
|
||||||
} = require("../integrations/utils")
|
|
||||||
const { getAppDB } = require("@budibase/backend-core/context")
|
|
||||||
|
|
||||||
// currently only counting new writes and deletes
|
|
||||||
const METHOD_MAP: any = {
|
|
||||||
POST: 1,
|
|
||||||
DELETE: -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
const DOMAIN_MAP: any = {
|
|
||||||
rows: {
|
|
||||||
name: StaticQuotaName.ROWS,
|
|
||||||
type: QuotaUsageType.STATIC,
|
|
||||||
},
|
|
||||||
applications: {
|
|
||||||
name: StaticQuotaName.APPS,
|
|
||||||
type: QuotaUsageType.STATIC,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
function getQuotaInfo(url: string) {
|
|
||||||
for (let domain of Object.keys(DOMAIN_MAP)) {
|
|
||||||
if (url.indexOf(domain) !== -1) {
|
|
||||||
return DOMAIN_MAP[domain]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = async (ctx: any, next: any) => {
|
|
||||||
if (!quotas.useQuotas()) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
|
|
||||||
let usage = METHOD_MAP[ctx.req.method]
|
|
||||||
const quotaInfo = getQuotaInfo(ctx.req.url)
|
|
||||||
if (usage == null || quotaInfo == null) {
|
|
||||||
return next()
|
|
||||||
}
|
|
||||||
// post request could be a save of a pre-existing entry
|
|
||||||
if (ctx.request.body && ctx.request.body._id && ctx.request.body._rev) {
|
|
||||||
const usageId = ctx.request.body._id
|
|
||||||
try {
|
|
||||||
if (ctx.appId) {
|
|
||||||
const db = getAppDB()
|
|
||||||
await db.get(usageId)
|
|
||||||
}
|
|
||||||
return next()
|
|
||||||
} catch (err) {
|
|
||||||
if (
|
|
||||||
isExternalTable(usageId) ||
|
|
||||||
(ctx.request.body.tableId &&
|
|
||||||
isExternalTable(ctx.request.body.tableId)) ||
|
|
||||||
isExternalRowId(usageId)
|
|
||||||
) {
|
|
||||||
return next()
|
|
||||||
} else {
|
|
||||||
ctx.throw(404, `${usageId} does not exist`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await performRequest(ctx, next, quotaInfo, usage)
|
|
||||||
} catch (err) {
|
|
||||||
ctx.throw(400, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const performRequest = async (
|
|
||||||
ctx: any,
|
|
||||||
next: any,
|
|
||||||
quotaInfo: any,
|
|
||||||
usage: number
|
|
||||||
) => {
|
|
||||||
const usageContext = {
|
|
||||||
skipNext: false,
|
|
||||||
skipUsage: false,
|
|
||||||
[StaticQuotaName.APPS]: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
const quotaName = quotaInfo.name
|
|
||||||
|
|
||||||
if (usage === -1) {
|
|
||||||
if (PRE_DELETE[quotaName]) {
|
|
||||||
await PRE_DELETE[quotaName](ctx, usageContext)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (PRE_CREATE[quotaName]) {
|
|
||||||
await PRE_CREATE[quotaName](ctx, usageContext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run the request
|
|
||||||
if (!usageContext.skipNext) {
|
|
||||||
await quotas.updateUsage(usage, quotaName, quotaInfo.type, {
|
|
||||||
dryRun: true,
|
|
||||||
})
|
|
||||||
await next()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usage === -1) {
|
|
||||||
if (POST_DELETE[quotaName]) {
|
|
||||||
await POST_DELETE[quotaName](ctx, usageContext)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (POST_CREATE[quotaName]) {
|
|
||||||
await POST_CREATE[quotaName](ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update the usage
|
|
||||||
if (!usageContext.skipUsage) {
|
|
||||||
await quotas.updateUsage(usage, quotaName, quotaInfo.type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const appPreDelete = async (ctx: any, usageContext: any) => {
|
|
||||||
if (ctx.query.unpublish) {
|
|
||||||
// don't run usage decrement for unpublish
|
|
||||||
usageContext.skipUsage = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// store the row count to delete
|
|
||||||
const rows = await getUniqueRows([ctx.appId])
|
|
||||||
if (rows.length) {
|
|
||||||
usageContext[StaticQuotaName.APPS] = { rowCount: rows.length }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const appPostDelete = async (ctx: any, usageContext: any) => {
|
|
||||||
// delete the app rows from usage
|
|
||||||
const rowCount = usageContext[StaticQuotaName.APPS].rowCount
|
|
||||||
if (rowCount) {
|
|
||||||
await quotas.updateUsage(
|
|
||||||
-rowCount,
|
|
||||||
StaticQuotaName.ROWS,
|
|
||||||
QuotaUsageType.STATIC
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const appPostCreate = async (ctx: any) => {
|
|
||||||
// app import & template creation
|
|
||||||
if (ctx.request.body.useTemplate === "true") {
|
|
||||||
const rows = await getUniqueRows([ctx.response.body.appId])
|
|
||||||
const rowCount = rows ? rows.length : 0
|
|
||||||
await quotas.updateUsage(
|
|
||||||
rowCount,
|
|
||||||
StaticQuotaName.ROWS,
|
|
||||||
QuotaUsageType.STATIC
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const PRE_DELETE: any = {
|
|
||||||
[StaticQuotaName.APPS]: appPreDelete,
|
|
||||||
}
|
|
||||||
|
|
||||||
const POST_DELETE: any = {
|
|
||||||
[StaticQuotaName.APPS]: appPostDelete,
|
|
||||||
}
|
|
||||||
|
|
||||||
const PRE_CREATE: any = {}
|
|
||||||
|
|
||||||
const POST_CREATE: any = {
|
|
||||||
[StaticQuotaName.APPS]: appPostCreate,
|
|
||||||
}
|
|
|
@ -1,8 +1,3 @@
|
||||||
import { quotas } from "@budibase/pro"
|
|
||||||
|
|
||||||
export const runQuotaMigration = async (migration: Function) => {
|
export const runQuotaMigration = async (migration: Function) => {
|
||||||
if (!quotas.useQuotas()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
await migration()
|
await migration()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
declare module "@budibase/backend-core"
|
declare module "@budibase/backend-core"
|
||||||
declare module "@budibase/backend-core/tenancy"
|
declare module "@budibase/backend-core/tenancy"
|
||||||
declare module "@budibase/backend-core/db"
|
declare module "@budibase/backend-core/db"
|
||||||
|
declare module "@budibase/backend-core/context"
|
||||||
|
declare module "@budibase/backend-core/cache"
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
const workerFarm = require("worker-farm")
|
import workerFarm from "worker-farm"
|
||||||
const env = require("../environment")
|
import * as env from "../environment"
|
||||||
|
|
||||||
const ThreadType = {
|
export const ThreadType = {
|
||||||
QUERY: "query",
|
QUERY: "query",
|
||||||
AUTOMATION: "automation",
|
AUTOMATION: "automation",
|
||||||
}
|
}
|
||||||
|
|
||||||
function typeToFile(type) {
|
function typeToFile(type: any) {
|
||||||
let filename = null
|
let filename = null
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case ThreadType.QUERY:
|
case ThreadType.QUERY:
|
||||||
|
@ -21,8 +21,13 @@ function typeToFile(type) {
|
||||||
return require.resolve(filename)
|
return require.resolve(filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Thread {
|
export class Thread {
|
||||||
constructor(type, opts = { timeoutMs: null, count: 1 }) {
|
type: any
|
||||||
|
count: any
|
||||||
|
disableThreading: any
|
||||||
|
workers: any
|
||||||
|
|
||||||
|
constructor(type: any, opts: any = { timeoutMs: null, count: 1 }) {
|
||||||
this.type = type
|
this.type = type
|
||||||
this.count = opts.count ? opts.count : 1
|
this.count = opts.count ? opts.count : 1
|
||||||
this.disableThreading =
|
this.disableThreading =
|
||||||
|
@ -31,7 +36,7 @@ class Thread {
|
||||||
this.count === 0 ||
|
this.count === 0 ||
|
||||||
env.isInThread()
|
env.isInThread()
|
||||||
if (!this.disableThreading) {
|
if (!this.disableThreading) {
|
||||||
const workerOpts = {
|
const workerOpts: any = {
|
||||||
autoStart: true,
|
autoStart: true,
|
||||||
maxConcurrentWorkers: this.count,
|
maxConcurrentWorkers: this.count,
|
||||||
}
|
}
|
||||||
|
@ -42,7 +47,7 @@ class Thread {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
run(data) {
|
run(data: any) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let fncToCall
|
let fncToCall
|
||||||
// if in test then don't use threading
|
// if in test then don't use threading
|
||||||
|
@ -51,7 +56,7 @@ class Thread {
|
||||||
} else {
|
} else {
|
||||||
fncToCall = this.workers
|
fncToCall = this.workers
|
||||||
}
|
}
|
||||||
fncToCall(data, (err, response) => {
|
fncToCall(data, (err: any, response: any) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -61,6 +66,3 @@ class Thread {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.Thread = Thread
|
|
||||||
module.exports.ThreadType = ThreadType
|
|
|
@ -66,7 +66,8 @@ class InMemoryQueue {
|
||||||
* @param {object} msg A message to be transported over the queue, this should be
|
* @param {object} msg A message to be transported over the queue, this should be
|
||||||
* a JSON message as this is required by Bull.
|
* a JSON message as this is required by Bull.
|
||||||
*/
|
*/
|
||||||
add(msg) {
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
add(msg, repeat) {
|
||||||
if (typeof msg !== "object") {
|
if (typeof msg !== "object") {
|
||||||
throw "Queue only supports carrying JSON."
|
throw "Queue only supports carrying JSON."
|
||||||
}
|
}
|
||||||
|
@ -90,6 +91,11 @@ class InMemoryQueue {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
removeJobs(pattern) {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implemented for tests
|
* Implemented for tests
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -224,7 +224,7 @@ async function oidcStrategyFactory(ctx: any, configId: any) {
|
||||||
const chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
|
const chosenConfig = config.configs.filter((c: any) => c.uuid === configId)[0]
|
||||||
let callbackUrl = await exports.oidcCallbackUrl(chosenConfig)
|
let callbackUrl = await exports.oidcCallbackUrl(chosenConfig)
|
||||||
|
|
||||||
return oidc.strategyFactory(chosenConfig, callbackUrl)
|
return oidc.strategyFactory(chosenConfig, callbackUrl, users.save)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -37,7 +37,7 @@ const allUsers = async () => {
|
||||||
|
|
||||||
export const save = async (ctx: any) => {
|
export const save = async (ctx: any) => {
|
||||||
try {
|
try {
|
||||||
const user = await users.save(ctx.request.body, getTenantId())
|
const user: any = await users.save(ctx.request.body, getTenantId())
|
||||||
// let server know to sync user
|
// let server know to sync user
|
||||||
await syncUserInApps(user._id)
|
await syncUserInApps(user._id)
|
||||||
ctx.body = user
|
ctx.body = user
|
||||||
|
@ -129,6 +129,7 @@ export const destroy = async (ctx: any) => {
|
||||||
|
|
||||||
await removeUserFromInfoDB(dbUser)
|
await removeUserFromInfoDB(dbUser)
|
||||||
await db.remove(dbUser._id, dbUser._rev)
|
await db.remove(dbUser._id, dbUser._rev)
|
||||||
|
await quotas.removeUser(dbUser)
|
||||||
await userCache.invalidateUser(dbUser._id)
|
await userCache.invalidateUser(dbUser._id)
|
||||||
await invalidateSessions(dbUser._id)
|
await invalidateSessions(dbUser._id)
|
||||||
// let server know to sync user
|
// let server know to sync user
|
||||||
|
|
Loading…
Reference in New Issue