@budibase/server 2.0.30-alpha.14 → 2.0.30-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/builder/assets/backups-default.8c011f1b.png +0 -0
- package/builder/assets/{index.acdbfe62.css → index.b15ffe95.css} +1 -1
- package/builder/assets/{index.099ad307.js → index.e50f636c.js} +320 -316
- package/builder/index.html +2 -2
- package/coverage/clover.xml +254 -313
- package/coverage/coverage-final.json +15 -17
- package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
- package/coverage/lcov-report/api/controllers/cloud.js.html +74 -62
- package/coverage/lcov-report/api/controllers/datasource.js.html +1 -1
- package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
- package/coverage/lcov-report/api/controllers/index.html +17 -32
- package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
- package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
- package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
- package/coverage/lcov-report/api/controllers/migrations.js.html +1 -1
- package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
- package/coverage/lcov-report/api/controllers/query/index.html +1 -1
- package/coverage/lcov-report/api/controllers/query/validation.js.html +1 -1
- package/coverage/lcov-report/api/controllers/role.js.html +1 -1
- package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/index.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/staticFormula.js.html +1 -1
- package/coverage/lcov-report/api/controllers/row/utils.js.html +1 -1
- package/coverage/lcov-report/api/controllers/script.js.html +1 -1
- package/coverage/lcov-report/api/controllers/table/bulkFormula.js.html +12 -9
- package/coverage/lcov-report/api/controllers/table/external.js.html +10 -10
- package/coverage/lcov-report/api/controllers/table/index.html +16 -16
- package/coverage/lcov-report/api/controllers/table/index.js.html +5 -5
- package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
- package/coverage/lcov-report/api/controllers/user.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.html +1 -1
- package/coverage/lcov-report/api/controllers/view/index.js.html +3 -3
- package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
- package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
- package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
- package/coverage/lcov-report/api/index.html +1 -1
- package/coverage/lcov-report/api/index.js.html +3 -3
- package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
- package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
- package/coverage/lcov-report/api/routes/automation.js.html +1 -1
- package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
- package/coverage/lcov-report/api/routes/component.js.html +1 -1
- package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
- package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
- package/coverage/lcov-report/api/routes/dev.js.html +1 -1
- package/coverage/lcov-report/api/routes/index.html +5 -20
- package/coverage/lcov-report/api/routes/integration.js.html +1 -1
- package/coverage/lcov-report/api/routes/layout.js.html +1 -1
- package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
- package/coverage/lcov-report/api/routes/migrations.js.html +1 -1
- package/coverage/lcov-report/api/routes/permission.js.html +1 -1
- package/coverage/lcov-report/api/routes/query.js.html +1 -1
- package/coverage/lcov-report/api/routes/role.js.html +1 -1
- package/coverage/lcov-report/api/routes/routing.js.html +1 -1
- package/coverage/lcov-report/api/routes/screen.js.html +1 -1
- package/coverage/lcov-report/api/routes/script.js.html +1 -1
- package/coverage/lcov-report/api/routes/table.js.html +1 -1
- package/coverage/lcov-report/api/routes/templates.js.html +1 -1
- package/coverage/lcov-report/api/routes/user.js.html +1 -1
- package/coverage/lcov-report/api/routes/utils/index.html +1 -1
- package/coverage/lcov-report/api/routes/utils/validators.js.html +1 -1
- package/coverage/lcov-report/api/routes/view.js.html +1 -1
- package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/actions.js.html +1 -1
- package/coverage/lcov-report/automations/automationUtils.js.html +6 -6
- package/coverage/lcov-report/automations/bullboard.js.html +17 -92
- package/coverage/lcov-report/automations/index.html +18 -18
- package/coverage/lcov-report/automations/index.js.html +5 -5
- package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
- package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
- package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
- package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
- package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
- package/coverage/lcov-report/automations/steps/index.html +1 -1
- package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
- package/coverage/lcov-report/automations/steps/loop.js.html +1 -1
- package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
- package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
- package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
- package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
- package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
- package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
- package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
- package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
- package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
- package/coverage/lcov-report/automations/triggers.js.html +5 -5
- package/coverage/lcov-report/constants/index.html +5 -5
- package/coverage/lcov-report/constants/index.js.html +6 -18
- package/coverage/lcov-report/constants/layouts.js.html +1 -1
- package/coverage/lcov-report/constants/screens.js.html +1 -1
- package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
- package/coverage/lcov-report/db/index.html +19 -19
- package/coverage/lcov-report/db/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.html +1 -1
- package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
- package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
- package/coverage/lcov-report/db/newid.js.html +2 -2
- package/coverage/lcov-report/db/utils.js.html +35 -308
- package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
- package/coverage/lcov-report/events/index.html +1 -1
- package/coverage/lcov-report/events/index.js.html +1 -1
- package/coverage/lcov-report/events/utils.js.html +1 -1
- package/coverage/lcov-report/index.html +62 -62
- package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
- package/coverage/lcov-report/integrations/tests/index.html +1 -1
- package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
- package/coverage/lcov-report/middleware/builder.js.html +1 -1
- package/coverage/lcov-report/middleware/currentapp.js.html +1 -1
- package/coverage/lcov-report/middleware/index.html +1 -1
- package/coverage/lcov-report/middleware/joi-validator.js.html +1 -1
- package/coverage/lcov-report/middleware/publicApi.js.html +1 -1
- package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
- package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
- package/coverage/lcov-report/middleware/utils.js.html +1 -1
- package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
- package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
- package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
- package/coverage/lcov-report/utilities/global.js.html +1 -1
- package/coverage/lcov-report/utilities/index.html +1 -1
- package/coverage/lcov-report/utilities/index.js.html +1 -1
- package/coverage/lcov-report/utilities/plugins.js.html +1 -1
- package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +52 -52
- package/coverage/lcov-report/utilities/queue/index.html +21 -21
- package/coverage/lcov-report/utilities/routing/index.html +1 -1
- package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
- package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
- package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
- package/coverage/lcov-report/utilities/security.js.html +2 -2
- package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/index.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/rows.js.html +1 -1
- package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +1 -1
- package/coverage/lcov-report/utilities/users.js.html +1 -1
- package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
- package/coverage/lcov.info +451 -564
- package/dist/api/controllers/application.js +2 -5
- package/dist/api/controllers/backup.js +19 -13
- package/dist/api/controllers/cloud.js +38 -35
- package/dist/api/controllers/deploy/index.js +11 -2
- package/dist/api/controllers/static/index.js +2 -2
- package/dist/api/controllers/table/bulkFormula.js +4 -3
- package/dist/api/controllers/table/external.js +5 -4
- package/dist/api/controllers/table/index.js +4 -4
- package/dist/api/controllers/table/internal.js +2 -1
- package/dist/api/controllers/table/utils.js +4 -48
- package/dist/api/controllers/view/index.js +2 -2
- package/dist/api/routes/backup.js +34 -7
- package/dist/api/routes/index.js +9 -4
- package/dist/app.js +14 -0
- package/dist/automations/automationUtils.js +2 -2
- package/dist/automations/bullboard.js +5 -33
- package/dist/automations/index.js +4 -4
- package/dist/automations/triggers.js +4 -4
- package/dist/automations/utils.js +13 -9
- package/dist/constants/index.js +2 -5
- package/dist/db/utils.js +10 -75
- package/dist/migrations/functions/backfill/app/tables.js +5 -8
- package/dist/package.json +11 -11
- package/dist/sdk/app/backups/constants.js +5 -0
- package/dist/sdk/app/backups/exports.js +172 -0
- package/dist/sdk/app/backups/imports.js +162 -0
- package/dist/sdk/app/backups/index.js +29 -0
- package/dist/sdk/app/backups/statistics.js +73 -0
- package/dist/sdk/app/tables/index.js +61 -0
- package/dist/sdk/index.js +15 -0
- package/dist/tests/utilities/TestConfiguration.js +4 -2
- package/dist/threads/automation.js +12 -17
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utilities/fileSystem/index.js +3 -95
- package/dist/utilities/fileSystem/utilities.js +2 -1
- package/package.json +12 -12
- package/src/api/controllers/application.ts +3 -10
- package/src/api/controllers/{backup.js → backup.ts} +7 -7
- package/src/api/controllers/cloud.js +44 -40
- package/src/api/controllers/deploy/index.ts +20 -7
- package/src/api/controllers/static/index.ts +2 -2
- package/src/api/controllers/table/bulkFormula.js +4 -3
- package/src/api/controllers/table/external.js +4 -4
- package/src/api/controllers/table/index.js +4 -4
- package/src/api/controllers/table/internal.ts +3 -7
- package/src/api/controllers/table/utils.ts +1 -52
- package/src/api/controllers/view/index.js +2 -2
- package/src/api/routes/backup.ts +10 -0
- package/src/api/routes/index.ts +11 -5
- package/src/api/routes/tests/backup.spec.js +1 -1
- package/src/api/routes/tests/{deployment.spec.js → deployment.spec.ts} +4 -4
- package/src/app.ts +15 -0
- package/src/automations/automationUtils.js +2 -2
- package/src/automations/bullboard.js +8 -33
- package/src/automations/index.js +4 -4
- package/src/automations/triggers.js +4 -4
- package/src/automations/utils.ts +12 -8
- package/src/constants/index.js +2 -6
- package/src/db/utils.js +17 -108
- package/src/definitions/automations.ts +0 -12
- package/src/migrations/functions/backfill/app/tables.ts +6 -13
- package/src/sdk/app/backups/constants.ts +2 -0
- package/src/sdk/app/backups/exports.ts +171 -0
- package/src/sdk/app/backups/imports.ts +168 -0
- package/src/sdk/app/backups/index.ts +9 -0
- package/src/sdk/app/backups/statistics.ts +77 -0
- package/src/sdk/app/tables/index.ts +60 -0
- package/src/sdk/index.ts +13 -0
- package/src/tests/utilities/TestConfiguration.js +5 -2
- package/src/threads/automation.ts +20 -27
- package/src/utilities/fileSystem/index.js +2 -119
- package/src/utilities/fileSystem/utilities.js +2 -0
- package/coverage/lcov-report/api/controllers/backup.js.html +0 -142
- package/coverage/lcov-report/api/routes/backup.js.html +0 -115
- package/dist/automations/listeners.js +0 -102
- package/src/api/routes/backup.js +0 -10
- package/src/automations/listeners.ts +0 -78
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { db as dbCore } from "@budibase/backend-core"
|
|
2
|
+
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
|
3
|
+
import { retrieveDirectory } from "../../../utilities/fileSystem/utilities"
|
|
4
|
+
import { streamFile, createTempFolder } from "../../../utilities/fileSystem"
|
|
5
|
+
import { ObjectStoreBuckets } from "../../../constants"
|
|
6
|
+
import {
|
|
7
|
+
LINK_USER_METADATA_PREFIX,
|
|
8
|
+
TABLE_ROW_PREFIX,
|
|
9
|
+
USER_METDATA_PREFIX,
|
|
10
|
+
} from "../../../db/utils"
|
|
11
|
+
import { DB_EXPORT_FILE, GLOBAL_DB_EXPORT_FILE } from "./constants"
|
|
12
|
+
import fs from "fs"
|
|
13
|
+
import { join } from "path"
|
|
14
|
+
import env from "../../../environment"
|
|
15
|
+
const uuid = require("uuid/v4")
|
|
16
|
+
const tar = require("tar")
|
|
17
|
+
const MemoryStream = require("memorystream")
|
|
18
|
+
|
|
19
|
+
type ExportOpts = {
|
|
20
|
+
filter?: any
|
|
21
|
+
exportPath?: string
|
|
22
|
+
tar?: boolean
|
|
23
|
+
excludeRows?: boolean
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function tarFilesToTmp(tmpDir: string, files: string[]) {
|
|
27
|
+
const exportFile = join(budibaseTempDir(), `${uuid()}.tar.gz`)
|
|
28
|
+
tar.create(
|
|
29
|
+
{
|
|
30
|
+
sync: true,
|
|
31
|
+
gzip: true,
|
|
32
|
+
file: exportFile,
|
|
33
|
+
recursive: true,
|
|
34
|
+
cwd: tmpDir,
|
|
35
|
+
},
|
|
36
|
+
files
|
|
37
|
+
)
|
|
38
|
+
return exportFile
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Exports a DB to either file or a variable (memory).
|
|
43
|
+
* @param {string} dbName the DB which is to be exported.
|
|
44
|
+
* @param {object} opts various options for the export, e.g. whether to stream,
|
|
45
|
+
* a filter function or the name of the export.
|
|
46
|
+
* @return {*} either a readable stream or a string
|
|
47
|
+
*/
|
|
48
|
+
export async function exportDB(dbName: string, opts: ExportOpts = {}) {
|
|
49
|
+
return dbCore.doWithDB(dbName, async (db: any) => {
|
|
50
|
+
// Write the dump to file if required
|
|
51
|
+
if (opts?.exportPath) {
|
|
52
|
+
const path = opts?.exportPath
|
|
53
|
+
const writeStream = fs.createWriteStream(path)
|
|
54
|
+
await db.dump(writeStream, { filter: opts?.filter })
|
|
55
|
+
return path
|
|
56
|
+
} else {
|
|
57
|
+
// Stringify the dump in memory if required
|
|
58
|
+
const memStream = new MemoryStream()
|
|
59
|
+
let appString = ""
|
|
60
|
+
memStream.on("data", (chunk: any) => {
|
|
61
|
+
appString += chunk.toString()
|
|
62
|
+
})
|
|
63
|
+
await db.dump(memStream, { filter: opts?.filter })
|
|
64
|
+
return appString
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function defineFilter(excludeRows?: boolean) {
|
|
70
|
+
const ids = [USER_METDATA_PREFIX, LINK_USER_METADATA_PREFIX]
|
|
71
|
+
if (excludeRows) {
|
|
72
|
+
ids.push(TABLE_ROW_PREFIX)
|
|
73
|
+
}
|
|
74
|
+
return (doc: any) =>
|
|
75
|
+
!ids.map(key => doc._id.includes(key)).reduce((prev, curr) => prev || curr)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Local utility to back up the database state for an app, excluding global user
|
|
80
|
+
* data or user relationships.
|
|
81
|
+
* @param {string} appId The app to back up
|
|
82
|
+
* @param {object} config Config to send to export DB/attachment export
|
|
83
|
+
* @returns {*} either a string or a stream of the backup
|
|
84
|
+
*/
|
|
85
|
+
export async function exportApp(appId: string, config?: ExportOpts) {
|
|
86
|
+
const prodAppId = dbCore.getProdAppID(appId)
|
|
87
|
+
const appPath = `${prodAppId}/`
|
|
88
|
+
// export bucket contents
|
|
89
|
+
let tmpPath
|
|
90
|
+
if (!env.isTest()) {
|
|
91
|
+
tmpPath = await retrieveDirectory(ObjectStoreBuckets.APPS, appPath)
|
|
92
|
+
} else {
|
|
93
|
+
tmpPath = createTempFolder(uuid())
|
|
94
|
+
}
|
|
95
|
+
const downloadedPath = join(tmpPath, appPath)
|
|
96
|
+
if (fs.existsSync(downloadedPath)) {
|
|
97
|
+
const allFiles = fs.readdirSync(downloadedPath)
|
|
98
|
+
for (let file of allFiles) {
|
|
99
|
+
const path = join(downloadedPath, file)
|
|
100
|
+
// move out of app directory, simplify structure
|
|
101
|
+
fs.renameSync(path, join(downloadedPath, "..", file))
|
|
102
|
+
}
|
|
103
|
+
// remove the old app directory created by object export
|
|
104
|
+
fs.rmdirSync(downloadedPath)
|
|
105
|
+
}
|
|
106
|
+
// enforce an export of app DB to the tmp path
|
|
107
|
+
const dbPath = join(tmpPath, DB_EXPORT_FILE)
|
|
108
|
+
await exportDB(appId, {
|
|
109
|
+
...config,
|
|
110
|
+
filter: defineFilter(config?.excludeRows),
|
|
111
|
+
exportPath: dbPath,
|
|
112
|
+
})
|
|
113
|
+
// if tar requested, return where the tarball is
|
|
114
|
+
if (config?.tar) {
|
|
115
|
+
// now the tmpPath contains both the DB export and attachments, tar this
|
|
116
|
+
const tarPath = tarFilesToTmp(tmpPath, fs.readdirSync(tmpPath))
|
|
117
|
+
// cleanup the tmp export files as tarball returned
|
|
118
|
+
fs.rmSync(tmpPath, { recursive: true, force: true })
|
|
119
|
+
return tarPath
|
|
120
|
+
}
|
|
121
|
+
// tar not requested, turn the directory where export is
|
|
122
|
+
else {
|
|
123
|
+
return tmpPath
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Export all apps + global DB (if supplied) to a single tarball, this includes
|
|
129
|
+
* the attachments for each app as well.
|
|
130
|
+
* @param {object[]} appMetadata The IDs and names of apps to export.
|
|
131
|
+
* @param {string} globalDbContents The contents of the global DB to export as well.
|
|
132
|
+
* @return {string} The path to the tarball.
|
|
133
|
+
*/
|
|
134
|
+
export async function exportMultipleApps(
|
|
135
|
+
appMetadata: { appId: string; name: string }[],
|
|
136
|
+
globalDbContents?: string
|
|
137
|
+
) {
|
|
138
|
+
const tmpPath = join(budibaseTempDir(), uuid())
|
|
139
|
+
fs.mkdirSync(tmpPath)
|
|
140
|
+
let exportPromises: Promise<void>[] = []
|
|
141
|
+
// export each app to a directory, then move it into the complete export
|
|
142
|
+
const exportAndMove = async (appId: string, appName: string) => {
|
|
143
|
+
const path = await exportApp(appId)
|
|
144
|
+
await fs.promises.rename(path, join(tmpPath, appName))
|
|
145
|
+
}
|
|
146
|
+
for (let metadata of appMetadata) {
|
|
147
|
+
exportPromises.push(exportAndMove(metadata.appId, metadata.name))
|
|
148
|
+
}
|
|
149
|
+
// wait for all exports to finish
|
|
150
|
+
await Promise.all(exportPromises)
|
|
151
|
+
// add the global DB contents
|
|
152
|
+
if (globalDbContents) {
|
|
153
|
+
fs.writeFileSync(join(tmpPath, GLOBAL_DB_EXPORT_FILE), globalDbContents)
|
|
154
|
+
}
|
|
155
|
+
const appNames = appMetadata.map(metadata => metadata.name)
|
|
156
|
+
const tarPath = tarFilesToTmp(tmpPath, [...appNames, GLOBAL_DB_EXPORT_FILE])
|
|
157
|
+
// clear up the tmp path now tarball generated
|
|
158
|
+
fs.rmSync(tmpPath, { recursive: true, force: true })
|
|
159
|
+
return tarPath
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Streams a backup of the database state for an app
|
|
164
|
+
* @param {string} appId The ID of the app which is to be backed up.
|
|
165
|
+
* @param {boolean} excludeRows Flag to state whether the export should include data.
|
|
166
|
+
* @returns {*} a readable stream of the backup which is written in real time
|
|
167
|
+
*/
|
|
168
|
+
export async function streamExportApp(appId: string, excludeRows: boolean) {
|
|
169
|
+
const tmpPath = await exportApp(appId, { excludeRows, tar: true })
|
|
170
|
+
return streamFile(tmpPath)
|
|
171
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { db as dbCore } from "@budibase/backend-core"
|
|
2
|
+
import { TABLE_ROW_PREFIX } from "../../../db/utils"
|
|
3
|
+
import { budibaseTempDir } from "../../../utilities/budibaseDir"
|
|
4
|
+
import { DB_EXPORT_FILE, GLOBAL_DB_EXPORT_FILE } from "./constants"
|
|
5
|
+
import {
|
|
6
|
+
uploadDirectory,
|
|
7
|
+
upload,
|
|
8
|
+
} from "../../../utilities/fileSystem/utilities"
|
|
9
|
+
import { downloadTemplate } from "../../../utilities/fileSystem"
|
|
10
|
+
import { ObjectStoreBuckets, FieldTypes } from "../../../constants"
|
|
11
|
+
import { join } from "path"
|
|
12
|
+
import fs from "fs"
|
|
13
|
+
import sdk from "../../"
|
|
14
|
+
import { CouchFindOptions, RowAttachment } from "@budibase/types"
|
|
15
|
+
const uuid = require("uuid/v4")
|
|
16
|
+
const tar = require("tar")
|
|
17
|
+
|
|
18
|
+
type TemplateType = {
|
|
19
|
+
file?: {
|
|
20
|
+
type: string
|
|
21
|
+
path: string
|
|
22
|
+
}
|
|
23
|
+
key?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function updateAttachmentColumns(
|
|
27
|
+
prodAppId: string,
|
|
28
|
+
db: PouchDB.Database
|
|
29
|
+
) {
|
|
30
|
+
// iterate through attachment documents and update them
|
|
31
|
+
const tables = await sdk.tables.getAllInternalTables(db)
|
|
32
|
+
for (let table of tables) {
|
|
33
|
+
const attachmentCols: string[] = []
|
|
34
|
+
for (let [key, column] of Object.entries(table.schema)) {
|
|
35
|
+
if (column.type === FieldTypes.ATTACHMENT) {
|
|
36
|
+
attachmentCols.push(key)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// no attachment columns, nothing to do
|
|
40
|
+
if (attachmentCols.length === 0) {
|
|
41
|
+
continue
|
|
42
|
+
}
|
|
43
|
+
// use the CouchDB Mango query API to lookup rows that have attachments
|
|
44
|
+
const params: CouchFindOptions = {
|
|
45
|
+
selector: {
|
|
46
|
+
_id: {
|
|
47
|
+
$regex: `^${TABLE_ROW_PREFIX}`,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
attachmentCols.forEach(col => (params.selector[col] = { $exists: true }))
|
|
52
|
+
const { rows } = await dbCore.directCouchFind(db.name, params)
|
|
53
|
+
for (let row of rows) {
|
|
54
|
+
for (let column of attachmentCols) {
|
|
55
|
+
if (!Array.isArray(row[column])) {
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
row[column] = row[column].map((attachment: RowAttachment) => {
|
|
59
|
+
// URL looks like: /prod-budi-app-assets/appId/attachments/file.csv
|
|
60
|
+
const urlParts = attachment.url.split("/")
|
|
61
|
+
// drop the first empty element
|
|
62
|
+
urlParts.shift()
|
|
63
|
+
// get the prefix
|
|
64
|
+
const prefix = urlParts.shift()
|
|
65
|
+
// remove the app ID
|
|
66
|
+
urlParts.shift()
|
|
67
|
+
// add new app ID
|
|
68
|
+
urlParts.unshift(prodAppId)
|
|
69
|
+
const key = urlParts.join("/")
|
|
70
|
+
return {
|
|
71
|
+
...attachment,
|
|
72
|
+
key,
|
|
73
|
+
url: `/${prefix}/${key}`,
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// write back the updated attachments
|
|
79
|
+
await db.bulkDocs(rows)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* This function manages temporary template files which are stored by Koa.
|
|
85
|
+
* @param {Object} template The template object retrieved from the Koa context object.
|
|
86
|
+
* @returns {Object} Returns a fs read stream which can be loaded into the database.
|
|
87
|
+
*/
|
|
88
|
+
async function getTemplateStream(template: TemplateType) {
|
|
89
|
+
if (template.file) {
|
|
90
|
+
return fs.createReadStream(template.file.path)
|
|
91
|
+
} else if (template.key) {
|
|
92
|
+
const [type, name] = template.key.split("/")
|
|
93
|
+
const tmpPath = await downloadTemplate(type, name)
|
|
94
|
+
return fs.createReadStream(join(tmpPath, name, "db", "dump.txt"))
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function untarFile(file: { path: string }) {
|
|
99
|
+
const tmpPath = join(budibaseTempDir(), uuid())
|
|
100
|
+
fs.mkdirSync(tmpPath)
|
|
101
|
+
// extract the tarball
|
|
102
|
+
tar.extract({
|
|
103
|
+
sync: true,
|
|
104
|
+
cwd: tmpPath,
|
|
105
|
+
file: file.path,
|
|
106
|
+
})
|
|
107
|
+
return tmpPath
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function getGlobalDBFile(tmpPath: string) {
|
|
111
|
+
return fs.readFileSync(join(tmpPath, GLOBAL_DB_EXPORT_FILE), "utf8")
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function getListOfAppsInMulti(tmpPath: string) {
|
|
115
|
+
return fs.readdirSync(tmpPath).filter(dir => dir !== GLOBAL_DB_EXPORT_FILE)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function importApp(
|
|
119
|
+
appId: string,
|
|
120
|
+
db: PouchDB.Database,
|
|
121
|
+
template: TemplateType
|
|
122
|
+
) {
|
|
123
|
+
let prodAppId = dbCore.getProdAppID(appId)
|
|
124
|
+
let dbStream: any
|
|
125
|
+
const isTar = template.file && template.file.type === "application/gzip"
|
|
126
|
+
const isDirectory =
|
|
127
|
+
template.file && fs.lstatSync(template.file.path).isDirectory()
|
|
128
|
+
if (template.file && (isTar || isDirectory)) {
|
|
129
|
+
const tmpPath = isTar ? untarFile(template.file) : template.file.path
|
|
130
|
+
const contents = fs.readdirSync(tmpPath)
|
|
131
|
+
// have to handle object import
|
|
132
|
+
if (contents.length) {
|
|
133
|
+
let promises = []
|
|
134
|
+
let excludedFiles = [GLOBAL_DB_EXPORT_FILE, DB_EXPORT_FILE]
|
|
135
|
+
for (let filename of contents) {
|
|
136
|
+
const path = join(tmpPath, filename)
|
|
137
|
+
if (excludedFiles.includes(filename)) {
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
filename = join(prodAppId, filename)
|
|
141
|
+
if (fs.lstatSync(path).isDirectory()) {
|
|
142
|
+
promises.push(
|
|
143
|
+
uploadDirectory(ObjectStoreBuckets.APPS, path, filename)
|
|
144
|
+
)
|
|
145
|
+
} else {
|
|
146
|
+
promises.push(
|
|
147
|
+
upload({
|
|
148
|
+
bucket: ObjectStoreBuckets.APPS,
|
|
149
|
+
path,
|
|
150
|
+
filename,
|
|
151
|
+
})
|
|
152
|
+
)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
await Promise.all(promises)
|
|
156
|
+
}
|
|
157
|
+
dbStream = fs.createReadStream(join(tmpPath, DB_EXPORT_FILE))
|
|
158
|
+
} else {
|
|
159
|
+
dbStream = await getTemplateStream(template)
|
|
160
|
+
}
|
|
161
|
+
// @ts-ignore
|
|
162
|
+
const { ok } = await db.load(dbStream)
|
|
163
|
+
if (!ok) {
|
|
164
|
+
throw "Error loading database dump from template."
|
|
165
|
+
}
|
|
166
|
+
await updateAttachmentColumns(prodAppId, db)
|
|
167
|
+
return ok
|
|
168
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { context, db as dbCore } from "@budibase/backend-core"
|
|
2
|
+
import {
|
|
3
|
+
getDatasourceParams,
|
|
4
|
+
getTableParams,
|
|
5
|
+
getAutomationParams,
|
|
6
|
+
getScreenParams,
|
|
7
|
+
} from "../../../db/utils"
|
|
8
|
+
|
|
9
|
+
async function runInContext(appId: string, cb: any, db?: PouchDB.Database) {
|
|
10
|
+
if (db) {
|
|
11
|
+
return cb(db)
|
|
12
|
+
} else {
|
|
13
|
+
const devAppId = dbCore.getDevAppID(appId)
|
|
14
|
+
return context.doInAppContext(devAppId, () => {
|
|
15
|
+
const db = context.getAppDB()
|
|
16
|
+
return cb(db)
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function calculateDatasourceCount(
|
|
22
|
+
appId: string,
|
|
23
|
+
db?: PouchDB.Database
|
|
24
|
+
) {
|
|
25
|
+
return runInContext(
|
|
26
|
+
appId,
|
|
27
|
+
async (db: PouchDB.Database) => {
|
|
28
|
+
const datasourceList = await db.allDocs(getDatasourceParams())
|
|
29
|
+
const tableList = await db.allDocs(getTableParams())
|
|
30
|
+
return datasourceList.rows.length + tableList.rows.length
|
|
31
|
+
},
|
|
32
|
+
db
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function calculateAutomationCount(
|
|
37
|
+
appId: string,
|
|
38
|
+
db?: PouchDB.Database
|
|
39
|
+
) {
|
|
40
|
+
return runInContext(
|
|
41
|
+
appId,
|
|
42
|
+
async (db: PouchDB.Database) => {
|
|
43
|
+
const automationList = await db.allDocs(getAutomationParams())
|
|
44
|
+
return automationList.rows.length
|
|
45
|
+
},
|
|
46
|
+
db
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function calculateScreenCount(
|
|
51
|
+
appId: string,
|
|
52
|
+
db?: PouchDB.Database
|
|
53
|
+
) {
|
|
54
|
+
return runInContext(
|
|
55
|
+
appId,
|
|
56
|
+
async (db: PouchDB.Database) => {
|
|
57
|
+
const screenList = await db.allDocs(getScreenParams())
|
|
58
|
+
return screenList.rows.length
|
|
59
|
+
},
|
|
60
|
+
db
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function calculateBackupStats(appId: string) {
|
|
65
|
+
return runInContext(appId, async (db: PouchDB.Database) => {
|
|
66
|
+
const promises = []
|
|
67
|
+
promises.push(calculateDatasourceCount(appId, db))
|
|
68
|
+
promises.push(calculateAutomationCount(appId, db))
|
|
69
|
+
promises.push(calculateScreenCount(appId, db))
|
|
70
|
+
const responses = await Promise.all(promises)
|
|
71
|
+
return {
|
|
72
|
+
datasources: responses[0],
|
|
73
|
+
automations: responses[1],
|
|
74
|
+
screens: responses[2],
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { getAppDB } from "@budibase/backend-core/context"
|
|
2
|
+
import { BudibaseInternalDB, getTableParams } from "../../../db/utils"
|
|
3
|
+
import {
|
|
4
|
+
breakExternalTableId,
|
|
5
|
+
isExternalTable,
|
|
6
|
+
isSQL,
|
|
7
|
+
} from "../../../integrations/utils"
|
|
8
|
+
import { Table } from "@budibase/types"
|
|
9
|
+
|
|
10
|
+
async function getAllInternalTables(db?: PouchDB.Database): Promise<Table[]> {
|
|
11
|
+
if (!db) {
|
|
12
|
+
db = getAppDB() as PouchDB.Database
|
|
13
|
+
}
|
|
14
|
+
const internalTables = await db.allDocs(
|
|
15
|
+
getTableParams(null, {
|
|
16
|
+
include_docs: true,
|
|
17
|
+
})
|
|
18
|
+
)
|
|
19
|
+
return internalTables.rows.map((tableDoc: any) => ({
|
|
20
|
+
...tableDoc.doc,
|
|
21
|
+
type: "internal",
|
|
22
|
+
sourceId: BudibaseInternalDB._id,
|
|
23
|
+
}))
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function getAllExternalTables(datasourceId: any): Promise<Table[]> {
|
|
27
|
+
const db = getAppDB()
|
|
28
|
+
const datasource = await db.get(datasourceId)
|
|
29
|
+
if (!datasource || !datasource.entities) {
|
|
30
|
+
throw "Datasource is not configured fully."
|
|
31
|
+
}
|
|
32
|
+
return datasource.entities
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function getExternalTable(
|
|
36
|
+
datasourceId: any,
|
|
37
|
+
tableName: any
|
|
38
|
+
): Promise<Table> {
|
|
39
|
+
const entities = await getAllExternalTables(datasourceId)
|
|
40
|
+
return entities[tableName]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function getTable(tableId: any): Promise<Table> {
|
|
44
|
+
const db = getAppDB()
|
|
45
|
+
if (isExternalTable(tableId)) {
|
|
46
|
+
let { datasourceId, tableName } = breakExternalTableId(tableId)
|
|
47
|
+
const datasource = await db.get(datasourceId)
|
|
48
|
+
const table = await getExternalTable(datasourceId, tableName)
|
|
49
|
+
return { ...table, sql: isSQL(datasource) }
|
|
50
|
+
} else {
|
|
51
|
+
return db.get(tableId)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default {
|
|
56
|
+
getAllInternalTables,
|
|
57
|
+
getAllExternalTables,
|
|
58
|
+
getExternalTable,
|
|
59
|
+
getTable,
|
|
60
|
+
}
|
package/src/sdk/index.ts
ADDED
|
@@ -25,7 +25,7 @@ const newid = require("../../db/newid")
|
|
|
25
25
|
const context = require("@budibase/backend-core/context")
|
|
26
26
|
const { generateDevInfoID, SEPARATOR } = require("@budibase/backend-core/db")
|
|
27
27
|
const { encrypt } = require("@budibase/backend-core/encryption")
|
|
28
|
-
const { DocumentType } = require("../../db/utils")
|
|
28
|
+
const { DocumentType, generateUserMetadataID } = require("../../db/utils")
|
|
29
29
|
|
|
30
30
|
const GLOBAL_USER_ID = "us_uuid1"
|
|
31
31
|
const EMAIL = "babs@babs.com"
|
|
@@ -95,7 +95,10 @@ class TestConfiguration {
|
|
|
95
95
|
|
|
96
96
|
// use a new id as the name to avoid name collisions
|
|
97
97
|
async init(appName = newid()) {
|
|
98
|
-
await this.globalUser()
|
|
98
|
+
this.user = await this.globalUser()
|
|
99
|
+
this.globalUserId = this.user._id
|
|
100
|
+
this.userMetadataId = generateUserMetadataID(this.globalUserId)
|
|
101
|
+
|
|
99
102
|
return this.createApp(appName)
|
|
100
103
|
}
|
|
101
104
|
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { default as threadUtils } from "./utils"
|
|
2
|
+
import { Job } from "bull"
|
|
2
3
|
threadUtils.threadSetup()
|
|
3
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
isRecurring,
|
|
6
|
+
disableCronById,
|
|
7
|
+
isErrorInOutput,
|
|
8
|
+
} from "../automations/utils"
|
|
4
9
|
import { default as actions } from "../automations/actions"
|
|
5
10
|
import { default as automationUtils } from "../automations/automationUtils"
|
|
6
11
|
import { default as AutomationEmitter } from "../events/AutomationEmitter"
|
|
@@ -13,7 +18,6 @@ import {
|
|
|
13
18
|
LoopStep,
|
|
14
19
|
LoopStepType,
|
|
15
20
|
LoopInput,
|
|
16
|
-
AutomationEvent,
|
|
17
21
|
TriggerOutput,
|
|
18
22
|
AutomationContext,
|
|
19
23
|
AutomationMetadata,
|
|
@@ -73,19 +77,16 @@ class Orchestrator {
|
|
|
73
77
|
_automation: Automation
|
|
74
78
|
_emitter: any
|
|
75
79
|
_context: AutomationContext
|
|
76
|
-
|
|
80
|
+
_job: Job
|
|
77
81
|
executionOutput: AutomationContext
|
|
78
82
|
|
|
79
|
-
constructor(
|
|
83
|
+
constructor(job: Job) {
|
|
84
|
+
let automation = job.data.automation,
|
|
85
|
+
triggerOutput = job.data.event
|
|
80
86
|
const metadata = triggerOutput.metadata
|
|
81
87
|
this._chainCount = metadata ? metadata.automationChainCount : 0
|
|
82
88
|
this._appId = triggerOutput.appId as string
|
|
83
|
-
|
|
84
|
-
this._repeat = {
|
|
85
|
-
jobId: opts.repeat.jobId,
|
|
86
|
-
jobKey: opts.repeat.key,
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
+
this._job = job
|
|
89
90
|
const triggerStepId = automation.definition.trigger.stepId
|
|
90
91
|
triggerOutput = this.cleanupTriggerOutputs(triggerStepId, triggerOutput)
|
|
91
92
|
// remove from context
|
|
@@ -134,7 +135,7 @@ class Orchestrator {
|
|
|
134
135
|
}
|
|
135
136
|
|
|
136
137
|
async stopCron(reason: string) {
|
|
137
|
-
if (!this.
|
|
138
|
+
if (!this._job.opts.repeat) {
|
|
138
139
|
return
|
|
139
140
|
}
|
|
140
141
|
logWarn(
|
|
@@ -142,7 +143,7 @@ class Orchestrator {
|
|
|
142
143
|
)
|
|
143
144
|
const automation = this._automation
|
|
144
145
|
const trigger = automation.definition.trigger
|
|
145
|
-
await
|
|
146
|
+
await disableCronById(this._job.id)
|
|
146
147
|
this.updateExecutionOutput(
|
|
147
148
|
trigger.id,
|
|
148
149
|
trigger.stepId,
|
|
@@ -156,7 +157,7 @@ class Orchestrator {
|
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
async checkIfShouldStop(metadata: AutomationMetadata): Promise<boolean> {
|
|
159
|
-
if (!metadata.errorCount || !this.
|
|
160
|
+
if (!metadata.errorCount || !this._job.opts.repeat) {
|
|
160
161
|
return false
|
|
161
162
|
}
|
|
162
163
|
if (metadata.errorCount >= MAX_AUTOMATION_RECURRING_ERRORS) {
|
|
@@ -475,17 +476,13 @@ class Orchestrator {
|
|
|
475
476
|
}
|
|
476
477
|
}
|
|
477
478
|
|
|
478
|
-
export function execute(
|
|
479
|
-
const appId =
|
|
479
|
+
export function execute(job: Job, callback: WorkerCallback) {
|
|
480
|
+
const appId = job.data.event.appId
|
|
480
481
|
if (!appId) {
|
|
481
482
|
throw new Error("Unable to execute, event doesn't contain app ID.")
|
|
482
483
|
}
|
|
483
484
|
doInAppContext(appId, async () => {
|
|
484
|
-
const automationOrchestrator = new Orchestrator(
|
|
485
|
-
input.data.automation,
|
|
486
|
-
input.data.event,
|
|
487
|
-
input.opts
|
|
488
|
-
)
|
|
485
|
+
const automationOrchestrator = new Orchestrator(job)
|
|
489
486
|
try {
|
|
490
487
|
const response = await automationOrchestrator.execute()
|
|
491
488
|
callback(null, response)
|
|
@@ -495,17 +492,13 @@ export function execute(input: AutomationEvent, callback: WorkerCallback) {
|
|
|
495
492
|
})
|
|
496
493
|
}
|
|
497
494
|
|
|
498
|
-
export const removeStalled = async (
|
|
499
|
-
const appId =
|
|
495
|
+
export const removeStalled = async (job: Job) => {
|
|
496
|
+
const appId = job.data.event.appId
|
|
500
497
|
if (!appId) {
|
|
501
498
|
throw new Error("Unable to execute, event doesn't contain app ID.")
|
|
502
499
|
}
|
|
503
500
|
await doInAppContext(appId, async () => {
|
|
504
|
-
const automationOrchestrator = new Orchestrator(
|
|
505
|
-
input.data.automation,
|
|
506
|
-
input.data.event,
|
|
507
|
-
input.opts
|
|
508
|
-
)
|
|
501
|
+
const automationOrchestrator = new Orchestrator(job)
|
|
509
502
|
await automationOrchestrator.stopCron("stalled")
|
|
510
503
|
})
|
|
511
504
|
}
|