@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.
Files changed (228) hide show
  1. package/builder/assets/backups-default.8c011f1b.png +0 -0
  2. package/builder/assets/{index.acdbfe62.css → index.b15ffe95.css} +1 -1
  3. package/builder/assets/{index.099ad307.js → index.e50f636c.js} +320 -316
  4. package/builder/index.html +2 -2
  5. package/coverage/clover.xml +254 -313
  6. package/coverage/coverage-final.json +15 -17
  7. package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
  8. package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
  9. package/coverage/lcov-report/api/controllers/cloud.js.html +74 -62
  10. package/coverage/lcov-report/api/controllers/datasource.js.html +1 -1
  11. package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
  12. package/coverage/lcov-report/api/controllers/index.html +17 -32
  13. package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
  14. package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
  15. package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
  16. package/coverage/lcov-report/api/controllers/migrations.js.html +1 -1
  17. package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
  18. package/coverage/lcov-report/api/controllers/query/index.html +1 -1
  19. package/coverage/lcov-report/api/controllers/query/validation.js.html +1 -1
  20. package/coverage/lcov-report/api/controllers/role.js.html +1 -1
  21. package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
  22. package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
  23. package/coverage/lcov-report/api/controllers/row/index.html +1 -1
  24. package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
  25. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +1 -1
  26. package/coverage/lcov-report/api/controllers/row/staticFormula.js.html +1 -1
  27. package/coverage/lcov-report/api/controllers/row/utils.js.html +1 -1
  28. package/coverage/lcov-report/api/controllers/script.js.html +1 -1
  29. package/coverage/lcov-report/api/controllers/table/bulkFormula.js.html +12 -9
  30. package/coverage/lcov-report/api/controllers/table/external.js.html +10 -10
  31. package/coverage/lcov-report/api/controllers/table/index.html +16 -16
  32. package/coverage/lcov-report/api/controllers/table/index.js.html +5 -5
  33. package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
  34. package/coverage/lcov-report/api/controllers/user.js.html +1 -1
  35. package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
  36. package/coverage/lcov-report/api/controllers/view/index.html +1 -1
  37. package/coverage/lcov-report/api/controllers/view/index.js.html +3 -3
  38. package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
  39. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
  40. package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
  41. package/coverage/lcov-report/api/index.html +1 -1
  42. package/coverage/lcov-report/api/index.js.html +3 -3
  43. package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
  44. package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
  45. package/coverage/lcov-report/api/routes/automation.js.html +1 -1
  46. package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
  47. package/coverage/lcov-report/api/routes/component.js.html +1 -1
  48. package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
  49. package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
  50. package/coverage/lcov-report/api/routes/dev.js.html +1 -1
  51. package/coverage/lcov-report/api/routes/index.html +5 -20
  52. package/coverage/lcov-report/api/routes/integration.js.html +1 -1
  53. package/coverage/lcov-report/api/routes/layout.js.html +1 -1
  54. package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
  55. package/coverage/lcov-report/api/routes/migrations.js.html +1 -1
  56. package/coverage/lcov-report/api/routes/permission.js.html +1 -1
  57. package/coverage/lcov-report/api/routes/query.js.html +1 -1
  58. package/coverage/lcov-report/api/routes/role.js.html +1 -1
  59. package/coverage/lcov-report/api/routes/routing.js.html +1 -1
  60. package/coverage/lcov-report/api/routes/screen.js.html +1 -1
  61. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  62. package/coverage/lcov-report/api/routes/table.js.html +1 -1
  63. package/coverage/lcov-report/api/routes/templates.js.html +1 -1
  64. package/coverage/lcov-report/api/routes/user.js.html +1 -1
  65. package/coverage/lcov-report/api/routes/utils/index.html +1 -1
  66. package/coverage/lcov-report/api/routes/utils/validators.js.html +1 -1
  67. package/coverage/lcov-report/api/routes/view.js.html +1 -1
  68. package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
  69. package/coverage/lcov-report/automations/actions.js.html +1 -1
  70. package/coverage/lcov-report/automations/automationUtils.js.html +6 -6
  71. package/coverage/lcov-report/automations/bullboard.js.html +17 -92
  72. package/coverage/lcov-report/automations/index.html +18 -18
  73. package/coverage/lcov-report/automations/index.js.html +5 -5
  74. package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
  75. package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
  76. package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
  77. package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
  78. package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
  79. package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
  80. package/coverage/lcov-report/automations/steps/index.html +1 -1
  81. package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
  82. package/coverage/lcov-report/automations/steps/loop.js.html +1 -1
  83. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
  84. package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
  85. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
  86. package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
  87. package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
  88. package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
  89. package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
  90. package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
  91. package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
  92. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
  93. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  94. package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
  95. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
  96. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
  97. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
  98. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
  99. package/coverage/lcov-report/automations/triggers.js.html +5 -5
  100. package/coverage/lcov-report/constants/index.html +5 -5
  101. package/coverage/lcov-report/constants/index.js.html +6 -18
  102. package/coverage/lcov-report/constants/layouts.js.html +1 -1
  103. package/coverage/lcov-report/constants/screens.js.html +1 -1
  104. package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
  105. package/coverage/lcov-report/db/index.html +19 -19
  106. package/coverage/lcov-report/db/index.js.html +1 -1
  107. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
  108. package/coverage/lcov-report/db/linkedRows/index.html +1 -1
  109. package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
  110. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
  111. package/coverage/lcov-report/db/newid.js.html +2 -2
  112. package/coverage/lcov-report/db/utils.js.html +35 -308
  113. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  114. package/coverage/lcov-report/events/index.html +1 -1
  115. package/coverage/lcov-report/events/index.js.html +1 -1
  116. package/coverage/lcov-report/events/utils.js.html +1 -1
  117. package/coverage/lcov-report/index.html +62 -62
  118. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  119. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  120. package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
  121. package/coverage/lcov-report/middleware/builder.js.html +1 -1
  122. package/coverage/lcov-report/middleware/currentapp.js.html +1 -1
  123. package/coverage/lcov-report/middleware/index.html +1 -1
  124. package/coverage/lcov-report/middleware/joi-validator.js.html +1 -1
  125. package/coverage/lcov-report/middleware/publicApi.js.html +1 -1
  126. package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
  127. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  128. package/coverage/lcov-report/middleware/utils.js.html +1 -1
  129. package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
  130. package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
  131. package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
  132. package/coverage/lcov-report/utilities/global.js.html +1 -1
  133. package/coverage/lcov-report/utilities/index.html +1 -1
  134. package/coverage/lcov-report/utilities/index.js.html +1 -1
  135. package/coverage/lcov-report/utilities/plugins.js.html +1 -1
  136. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +52 -52
  137. package/coverage/lcov-report/utilities/queue/index.html +21 -21
  138. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  139. package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
  140. package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
  141. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
  142. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
  143. package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
  144. package/coverage/lcov-report/utilities/security.js.html +2 -2
  145. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  146. package/coverage/lcov-report/utilities/usageQuota/index.html +1 -1
  147. package/coverage/lcov-report/utilities/usageQuota/rows.js.html +1 -1
  148. package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +1 -1
  149. package/coverage/lcov-report/utilities/users.js.html +1 -1
  150. package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
  151. package/coverage/lcov.info +451 -564
  152. package/dist/api/controllers/application.js +2 -5
  153. package/dist/api/controllers/backup.js +19 -13
  154. package/dist/api/controllers/cloud.js +38 -35
  155. package/dist/api/controllers/deploy/index.js +11 -2
  156. package/dist/api/controllers/static/index.js +2 -2
  157. package/dist/api/controllers/table/bulkFormula.js +4 -3
  158. package/dist/api/controllers/table/external.js +5 -4
  159. package/dist/api/controllers/table/index.js +4 -4
  160. package/dist/api/controllers/table/internal.js +2 -1
  161. package/dist/api/controllers/table/utils.js +4 -48
  162. package/dist/api/controllers/view/index.js +2 -2
  163. package/dist/api/routes/backup.js +34 -7
  164. package/dist/api/routes/index.js +9 -4
  165. package/dist/app.js +14 -0
  166. package/dist/automations/automationUtils.js +2 -2
  167. package/dist/automations/bullboard.js +5 -33
  168. package/dist/automations/index.js +4 -4
  169. package/dist/automations/triggers.js +4 -4
  170. package/dist/automations/utils.js +13 -9
  171. package/dist/constants/index.js +2 -5
  172. package/dist/db/utils.js +10 -75
  173. package/dist/migrations/functions/backfill/app/tables.js +5 -8
  174. package/dist/package.json +11 -11
  175. package/dist/sdk/app/backups/constants.js +5 -0
  176. package/dist/sdk/app/backups/exports.js +172 -0
  177. package/dist/sdk/app/backups/imports.js +162 -0
  178. package/dist/sdk/app/backups/index.js +29 -0
  179. package/dist/sdk/app/backups/statistics.js +73 -0
  180. package/dist/sdk/app/tables/index.js +61 -0
  181. package/dist/sdk/index.js +15 -0
  182. package/dist/tests/utilities/TestConfiguration.js +4 -2
  183. package/dist/threads/automation.js +12 -17
  184. package/dist/tsconfig.build.tsbuildinfo +1 -1
  185. package/dist/utilities/fileSystem/index.js +3 -95
  186. package/dist/utilities/fileSystem/utilities.js +2 -1
  187. package/package.json +12 -12
  188. package/src/api/controllers/application.ts +3 -10
  189. package/src/api/controllers/{backup.js → backup.ts} +7 -7
  190. package/src/api/controllers/cloud.js +44 -40
  191. package/src/api/controllers/deploy/index.ts +20 -7
  192. package/src/api/controllers/static/index.ts +2 -2
  193. package/src/api/controllers/table/bulkFormula.js +4 -3
  194. package/src/api/controllers/table/external.js +4 -4
  195. package/src/api/controllers/table/index.js +4 -4
  196. package/src/api/controllers/table/internal.ts +3 -7
  197. package/src/api/controllers/table/utils.ts +1 -52
  198. package/src/api/controllers/view/index.js +2 -2
  199. package/src/api/routes/backup.ts +10 -0
  200. package/src/api/routes/index.ts +11 -5
  201. package/src/api/routes/tests/backup.spec.js +1 -1
  202. package/src/api/routes/tests/{deployment.spec.js → deployment.spec.ts} +4 -4
  203. package/src/app.ts +15 -0
  204. package/src/automations/automationUtils.js +2 -2
  205. package/src/automations/bullboard.js +8 -33
  206. package/src/automations/index.js +4 -4
  207. package/src/automations/triggers.js +4 -4
  208. package/src/automations/utils.ts +12 -8
  209. package/src/constants/index.js +2 -6
  210. package/src/db/utils.js +17 -108
  211. package/src/definitions/automations.ts +0 -12
  212. package/src/migrations/functions/backfill/app/tables.ts +6 -13
  213. package/src/sdk/app/backups/constants.ts +2 -0
  214. package/src/sdk/app/backups/exports.ts +171 -0
  215. package/src/sdk/app/backups/imports.ts +168 -0
  216. package/src/sdk/app/backups/index.ts +9 -0
  217. package/src/sdk/app/backups/statistics.ts +77 -0
  218. package/src/sdk/app/tables/index.ts +60 -0
  219. package/src/sdk/index.ts +13 -0
  220. package/src/tests/utilities/TestConfiguration.js +5 -2
  221. package/src/threads/automation.ts +20 -27
  222. package/src/utilities/fileSystem/index.js +2 -119
  223. package/src/utilities/fileSystem/utilities.js +2 -0
  224. package/coverage/lcov-report/api/controllers/backup.js.html +0 -142
  225. package/coverage/lcov-report/api/routes/backup.js.html +0 -115
  226. package/dist/automations/listeners.js +0 -102
  227. package/src/api/routes/backup.js +0 -10
  228. 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,9 @@
