Merge pull request #8416 from Budibase/fix/mike-oct-fixes
Various fixes for release
This commit is contained in:
commit
b0f3cc7af0
|
@ -171,11 +171,13 @@ http {
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
|
||||||
proxy_connect_timeout 300;
|
proxy_connect_timeout 300;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Connection "";
|
proxy_set_header Connection "";
|
||||||
chunked_transfer_encoding off;
|
chunked_transfer_encoding off;
|
||||||
|
|
||||||
proxy_pass http://$minio:9000;
|
proxy_pass http://$minio:9000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { baseGlobalDBName } from "../db/tenancy"
|
||||||
import { IdentityContext } from "@budibase/types"
|
import { IdentityContext } from "@budibase/types"
|
||||||
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
|
import { DEFAULT_TENANT_ID as _DEFAULT_TENANT_ID } from "../constants"
|
||||||
import { ContextKey } from "./constants"
|
import { ContextKey } from "./constants"
|
||||||
|
import PouchDB from "pouchdb"
|
||||||
import {
|
import {
|
||||||
updateUsing,
|
updateUsing,
|
||||||
closeWithUsing,
|
closeWithUsing,
|
||||||
|
@ -22,16 +23,15 @@ export const DEFAULT_TENANT_ID = _DEFAULT_TENANT_ID
|
||||||
let TEST_APP_ID: string | null = null
|
let TEST_APP_ID: string | null = null
|
||||||
|
|
||||||
export const closeTenancy = async () => {
|
export const closeTenancy = async () => {
|
||||||
let db
|
|
||||||
try {
|
try {
|
||||||
if (env.USE_COUCH) {
|
if (env.USE_COUCH) {
|
||||||
db = getGlobalDB()
|
const db = getGlobalDB()
|
||||||
|
await closeDB(db)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// no DB found - skip closing
|
// no DB found - skip closing
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await closeDB(db)
|
|
||||||
// clear from context now that database is closed/task is finished
|
// clear from context now that database is closed/task is finished
|
||||||
cls.setOnContext(ContextKey.TENANT_ID, null)
|
cls.setOnContext(ContextKey.TENANT_ID, null)
|
||||||
cls.setOnContext(ContextKey.GLOBAL_DB, null)
|
cls.setOnContext(ContextKey.GLOBAL_DB, null)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import * as events from "./events"
|
||||||
import * as migrations from "./migrations"
|
import * as migrations from "./migrations"
|
||||||
import * as users from "./users"
|
import * as users from "./users"
|
||||||
import * as roles from "./security/roles"
|
import * as roles from "./security/roles"
|
||||||
|
import * as permissions from "./security/permissions"
|
||||||
import * as accounts from "./cloud/accounts"
|
import * as accounts from "./cloud/accounts"
|
||||||
import * as installation from "./installation"
|
import * as installation from "./installation"
|
||||||
import env from "./environment"
|
import env from "./environment"
|
||||||
|
@ -65,6 +66,7 @@ const core = {
|
||||||
middleware,
|
middleware,
|
||||||
encryption,
|
encryption,
|
||||||
queue,
|
queue,
|
||||||
|
permissions,
|
||||||
}
|
}
|
||||||
|
|
||||||
export = core
|
export = core
|
||||||
|
|
|
@ -4,7 +4,7 @@ const fs = require("fs")
|
||||||
const { join } = require("path")
|
const { join } = require("path")
|
||||||
const { getAllDbs } = require("../core/db")
|
const { getAllDbs } = require("../core/db")
|
||||||
const tar = require("tar")
|
const tar = require("tar")
|
||||||
const { progressBar } = require("../utils")
|
const { progressBar, httpCall } = require("../utils")
|
||||||
const {
|
const {
|
||||||
TEMP_DIR,
|
TEMP_DIR,
|
||||||
COUCH_DIR,
|
COUCH_DIR,
|
||||||
|
@ -86,6 +86,15 @@ async function importBackup(opts) {
|
||||||
bar.stop()
|
bar.stop()
|
||||||
console.log("MinIO Import")
|
console.log("MinIO Import")
|
||||||
await importObjects()
|
await importObjects()
|
||||||
|
// finish by letting the system know that a restore has occurred
|
||||||
|
try {
|
||||||
|
await httpCall(
|
||||||
|
`http://localhost:${config.MAIN_PORT}/api/system/restored`,
|
||||||
|
"POST"
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
// ignore error - it will be an older system
|
||||||
|
}
|
||||||
console.log("Import complete")
|
console.log("Import complete")
|
||||||
fs.rmSync(TEMP_DIR, { recursive: true })
|
fs.rmSync(TEMP_DIR, { recursive: true })
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,16 +16,21 @@ exports.exportObjects = async () => {
|
||||||
const path = join(TEMP_DIR, MINIO_DIR)
|
const path = join(TEMP_DIR, MINIO_DIR)
|
||||||
fs.mkdirSync(path)
|
fs.mkdirSync(path)
|
||||||
let fullList = []
|
let fullList = []
|
||||||
|
let errorCount = 0
|
||||||
for (let bucket of bucketList) {
|
for (let bucket of bucketList) {
|
||||||
const client = ObjectStore(bucket)
|
const client = ObjectStore(bucket)
|
||||||
try {
|
try {
|
||||||
await client.headBucket().promise()
|
await client.headBucket().promise()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
errorCount++
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const list = await client.listObjectsV2().promise()
|
const list = await client.listObjectsV2().promise()
|
||||||
fullList = fullList.concat(list.Contents.map(el => ({ ...el, bucket })))
|
fullList = fullList.concat(list.Contents.map(el => ({ ...el, bucket })))
|
||||||
}
|
}
|
||||||
|
if (errorCount === bucketList.length) {
|
||||||
|
throw new Error("Unable to access MinIO/S3 - check environment config.")
|
||||||
|
}
|
||||||
const bar = progressBar(fullList.length)
|
const bar = progressBar(fullList.length)
|
||||||
let count = 0
|
let count = 0
|
||||||
for (let object of fullList) {
|
for (let object of fullList) {
|
||||||
|
|
|
@ -2,17 +2,19 @@ const dotenv = require("dotenv")
|
||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
const { string } = require("../questions")
|
const { string } = require("../questions")
|
||||||
const { getPouch } = require("../core/db")
|
const { getPouch } = require("../core/db")
|
||||||
|
const { env: environment } = require("@budibase/backend-core")
|
||||||
|
|
||||||
exports.DEFAULT_COUCH = "http://budibase:budibase@localhost:10000/db/"
|
|
||||||
exports.DEFAULT_MINIO = "http://localhost:10000/"
|
|
||||||
exports.TEMP_DIR = ".temp"
|
exports.TEMP_DIR = ".temp"
|
||||||
exports.COUCH_DIR = "couchdb"
|
exports.COUCH_DIR = "couchdb"
|
||||||
exports.MINIO_DIR = "minio"
|
exports.MINIO_DIR = "minio"
|
||||||
|
|
||||||
const REQUIRED = [
|
const REQUIRED = [
|
||||||
{ value: "MAIN_PORT", default: "10000" },
|
{ value: "MAIN_PORT", default: "10000" },
|
||||||
{ value: "COUCH_DB_URL", default: exports.DEFAULT_COUCH },
|
{
|
||||||
{ value: "MINIO_URL", default: exports.DEFAULT_MINIO },
|
value: "COUCH_DB_URL",
|
||||||
|
default: "http://budibase:budibase@localhost:10000/db/",
|
||||||
|
},
|
||||||
|
{ value: "MINIO_URL", default: "http://localhost:10000" },
|
||||||
{ value: "MINIO_ACCESS_KEY" },
|
{ value: "MINIO_ACCESS_KEY" },
|
||||||
{ value: "MINIO_SECRET_KEY" },
|
{ value: "MINIO_SECRET_KEY" },
|
||||||
]
|
]
|
||||||
|
@ -27,7 +29,7 @@ exports.checkURLs = config => {
|
||||||
] = `http://${username}:${password}@localhost:${mainPort}/db/`
|
] = `http://${username}:${password}@localhost:${mainPort}/db/`
|
||||||
}
|
}
|
||||||
if (!config["MINIO_URL"]) {
|
if (!config["MINIO_URL"]) {
|
||||||
config["MINIO_URL"] = exports.DEFAULT_MINIO
|
config["MINIO_URL"] = `http://localhost:${mainPort}/`
|
||||||
}
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
@ -65,6 +67,10 @@ exports.getConfig = async (envFile = true) => {
|
||||||
} else {
|
} else {
|
||||||
config = await exports.askQuestions()
|
config = await exports.askQuestions()
|
||||||
}
|
}
|
||||||
|
// fill out environment
|
||||||
|
for (let key of Object.keys(config)) {
|
||||||
|
environment._set(key, config[key])
|
||||||
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
require("./prebuilds")
|
require("./prebuilds")
|
||||||
require("./environment")
|
require("./environment")
|
||||||
|
const json = require("../package.json")
|
||||||
const { getCommands } = require("./options")
|
const { getCommands } = require("./options")
|
||||||
const { Command } = require("commander")
|
const { Command } = require("commander")
|
||||||
const { getHelpDescription } = require("./utils")
|
const { getHelpDescription } = require("./utils")
|
||||||
|
@ -10,7 +11,7 @@ async function init() {
|
||||||
const program = new Command()
|
const program = new Command()
|
||||||
.addHelpCommand("help", getHelpDescription("Help with Budibase commands."))
|
.addHelpCommand("help", getHelpDescription("Help with Budibase commands."))
|
||||||
.helpOption(false)
|
.helpOption(false)
|
||||||
program.helpOption()
|
.version(json.version)
|
||||||
// add commands
|
// add commands
|
||||||
for (let command of getCommands()) {
|
for (let command of getCommands()) {
|
||||||
command.configure(program)
|
command.configure(program)
|
||||||
|
|
|
@ -23,6 +23,14 @@ exports.downloadFile = async (url, filePath) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.httpCall = async (url, method) => {
|
||||||
|
const response = await axios({
|
||||||
|
url,
|
||||||
|
method,
|
||||||
|
})
|
||||||
|
return response.data
|
||||||
|
}
|
||||||
|
|
||||||
exports.getHelpDescription = string => {
|
exports.getHelpDescription = string => {
|
||||||
return chalk.cyan(string)
|
return chalk.cyan(string)
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,11 +30,21 @@ module FetchMock {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.includes("/api/global")) {
|
if (url.includes("/api/global")) {
|
||||||
return json({
|
const user = {
|
||||||
email: "test@test.com",
|
email: "test@test.com",
|
||||||
_id: "us_test@test.com",
|
_id: "us_test@test.com",
|
||||||
status: "active",
|
status: "active",
|
||||||
})
|
roles: {},
|
||||||
|
builder: {
|
||||||
|
global: false,
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
global: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return url.endsWith("/users") && opts.method === "GET"
|
||||||
|
? json([user])
|
||||||
|
: json(user)
|
||||||
}
|
}
|
||||||
// mocked data based on url
|
// mocked data based on url
|
||||||
else if (url.includes("api/apps")) {
|
else if (url.includes("api/apps")) {
|
||||||
|
|
|
@ -1,66 +1,51 @@
|
||||||
const { generateWebhookID, getWebhookParams } = require("../../db/utils")
|
import { getWebhookParams } from "../../db/utils"
|
||||||
|
import triggers from "../../automations/triggers"
|
||||||
|
import { db as dbCore, context } from "@budibase/backend-core"
|
||||||
|
import {
|
||||||
|
Webhook,
|
||||||
|
WebhookActionType,
|
||||||
|
BBContext,
|
||||||
|
Automation,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import sdk from "../../sdk"
|
||||||
const toJsonSchema = require("to-json-schema")
|
const toJsonSchema = require("to-json-schema")
|
||||||
const validate = require("jsonschema").validate
|
const validate = require("jsonschema").validate
|
||||||
const { WebhookType } = require("../../constants")
|
|
||||||
const triggers = require("../../automations/triggers")
|
|
||||||
const { getProdAppID } = require("@budibase/backend-core/db")
|
|
||||||
const { getAppDB, updateAppId } = require("@budibase/backend-core/context")
|
|
||||||
|
|
||||||
const AUTOMATION_DESCRIPTION = "Generated from Webhook Schema"
|
const AUTOMATION_DESCRIPTION = "Generated from Webhook Schema"
|
||||||
|
|
||||||
function Webhook(name, type, target) {
|
export async function fetch(ctx: BBContext) {
|
||||||
this.live = true
|
const db = context.getAppDB()
|
||||||
this.name = name
|
|
||||||
this.action = {
|
|
||||||
type,
|
|
||||||
target,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.Webhook = Webhook
|
|
||||||
|
|
||||||
exports.fetch = async ctx => {
|
|
||||||
const db = getAppDB()
|
|
||||||
const response = await db.allDocs(
|
const response = await db.allDocs(
|
||||||
getWebhookParams(null, {
|
getWebhookParams(null, {
|
||||||
include_docs: true,
|
include_docs: true,
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
ctx.body = response.rows.map(row => row.doc)
|
ctx.body = response.rows.map((row: any) => row.doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.save = async ctx => {
|
export async function save(ctx: BBContext) {
|
||||||
const db = getAppDB()
|
const webhook = await sdk.automations.webhook.save(ctx.request.body)
|
||||||
const webhook = ctx.request.body
|
|
||||||
webhook.appId = ctx.appId
|
|
||||||
|
|
||||||
// check that the webhook exists
|
|
||||||
if (webhook._id) {
|
|
||||||
await db.get(webhook._id)
|
|
||||||
} else {
|
|
||||||
webhook._id = generateWebhookID()
|
|
||||||
}
|
|
||||||
const response = await db.put(webhook)
|
|
||||||
webhook._rev = response.rev
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Webhook created successfully",
|
message: "Webhook created successfully",
|
||||||
webhook,
|
webhook,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.destroy = async ctx => {
|
export async function destroy(ctx: BBContext) {
|
||||||
const db = getAppDB()
|
ctx.body = await sdk.automations.webhook.destroy(
|
||||||
ctx.body = await db.remove(ctx.params.id, ctx.params.rev)
|
ctx.params.id,
|
||||||
|
ctx.params.rev
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.buildSchema = async ctx => {
|
export async function buildSchema(ctx: BBContext) {
|
||||||
await updateAppId(ctx.params.instance)
|
await context.updateAppId(ctx.params.instance)
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
const webhook = await db.get(ctx.params.id)
|
const webhook = (await db.get(ctx.params.id)) as Webhook
|
||||||
webhook.bodySchema = toJsonSchema(ctx.request.body)
|
webhook.bodySchema = toJsonSchema(ctx.request.body)
|
||||||
// update the automation outputs
|
// update the automation outputs
|
||||||
if (webhook.action.type === WebhookType.AUTOMATION) {
|
if (webhook.action.type === WebhookActionType.AUTOMATION) {
|
||||||
let automation = await db.get(webhook.action.target)
|
let automation = (await db.get(webhook.action.target)) as Automation
|
||||||
const autoOutputs = automation.definition.trigger.schema.outputs
|
const autoOutputs = automation.definition.trigger.schema.outputs
|
||||||
let properties = webhook.bodySchema.properties
|
let properties = webhook.bodySchema.properties
|
||||||
// reset webhook outputs
|
// reset webhook outputs
|
||||||
|
@ -78,18 +63,18 @@ exports.buildSchema = async ctx => {
|
||||||
ctx.body = await db.put(webhook)
|
ctx.body = await db.put(webhook)
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.trigger = async ctx => {
|
export async function trigger(ctx: BBContext) {
|
||||||
const prodAppId = getProdAppID(ctx.params.instance)
|
const prodAppId = dbCore.getProdAppID(ctx.params.instance)
|
||||||
await updateAppId(prodAppId)
|
await context.updateAppId(prodAppId)
|
||||||
try {
|
try {
|
||||||
const db = getAppDB()
|
const db = context.getAppDB()
|
||||||
const webhook = await db.get(ctx.params.id)
|
const webhook = (await db.get(ctx.params.id)) as Webhook
|
||||||
// validate against the schema
|
// validate against the schema
|
||||||
if (webhook.bodySchema) {
|
if (webhook.bodySchema) {
|
||||||
validate(ctx.request.body, webhook.bodySchema)
|
validate(ctx.request.body, webhook.bodySchema)
|
||||||
}
|
}
|
||||||
const target = await db.get(webhook.action.target)
|
const target = await db.get(webhook.action.target)
|
||||||
if (webhook.action.type === WebhookType.AUTOMATION) {
|
if (webhook.action.type === WebhookActionType.AUTOMATION) {
|
||||||
// trigger with both the pure request and then expand it
|
// trigger with both the pure request and then expand it
|
||||||
// incase the user has produced a schema to bind to
|
// incase the user has produced a schema to bind to
|
||||||
await triggers.externalTrigger(target, {
|
await triggers.externalTrigger(target, {
|
||||||
|
@ -102,7 +87,7 @@ exports.trigger = async ctx => {
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
message: "Webhook trigger fired successfully",
|
message: "Webhook trigger fired successfully",
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
if (err.status === 404) {
|
if (err.status === 404) {
|
||||||
ctx.status = 200
|
ctx.status = 200
|
||||||
ctx.body = {
|
ctx.body = {
|
|
@ -1,10 +1,10 @@
|
||||||
const { joiValidator } = require("@budibase/backend-core/auth")
|
const { joiValidator } = require("@budibase/backend-core/auth")
|
||||||
const { DataSourceOperation } = require("../../../constants")
|
const { DataSourceOperation } = require("../../../constants")
|
||||||
const { WebhookType } = require("../../../constants")
|
|
||||||
const {
|
const {
|
||||||
BUILTIN_PERMISSION_IDS,
|
BUILTIN_PERMISSION_IDS,
|
||||||
PermissionLevels,
|
PermissionLevels,
|
||||||
} = require("@budibase/backend-core/permissions")
|
} = require("@budibase/backend-core/permissions")
|
||||||
|
const { WebhookActionType } = require("@budibase/types")
|
||||||
const Joi = require("joi")
|
const Joi = require("joi")
|
||||||
|
|
||||||
const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
|
const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
|
||||||
|
@ -126,7 +126,7 @@ exports.webhookValidator = () => {
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
bodySchema: Joi.object().optional(),
|
bodySchema: Joi.object().optional(),
|
||||||
action: Joi.object({
|
action: Joi.object({
|
||||||
type: Joi.string().required().valid(WebhookType.AUTOMATION),
|
type: Joi.string().required().valid(WebhookActionType.AUTOMATION),
|
||||||
target: Joi.string().required(),
|
target: Joi.string().required(),
|
||||||
}).required(),
|
}).required(),
|
||||||
}).unknown(true))
|
}).unknown(true))
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
const Router = require("@koa/router")
|
import Router from "@koa/router"
|
||||||
const controller = require("../controllers/webhook")
|
import * as controller from "../controllers/webhook"
|
||||||
const authorized = require("../../middleware/authorized")
|
import authorized from "../../middleware/authorized"
|
||||||
const { BUILDER } = require("@budibase/backend-core/permissions")
|
import { permissions } from "@budibase/backend-core"
|
||||||
const { webhookValidator } = require("./utils/validators")
|
import { webhookValidator } from "./utils/validators"
|
||||||
|
|
||||||
|
const BUILDER = permissions.BUILDER
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
|
||||||
router
|
router
|
||||||
|
@ -23,4 +24,4 @@ router
|
||||||
// this shouldn't have authorisation, right now its always public
|
// this shouldn't have authorisation, right now its always public
|
||||||
.post("/api/webhooks/trigger/:instance/:id", controller.trigger)
|
.post("/api/webhooks/trigger/:instance/:id", controller.trigger)
|
||||||
|
|
||||||
module.exports = router
|
export default router
|
|
@ -15,30 +15,16 @@ db.init()
|
||||||
const Koa = require("koa")
|
const Koa = require("koa")
|
||||||
const destroyable = require("server-destroy")
|
const destroyable = require("server-destroy")
|
||||||
const koaBody = require("koa-body")
|
const koaBody = require("koa-body")
|
||||||
const pino = require("koa-pino-logger")
|
|
||||||
const http = require("http")
|
const http = require("http")
|
||||||
const api = require("./api")
|
const api = require("./api")
|
||||||
const eventEmitter = require("./events")
|
|
||||||
const automations = require("./automations/index")
|
const automations = require("./automations/index")
|
||||||
const Sentry = require("@sentry/node")
|
const Sentry = require("@sentry/node")
|
||||||
const fileSystem = require("./utilities/fileSystem")
|
|
||||||
const bullboard = require("./automations/bullboard")
|
|
||||||
const { logAlert } = require("@budibase/backend-core/logging")
|
const { logAlert } = require("@budibase/backend-core/logging")
|
||||||
const { pinoSettings } = require("@budibase/backend-core")
|
|
||||||
const { Thread } = require("./threads")
|
const { Thread } = require("./threads")
|
||||||
const fs = require("fs")
|
|
||||||
import redis from "./utilities/redis"
|
import redis from "./utilities/redis"
|
||||||
import * as migrations from "./migrations"
|
import { events } from "@budibase/backend-core"
|
||||||
import { events, installation, tenancy } from "@budibase/backend-core"
|
|
||||||
import {
|
|
||||||
createAdminUser,
|
|
||||||
generateApiKey,
|
|
||||||
getChecklist,
|
|
||||||
} from "./utilities/workerRequests"
|
|
||||||
import { watch } from "./watch"
|
|
||||||
import { initialise as initialiseWebsockets } from "./websocket"
|
import { initialise as initialiseWebsockets } from "./websocket"
|
||||||
import sdk from "./sdk"
|
import { startup } from "./startup"
|
||||||
import * as pro from "@budibase/pro"
|
|
||||||
|
|
||||||
const app = new Koa()
|
const app = new Koa()
|
||||||
|
|
||||||
|
@ -54,19 +40,6 @@ app.use(
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
app.use(pino(pinoSettings()))
|
|
||||||
|
|
||||||
if (!env.isTest()) {
|
|
||||||
const plugin = bullboard.init()
|
|
||||||
app.use(plugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
app.context.eventEmitter = eventEmitter
|
|
||||||
app.context.auth = {}
|
|
||||||
|
|
||||||
// api routes
|
|
||||||
app.use(api.router.routes())
|
|
||||||
|
|
||||||
if (env.isProd()) {
|
if (env.isProd()) {
|
||||||
env._set("NODE_ENV", "production")
|
env._set("NODE_ENV", "production")
|
||||||
Sentry.init()
|
Sentry.init()
|
||||||
|
@ -104,86 +77,8 @@ server.on("close", async () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const initPro = async () => {
|
|
||||||
await pro.init({
|
|
||||||
backups: {
|
|
||||||
processing: {
|
|
||||||
exportAppFn: sdk.backups.exportApp,
|
|
||||||
importAppFn: sdk.backups.importApp,
|
|
||||||
statsFn: sdk.backups.calculateBackupStats,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = server.listen(env.PORT || 0, async () => {
|
module.exports = server.listen(env.PORT || 0, async () => {
|
||||||
console.log(`Budibase running on ${JSON.stringify(server.address())}`)
|
await startup(app, server)
|
||||||
env._set("PORT", server.address().port)
|
|
||||||
eventEmitter.emitPort(env.PORT)
|
|
||||||
fileSystem.init()
|
|
||||||
await redis.init()
|
|
||||||
|
|
||||||
// run migrations on startup if not done via http
|
|
||||||
// not recommended in a clustered environment
|
|
||||||
if (!env.HTTP_MIGRATIONS && !env.isTest()) {
|
|
||||||
try {
|
|
||||||
await migrations.migrate()
|
|
||||||
} catch (e) {
|
|
||||||
logAlert("Error performing migrations. Exiting.", e)
|
|
||||||
shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check and create admin user if required
|
|
||||||
if (
|
|
||||||
env.SELF_HOSTED &&
|
|
||||||
!env.MULTI_TENANCY &&
|
|
||||||
env.BB_ADMIN_USER_EMAIL &&
|
|
||||||
env.BB_ADMIN_USER_PASSWORD
|
|
||||||
) {
|
|
||||||
const checklist = await getChecklist()
|
|
||||||
if (!checklist?.adminUser?.checked) {
|
|
||||||
try {
|
|
||||||
const tenantId = tenancy.getTenantId()
|
|
||||||
const user = await createAdminUser(
|
|
||||||
env.BB_ADMIN_USER_EMAIL,
|
|
||||||
env.BB_ADMIN_USER_PASSWORD,
|
|
||||||
tenantId
|
|
||||||
)
|
|
||||||
// Need to set up an API key for automated integration tests
|
|
||||||
if (env.isTest()) {
|
|
||||||
await generateApiKey(user._id)
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"Admin account automatically created for",
|
|
||||||
env.BB_ADMIN_USER_EMAIL
|
|
||||||
)
|
|
||||||
} catch (e) {
|
|
||||||
logAlert("Error creating initial admin user. Exiting.", e)
|
|
||||||
shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// monitor plugin directory if required
|
|
||||||
if (
|
|
||||||
env.SELF_HOSTED &&
|
|
||||||
!env.MULTI_TENANCY &&
|
|
||||||
env.PLUGINS_DIR &&
|
|
||||||
fs.existsSync(env.PLUGINS_DIR)
|
|
||||||
) {
|
|
||||||
watch()
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for version updates
|
|
||||||
await installation.checkInstallVersion()
|
|
||||||
|
|
||||||
// done last - these will never complete
|
|
||||||
let promises = []
|
|
||||||
promises.push(automations.init())
|
|
||||||
promises.push(initPro())
|
|
||||||
await Promise.all(promises)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const shutdown = () => {
|
const shutdown = () => {
|
||||||
|
|
|
@ -3,6 +3,7 @@ const { BullAdapter } = require("@bull-board/api/bullAdapter")
|
||||||
const { KoaAdapter } = require("@bull-board/koa")
|
const { KoaAdapter } = require("@bull-board/koa")
|
||||||
const { queue } = require("@budibase/backend-core")
|
const { queue } = require("@budibase/backend-core")
|
||||||
const automation = require("../threads/automation")
|
const automation = require("../threads/automation")
|
||||||
|
const { backups } = require("@budibase/pro")
|
||||||
|
|
||||||
let automationQueue = queue.createQueue(
|
let automationQueue = queue.createQueue(
|
||||||
queue.JobQueue.AUTOMATION,
|
queue.JobQueue.AUTOMATION,
|
||||||
|
@ -11,9 +12,13 @@ let automationQueue = queue.createQueue(
|
||||||
|
|
||||||
const PATH_PREFIX = "/bulladmin"
|
const PATH_PREFIX = "/bulladmin"
|
||||||
|
|
||||||
exports.init = () => {
|
exports.init = async () => {
|
||||||
// Set up queues for bull board admin
|
// Set up queues for bull board admin
|
||||||
|
const backupQueue = await backups.getBackupQueue()
|
||||||
const queues = [automationQueue]
|
const queues = [automationQueue]
|
||||||
|
if (backupQueue) {
|
||||||
|
queues.push(backupQueue)
|
||||||
|
}
|
||||||
const adapters = []
|
const adapters = []
|
||||||
const serverAdapter = new KoaAdapter()
|
const serverAdapter = new KoaAdapter()
|
||||||
for (let queue of queues) {
|
for (let queue of queues) {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { Thread, ThreadType } from "../threads"
|
import { Thread, ThreadType } from "../threads"
|
||||||
import { definitions } from "./triggerInfo"
|
import { definitions } from "./triggerInfo"
|
||||||
import * as webhooks from "../api/controllers/webhook"
|
|
||||||
import { automationQueue } from "./bullboard"
|
import { automationQueue } from "./bullboard"
|
||||||
import newid from "../db/newid"
|
import newid from "../db/newid"
|
||||||
import { updateEntityMetadata } from "../utilities"
|
import { updateEntityMetadata } from "../utilities"
|
||||||
import { MetadataTypes, WebhookType } from "../constants"
|
import { MetadataTypes } from "../constants"
|
||||||
import { getProdAppID, doWithDB } from "@budibase/backend-core/db"
|
import { getProdAppID, doWithDB } from "@budibase/backend-core/db"
|
||||||
import { getAutomationMetadataParams } from "../db/utils"
|
import { getAutomationMetadataParams } from "../db/utils"
|
||||||
import { cloneDeep } from "lodash/fp"
|
import { cloneDeep } from "lodash/fp"
|
||||||
|
@ -15,7 +14,8 @@ import {
|
||||||
} from "@budibase/backend-core/context"
|
} from "@budibase/backend-core/context"
|
||||||
import { context } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import { quotas } from "@budibase/pro"
|
import { quotas } from "@budibase/pro"
|
||||||
import { Automation } from "@budibase/types"
|
import { Automation, WebhookActionType } from "@budibase/types"
|
||||||
|
import sdk from "../sdk"
|
||||||
|
|
||||||
const REBOOT_CRON = "@reboot"
|
const REBOOT_CRON = "@reboot"
|
||||||
const WH_STEP_ID = definitions.WEBHOOK.stepId
|
const WH_STEP_ID = definitions.WEBHOOK.stepId
|
||||||
|
@ -197,16 +197,12 @@ export async function checkForWebhooks({ oldAuto, newAuto }: any) {
|
||||||
let db = getAppDB()
|
let db = getAppDB()
|
||||||
// need to get the webhook to get the rev
|
// need to get the webhook to get the rev
|
||||||
const webhook = await db.get(oldTrigger.webhookId)
|
const webhook = await db.get(oldTrigger.webhookId)
|
||||||
const ctx = {
|
|
||||||
appId,
|
|
||||||
params: { id: webhook._id, rev: webhook._rev },
|
|
||||||
}
|
|
||||||
// might be updating - reset the inputs to remove the URLs
|
// might be updating - reset the inputs to remove the URLs
|
||||||
if (newTrigger) {
|
if (newTrigger) {
|
||||||
delete newTrigger.webhookId
|
delete newTrigger.webhookId
|
||||||
newTrigger.inputs = {}
|
newTrigger.inputs = {}
|
||||||
}
|
}
|
||||||
await webhooks.destroy(ctx)
|
await sdk.automations.webhook.destroy(webhook._id, webhook._rev)
|
||||||
} 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
|
||||||
}
|
}
|
||||||
|
@ -216,18 +212,14 @@ export async function checkForWebhooks({ oldAuto, newAuto }: any) {
|
||||||
(!isWebhookTrigger(oldAuto) || triggerChanged) &&
|
(!isWebhookTrigger(oldAuto) || triggerChanged) &&
|
||||||
isWebhookTrigger(newAuto)
|
isWebhookTrigger(newAuto)
|
||||||
) {
|
) {
|
||||||
const ctx: any = {
|
const webhook = await sdk.automations.webhook.save(
|
||||||
appId,
|
sdk.automations.webhook.newDoc(
|
||||||
request: {
|
"Automation webhook",
|
||||||
body: new webhooks.Webhook(
|
WebhookActionType.AUTOMATION,
|
||||||
"Automation webhook",
|
newAuto._id
|
||||||
WebhookType.AUTOMATION,
|
)
|
||||||
newAuto._id
|
)
|
||||||
),
|
const id = webhook._id
|
||||||
},
|
|
||||||
}
|
|
||||||
await webhooks.save(ctx)
|
|
||||||
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
|
||||||
// it can only be used when building the app
|
// it can only be used when building the app
|
||||||
|
|
|
@ -196,10 +196,6 @@ exports.BuildSchemaErrors = {
|
||||||
INVALID_COLUMN: "invalid_column",
|
INVALID_COLUMN: "invalid_column",
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.WebhookType = {
|
|
||||||
AUTOMATION: "automation",
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.AutomationErrors = {
|
exports.AutomationErrors = {
|
||||||
INCORRECT_TYPE: "INCORRECT_TYPE",
|
INCORRECT_TYPE: "INCORRECT_TYPE",
|
||||||
MAX_ITERATIONS: "MAX_ITERATIONS_REACHED",
|
MAX_ITERATIONS: "MAX_ITERATIONS_REACHED",
|
||||||
|
|
|
@ -1,9 +1,4 @@
|
||||||
import {
|
import { AutomationResults, AutomationStep, Document } from "@budibase/types"
|
||||||
Automation,
|
|
||||||
AutomationResults,
|
|
||||||
AutomationStep,
|
|
||||||
Document,
|
|
||||||
} from "@budibase/types"
|
|
||||||
|
|
||||||
export enum LoopStepType {
|
export enum LoopStepType {
|
||||||
ARRAY = "Array",
|
ARRAY = "Array",
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
const TestConfig = require("../../../tests/utilities/TestConfiguration")
|
|
||||||
|
|
||||||
const syncApps = jest.fn()
|
const syncApps = jest.fn()
|
||||||
const syncRows = jest.fn()
|
const syncRows = jest.fn()
|
||||||
const syncPlugins = jest.fn()
|
const syncPlugins = jest.fn()
|
||||||
|
|
||||||
jest.mock("../usageQuotas/syncApps", () => ({ run: syncApps }) )
|
jest.mock("../usageQuotas/syncApps", () => ({ run: syncApps }) )
|
||||||
jest.mock("../usageQuotas/syncRows", () => ({ run: syncRows }) )
|
jest.mock("../usageQuotas/syncRows", () => ({ run: syncRows }) )
|
||||||
jest.mock("../usageQuotas/syncPlugins", () => ({ run: syncPlugins }) )
|
jest.mock("../usageQuotas/syncPlugins", () => ({ run: syncPlugins }) )
|
||||||
|
|
||||||
|
const TestConfig = require("../../../tests/utilities/TestConfiguration")
|
||||||
const migration = require("../syncQuotas")
|
const migration = require("../syncQuotas")
|
||||||
|
|
||||||
describe("run", () => {
|
describe("run", () => {
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
|
jest.mock("@budibase/backend-core/db", () => ({
|
||||||
|
...jest.requireActual("@budibase/backend-core/db"),
|
||||||
|
createNewUserEmailView: jest.fn(),
|
||||||
|
}))
|
||||||
|
const coreDb = require("@budibase/backend-core/db")
|
||||||
const TestConfig = require("../../../tests/utilities/TestConfiguration")
|
const TestConfig = require("../../../tests/utilities/TestConfiguration")
|
||||||
const { TENANT_ID } = require("../../../tests/utilities/structures")
|
const { TENANT_ID } = require("../../../tests/utilities/structures")
|
||||||
const { getGlobalDB, doInTenant } = require("@budibase/backend-core/tenancy")
|
const { getGlobalDB, doInTenant } = require("@budibase/backend-core/tenancy")
|
||||||
|
|
||||||
// mock email view creation
|
// mock email view creation
|
||||||
const coreDb = require("@budibase/backend-core/db")
|
|
||||||
const createNewUserEmailView = jest.fn()
|
|
||||||
coreDb.createNewUserEmailView = createNewUserEmailView
|
|
||||||
|
|
||||||
const migration = require("../userEmailViewCasing")
|
const migration = require("../userEmailViewCasing")
|
||||||
|
|
||||||
|
@ -22,7 +24,7 @@ describe("run", () => {
|
||||||
await doInTenant(TENANT_ID, async () => {
|
await doInTenant(TENANT_ID, async () => {
|
||||||
const globalDb = getGlobalDB()
|
const globalDb = getGlobalDB()
|
||||||
await migration.run(globalDb)
|
await migration.run(globalDb)
|
||||||
expect(createNewUserEmailView).toHaveBeenCalledTimes(1)
|
expect(coreDb.createNewUserEmailView).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import * as webhook from "./webhook"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
webhook,
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { Webhook, WebhookActionType } from "@budibase/types"
|
||||||
|
import { db as dbCore, context } from "@budibase/backend-core"
|
||||||
|
import { generateWebhookID } from "../../../db/utils"
|
||||||
|
|
||||||
|
function isWebhookID(id: string) {
|
||||||
|
return id.startsWith(dbCore.DocumentType.WEBHOOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function newDoc(
|
||||||
|
name: string,
|
||||||
|
type: WebhookActionType,
|
||||||
|
target: string
|
||||||
|
): Webhook {
|
||||||
|
return {
|
||||||
|
live: true,
|
||||||
|
name,
|
||||||
|
action: {
|
||||||
|
type,
|
||||||
|
target,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function save(webhook: Webhook) {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
// check that the webhook exists
|
||||||
|
if (webhook._id && isWebhookID(webhook._id)) {
|
||||||
|
await db.get(webhook._id)
|
||||||
|
} else {
|
||||||
|
webhook._id = generateWebhookID()
|
||||||
|
}
|
||||||
|
const response = await db.put(webhook)
|
||||||
|
webhook._rev = response.rev
|
||||||
|
return webhook
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function destroy(id: string, rev: string) {
|
||||||
|
const db = context.getAppDB()
|
||||||
|
if (!id || !isWebhookID(id)) {
|
||||||
|
throw new Error("Provided webhook ID is not valid.")
|
||||||
|
}
|
||||||
|
return await db.remove(id, rev)
|
||||||
|
}
|
|
@ -1,20 +1,25 @@
|
||||||
import { db as dbCore } from "@budibase/backend-core"
|
import { db as dbCore } from "@budibase/backend-core"
|
||||||
import { TABLE_ROW_PREFIX } from "../../../db/utils"
|
import { getAutomationParams, TABLE_ROW_PREFIX } from "../../../db/utils"
|
||||||
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
||||||
import { DB_EXPORT_FILE, GLOBAL_DB_EXPORT_FILE } from "./constants"
|
import { DB_EXPORT_FILE, GLOBAL_DB_EXPORT_FILE } from "./constants"
|
||||||
import {
|
import {
|
||||||
uploadDirectory,
|
|
||||||
upload,
|
upload,
|
||||||
|
uploadDirectory,
|
||||||
} from "../../../utilities/fileSystem/utilities"
|
} from "../../../utilities/fileSystem/utilities"
|
||||||
import { downloadTemplate } from "../../../utilities/fileSystem"
|
import { downloadTemplate } from "../../../utilities/fileSystem"
|
||||||
import { ObjectStoreBuckets, FieldTypes } from "../../../constants"
|
import { FieldTypes, ObjectStoreBuckets } from "../../../constants"
|
||||||
import { join } from "path"
|
import { join } from "path"
|
||||||
import fs from "fs"
|
import fs from "fs"
|
||||||
import sdk from "../../"
|
import sdk from "../../"
|
||||||
import { CouchFindOptions, RowAttachment } from "@budibase/types"
|
import {
|
||||||
|
Automation,
|
||||||
|
AutomationTriggerStepId,
|
||||||
|
CouchFindOptions,
|
||||||
|
RowAttachment,
|
||||||
|
} from "@budibase/types"
|
||||||
|
import PouchDB from "pouchdb"
|
||||||
const uuid = require("uuid/v4")
|
const uuid = require("uuid/v4")
|
||||||
const tar = require("tar")
|
const tar = require("tar")
|
||||||
import PouchDB from "pouchdb"
|
|
||||||
|
|
||||||
type TemplateType = {
|
type TemplateType = {
|
||||||
file?: {
|
file?: {
|
||||||
|
@ -81,6 +86,34 @@ async function updateAttachmentColumns(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateAutomations(prodAppId: string, db: PouchDB.Database) {
|
||||||
|
const automations = (
|
||||||
|
await db.allDocs(
|
||||||
|
getAutomationParams(null, {
|
||||||
|
include_docs: true,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
).rows.map(row => row.doc) as Automation[]
|
||||||
|
const devAppId = dbCore.getDevAppID(prodAppId)
|
||||||
|
let toSave: Automation[] = []
|
||||||
|
for (let automation of automations) {
|
||||||
|
const oldDevAppId = automation.appId,
|
||||||
|
oldProdAppId = dbCore.getProdAppID(automation.appId)
|
||||||
|
if (
|
||||||
|
automation.definition.trigger.stepId === AutomationTriggerStepId.WEBHOOK
|
||||||
|
) {
|
||||||
|
const old = automation.definition.trigger.inputs
|
||||||
|
automation.definition.trigger.inputs = {
|
||||||
|
schemaUrl: old.schemaUrl.replace(oldDevAppId, devAppId),
|
||||||
|
triggerUrl: old.triggerUrl.replace(oldProdAppId, prodAppId),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
automation.appId = devAppId
|
||||||
|
toSave.push(automation)
|
||||||
|
}
|
||||||
|
await db.bulkDocs(toSave)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function manages temporary template files which are stored by Koa.
|
* This function manages temporary template files which are stored by Koa.
|
||||||
* @param {Object} template The template object retrieved from the Koa context object.
|
* @param {Object} template The template object retrieved from the Koa context object.
|
||||||
|
@ -165,5 +198,6 @@ export async function importApp(
|
||||||
throw "Error loading database dump from template."
|
throw "Error loading database dump from template."
|
||||||
}
|
}
|
||||||
await updateAttachmentColumns(prodAppId, db)
|
await updateAttachmentColumns(prodAppId, db)
|
||||||
|
await updateAutomations(prodAppId, db)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import { default as backups } from "./app/backups"
|
import { default as backups } from "./app/backups"
|
||||||
import { default as tables } from "./app/tables"
|
import { default as tables } from "./app/tables"
|
||||||
|
import { default as automations } from "./app/automations"
|
||||||
|
|
||||||
const sdk = {
|
const sdk = {
|
||||||
backups,
|
backups,
|
||||||
tables,
|
tables,
|
||||||
|
automations,
|
||||||
}
|
}
|
||||||
|
|
||||||
// default export for TS
|
// default export for TS
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
import * as env from "./environment"
|
||||||
|
import redis from "./utilities/redis"
|
||||||
|
import {
|
||||||
|
createAdminUser,
|
||||||
|
generateApiKey,
|
||||||
|
getChecklist,
|
||||||
|
} from "./utilities/workerRequests"
|
||||||
|
import {
|
||||||
|
installation,
|
||||||
|
pinoSettings,
|
||||||
|
tenancy,
|
||||||
|
logging,
|
||||||
|
} from "@budibase/backend-core"
|
||||||
|
import fs from "fs"
|
||||||
|
import { watch } from "./watch"
|
||||||
|
import automations from "./automations"
|
||||||
|
import fileSystem from "./utilities/fileSystem"
|
||||||
|
import eventEmitter from "./events"
|
||||||
|
import * as migrations from "./migrations"
|
||||||
|
import bullboard from "./automations/bullboard"
|
||||||
|
import * as pro from "../../../../budibase-pro/packages/pro"
|
||||||
|
import api from "./api"
|
||||||
|
import sdk from "./sdk"
|
||||||
|
const pino = require("koa-pino-logger")
|
||||||
|
|
||||||
|
let STARTUP_RAN = false
|
||||||
|
|
||||||
|
async function initRoutes(app: any) {
|
||||||
|
app.use(pino(pinoSettings()))
|
||||||
|
|
||||||
|
if (!env.isTest()) {
|
||||||
|
const plugin = await bullboard.init()
|
||||||
|
app.use(plugin)
|
||||||
|
}
|
||||||
|
|
||||||
|
app.context.eventEmitter = eventEmitter
|
||||||
|
app.context.auth = {}
|
||||||
|
|
||||||
|
// api routes
|
||||||
|
app.use(api.router.routes())
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initPro() {
|
||||||
|
await pro.init({
|
||||||
|
backups: {
|
||||||
|
processing: {
|
||||||
|
exportAppFn: sdk.backups.exportApp,
|
||||||
|
importAppFn: sdk.backups.importApp,
|
||||||
|
statsFn: sdk.backups.calculateBackupStats,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function shutdown(server?: any) {
|
||||||
|
server.close()
|
||||||
|
server.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function startup(app?: any, server?: any) {
|
||||||
|
if (STARTUP_RAN) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
STARTUP_RAN = true
|
||||||
|
if (server) {
|
||||||
|
console.log(`Budibase running on ${JSON.stringify(server.address())}`)
|
||||||
|
env._set("PORT", server.address().port)
|
||||||
|
}
|
||||||
|
eventEmitter.emitPort(env.PORT)
|
||||||
|
fileSystem.init()
|
||||||
|
await redis.init()
|
||||||
|
|
||||||
|
// run migrations on startup if not done via http
|
||||||
|
// not recommended in a clustered environment
|
||||||
|
if (!env.HTTP_MIGRATIONS && !env.isTest()) {
|
||||||
|
try {
|
||||||
|
await migrations.migrate()
|
||||||
|
} catch (e) {
|
||||||
|
logging.logAlert("Error performing migrations. Exiting.", e)
|
||||||
|
shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check and create admin user if required
|
||||||
|
if (
|
||||||
|
env.SELF_HOSTED &&
|
||||||
|
!env.MULTI_TENANCY &&
|
||||||
|
env.BB_ADMIN_USER_EMAIL &&
|
||||||
|
env.BB_ADMIN_USER_PASSWORD
|
||||||
|
) {
|
||||||
|
const checklist = await getChecklist()
|
||||||
|
if (!checklist?.adminUser?.checked) {
|
||||||
|
try {
|
||||||
|
const tenantId = tenancy.getTenantId()
|
||||||
|
const user = await createAdminUser(
|
||||||
|
env.BB_ADMIN_USER_EMAIL,
|
||||||
|
env.BB_ADMIN_USER_PASSWORD,
|
||||||
|
tenantId
|
||||||
|
)
|
||||||
|
// Need to set up an API key for automated integration tests
|
||||||
|
if (env.isTest()) {
|
||||||
|
await generateApiKey(user._id)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Admin account automatically created for",
|
||||||
|
env.BB_ADMIN_USER_EMAIL
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
logging.logAlert("Error creating initial admin user. Exiting.", e)
|
||||||
|
shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// monitor plugin directory if required
|
||||||
|
if (
|
||||||
|
env.SELF_HOSTED &&
|
||||||
|
!env.MULTI_TENANCY &&
|
||||||
|
env.PLUGINS_DIR &&
|
||||||
|
fs.existsSync(env.PLUGINS_DIR)
|
||||||
|
) {
|
||||||
|
watch()
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for version updates
|
||||||
|
await installation.checkInstallVersion()
|
||||||
|
|
||||||
|
// get the references to the queue promises, don't await as
|
||||||
|
// they will never end, unless the processing stops
|
||||||
|
let queuePromises = []
|
||||||
|
queuePromises.push(automations.init())
|
||||||
|
queuePromises.push(initPro())
|
||||||
|
if (app) {
|
||||||
|
// bring routes online as final step once everything ready
|
||||||
|
await initRoutes(app)
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ const context = require("@budibase/backend-core/context")
|
||||||
const { generateDevInfoID, SEPARATOR } = require("@budibase/backend-core/db")
|
const { generateDevInfoID, SEPARATOR } = require("@budibase/backend-core/db")
|
||||||
const { encrypt } = require("@budibase/backend-core/encryption")
|
const { encrypt } = require("@budibase/backend-core/encryption")
|
||||||
const { DocumentType, generateUserMetadataID } = require("../../db/utils")
|
const { DocumentType, generateUserMetadataID } = require("../../db/utils")
|
||||||
|
const { startup } = require("../../startup")
|
||||||
|
|
||||||
const GLOBAL_USER_ID = "us_uuid1"
|
const GLOBAL_USER_ID = "us_uuid1"
|
||||||
const EMAIL = "babs@babs.com"
|
const EMAIL = "babs@babs.com"
|
||||||
|
@ -41,6 +42,9 @@ class TestConfiguration {
|
||||||
this.server = require("../../app")
|
this.server = require("../../app")
|
||||||
// we need the request for logging in, involves cookies, hard to fake
|
// we need the request for logging in, involves cookies, hard to fake
|
||||||
this.request = supertest(this.server)
|
this.request = supertest(this.server)
|
||||||
|
this.started = true
|
||||||
|
} else {
|
||||||
|
this.started = false
|
||||||
}
|
}
|
||||||
this.appId = null
|
this.appId = null
|
||||||
this.allApps = []
|
this.allApps = []
|
||||||
|
@ -95,6 +99,9 @@ class TestConfiguration {
|
||||||
|
|
||||||
// use a new id as the name to avoid name collisions
|
// use a new id as the name to avoid name collisions
|
||||||
async init(appName = newid()) {
|
async init(appName = newid()) {
|
||||||
|
if (!this.started) {
|
||||||
|
await startup()
|
||||||
|
}
|
||||||
this.user = await this.globalUser()
|
this.user = await this.globalUser()
|
||||||
this.globalUserId = this.user._id
|
this.globalUserId = this.user._id
|
||||||
this.userMetadataId = generateUserMetadataID(this.globalUserId)
|
this.userMetadataId = generateUserMetadataID(this.globalUserId)
|
||||||
|
|
|
@ -1,5 +1,34 @@
|
||||||
import { Document } from "../document"
|
import { Document } from "../document"
|
||||||
|
|
||||||
|
export enum AutomationTriggerStepId {
|
||||||
|
ROW_SAVED = "ROW_SAVED",
|
||||||
|
ROW_UPDATED = "ROW_UPDATED",
|
||||||
|
ROW_DELETED = "ROW_DELETED",
|
||||||
|
WEBHOOK = "WEBHOOK",
|
||||||
|
APP = "APP",
|
||||||
|
CRON = "CRON",
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AutomationActionStepId {
|
||||||
|
SEND_EMAIL_SMTP = "SEND_EMAIL_SMTP",
|
||||||
|
CREATE_ROW = "CREATE_ROW",
|
||||||
|
UPDATE_ROW = "UPDATE_ROW",
|
||||||
|
DELETE_ROW = "DELETE_ROW",
|
||||||
|
OUTGOING_WEBHOOK = "OUTGOING_WEBHOOK",
|
||||||
|
EXECUTE_SCRIPT = "EXECUTE_SCRIPT",
|
||||||
|
EXECUTE_QUERY = "EXECUTE_QUERY",
|
||||||
|
SERVER_LOG = "SERVER_LOG",
|
||||||
|
DELAY = "DELAY",
|
||||||
|
FILTER = "FILTER",
|
||||||
|
QUERY_ROWS = "QUERY_ROWS",
|
||||||
|
LOOP = "LOOP",
|
||||||
|
// these used to be lowercase step IDs, maintain for backwards compat
|
||||||
|
discord = "discord",
|
||||||
|
slack = "slack",
|
||||||
|
zapier = "zapier",
|
||||||
|
integromat = "integromat",
|
||||||
|
}
|
||||||
|
|
||||||
export interface Automation extends Document {
|
export interface Automation extends Document {
|
||||||
definition: {
|
definition: {
|
||||||
steps: AutomationStep[]
|
steps: AutomationStep[]
|
||||||
|
@ -11,7 +40,7 @@ export interface Automation extends Document {
|
||||||
|
|
||||||
export interface AutomationStep {
|
export interface AutomationStep {
|
||||||
id: string
|
id: string
|
||||||
stepId: string
|
stepId: AutomationTriggerStepId | AutomationActionStepId
|
||||||
inputs: {
|
inputs: {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
@ -19,15 +48,13 @@ export interface AutomationStep {
|
||||||
inputs: {
|
inputs: {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
outputs: {
|
||||||
|
[key: string]: any
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutomationTrigger {
|
export interface AutomationTrigger extends AutomationStep {
|
||||||
id: string
|
|
||||||
stepId: string
|
|
||||||
inputs: {
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
cronJobId?: string
|
cronJobId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +70,7 @@ export interface AutomationResults {
|
||||||
status?: AutomationStatus
|
status?: AutomationStatus
|
||||||
trigger?: any
|
trigger?: any
|
||||||
steps: {
|
steps: {
|
||||||
stepId: string
|
stepId: AutomationTriggerStepId | AutomationActionStepId
|
||||||
inputs: {
|
inputs: {
|
||||||
[key: string]: any
|
[key: string]: any
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,3 +11,4 @@ export * from "../document"
|
||||||
export * from "./row"
|
export * from "./row"
|
||||||
export * from "./user"
|
export * from "./user"
|
||||||
export * from "./backup"
|
export * from "./backup"
|
||||||
|
export * from "./webhook"
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Document } from "../document"
|
||||||
|
|
||||||
|
export enum WebhookActionType {
|
||||||
|
AUTOMATION = "automation",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Webhook extends Document {
|
||||||
|
live: boolean
|
||||||
|
name: string
|
||||||
|
action: {
|
||||||
|
type: WebhookActionType
|
||||||
|
target: string
|
||||||
|
}
|
||||||
|
bodySchema?: any
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import env from "../../../environment"
|
||||||
|
import { BBContext } from "@budibase/types"
|
||||||
|
import { cache } from "@budibase/backend-core"
|
||||||
|
|
||||||
|
export async function systemRestored(ctx: BBContext) {
|
||||||
|
if (!env.SELF_HOSTED) {
|
||||||
|
ctx.throw(405, "This operation is not allowed in cloud.")
|
||||||
|
}
|
||||||
|
await cache.bustCache(cache.CacheKeys.CHECKLIST)
|
||||||
|
ctx.body = {
|
||||||
|
message: "System prepared after restore.",
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,6 +55,10 @@ const PUBLIC_ENDPOINTS = [
|
||||||
route: "/api/global/users/tenant/:id",
|
route: "/api/global/users/tenant/:id",
|
||||||
method: "GET",
|
method: "GET",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
route: "/api/system/restored",
|
||||||
|
method: "POST",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const NO_TENANCY_ENDPOINTS = [
|
const NO_TENANCY_ENDPOINTS = [
|
||||||
|
|
|
@ -13,6 +13,7 @@ import selfRoutes from "./global/self"
|
||||||
import licenseRoutes from "./global/license"
|
import licenseRoutes from "./global/license"
|
||||||
import migrationRoutes from "./system/migrations"
|
import migrationRoutes from "./system/migrations"
|
||||||
import accountRoutes from "./system/accounts"
|
import accountRoutes from "./system/accounts"
|
||||||
|
import restoreRoutes from "./system/restore"
|
||||||
|
|
||||||
let userGroupRoutes = api.groups
|
let userGroupRoutes = api.groups
|
||||||
export const routes = [
|
export const routes = [
|
||||||
|
@ -31,4 +32,5 @@ export const routes = [
|
||||||
userGroupRoutes,
|
userGroupRoutes,
|
||||||
migrationRoutes,
|
migrationRoutes,
|
||||||
accountRoutes,
|
accountRoutes,
|
||||||
|
restoreRoutes,
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import * as controller from "../../controllers/system/restore"
|
||||||
|
import Router from "@koa/router"
|
||||||
|
|
||||||
|
const router = new Router()
|
||||||
|
|
||||||
|
router.post("/api/system/restored", controller.systemRestored)
|
||||||
|
|
||||||
|
export = router
|
Loading…
Reference in New Issue