1
+ import * as exportApps from "./exports"
2
+ import * as importApps from "./imports"
3
+ import * as statistics from "./statistics"
4
+
5
+ export default {
6
+ ...exportApps,
7
+ ...importApps,
8
+ ...statistics,
9
+ }
@@ -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
+ }
@@ -0,0 +1,13 @@
1
+ import { default as backups } from "./app/backups"
2
+ import { default as tables } from "./app/tables"
3
+
4
+ const sdk = {
5
+ backups,
6
+ tables,
7
+ }
8
+
9
+ // default export for TS
10
+ export default sdk
11
+
12
+ // default export for JS
13
+ module.exports = sdk
@@ -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 { isRecurring, disableCron, isErrorInOutput } from "../automations/utils"
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
- _repeat?: { jobId: string; jobKey: string }
80
+ _job: Job
77
81
  executionOutput: AutomationContext
78
82
 
79
- constructor(automation: Automation, triggerOutput: TriggerOutput, opts: any) {
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
- if (opts?.repeat) {
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._repeat) {
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 disableCron(this._repeat?.jobId, this._repeat?.jobKey)
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._repeat) {
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(input: AutomationEvent, callback: WorkerCallback) {
479
- const appId = input.data.event.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 (input: AutomationEvent) => {
499
- const appId = input.data.event.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
  }