@budibase/server 1.3.13 → 1.3.15-alpha.1

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 (283) hide show
  1. package/__mocks__/mongodb.ts +1 -1
  2. package/builder/assets/{index.e50bda9f.css → index.ecdac070.css} +2 -2
  3. package/builder/assets/index.ed9ae6a1.js +995 -0
  4. package/builder/index.html +2 -4
  5. package/coverage/clover.xml +377 -313
  6. package/coverage/coverage-final.json +22 -22
  7. package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
  8. package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
  9. package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
  10. package/coverage/lcov-report/api/controllers/backup.js.html +8 -5
  11. package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
  12. package/coverage/lcov-report/api/controllers/component.js.html +82 -13
  13. package/coverage/lcov-report/api/controllers/datasource.js.html +20 -20
  14. package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
  15. package/coverage/lcov-report/api/controllers/index.html +32 -32
  16. package/coverage/lcov-report/api/controllers/integration.js.html +6 -6
  17. package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
  18. package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
  19. package/coverage/lcov-report/api/controllers/migrations.js.html +1 -1
  20. package/coverage/lcov-report/api/controllers/permission.js.html +1 -1
  21. package/coverage/lcov-report/api/controllers/query/index.html +1 -1
  22. package/coverage/lcov-report/api/controllers/query/validation.js.html +1 -1
  23. package/coverage/lcov-report/api/controllers/role.js.html +1 -1
  24. package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
  25. package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
  26. package/coverage/lcov-report/api/controllers/row/index.html +1 -1
  27. package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
  28. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +1 -1
  29. package/coverage/lcov-report/api/controllers/row/staticFormula.js.html +1 -1
  30. package/coverage/lcov-report/api/controllers/row/utils.js.html +1 -1
  31. package/coverage/lcov-report/api/controllers/screen.js.html +204 -12
  32. package/coverage/lcov-report/api/controllers/script.js.html +1 -1
  33. package/coverage/lcov-report/api/controllers/table/bulkFormula.js.html +1 -1
  34. package/coverage/lcov-report/api/controllers/table/external.js.html +1 -1
  35. package/coverage/lcov-report/api/controllers/table/index.html +1 -1
  36. package/coverage/lcov-report/api/controllers/table/index.js.html +1 -1
  37. package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
  38. package/coverage/lcov-report/api/controllers/user.js.html +1 -1
  39. package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
  40. package/coverage/lcov-report/api/controllers/view/index.html +1 -1
  41. package/coverage/lcov-report/api/controllers/view/index.js.html +1 -1
  42. package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
  43. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
  44. package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
  45. package/coverage/lcov-report/api/index.html +1 -1
  46. package/coverage/lcov-report/api/index.js.html +12 -12
  47. package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
  48. package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
  49. package/coverage/lcov-report/api/routes/auth.js.html +1 -1
  50. package/coverage/lcov-report/api/routes/automation.js.html +1 -1
  51. package/coverage/lcov-report/api/routes/backup.js.html +1 -1
  52. package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
  53. package/coverage/lcov-report/api/routes/component.js.html +1 -1
  54. package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
  55. package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
  56. package/coverage/lcov-report/api/routes/dev.js.html +1 -1
  57. package/coverage/lcov-report/api/routes/index.html +1 -1
  58. package/coverage/lcov-report/api/routes/integration.js.html +1 -1
  59. package/coverage/lcov-report/api/routes/layout.js.html +1 -1
  60. package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
  61. package/coverage/lcov-report/api/routes/migrations.js.html +1 -1
  62. package/coverage/lcov-report/api/routes/permission.js.html +1 -1
  63. package/coverage/lcov-report/api/routes/query.js.html +1 -1
  64. package/coverage/lcov-report/api/routes/role.js.html +1 -1
  65. package/coverage/lcov-report/api/routes/routing.js.html +1 -1
  66. package/coverage/lcov-report/api/routes/screen.js.html +1 -1
  67. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  68. package/coverage/lcov-report/api/routes/table.js.html +1 -1
  69. package/coverage/lcov-report/api/routes/templates.js.html +1 -1
  70. package/coverage/lcov-report/api/routes/user.js.html +1 -1
  71. package/coverage/lcov-report/api/routes/utils/index.html +1 -1
  72. package/coverage/lcov-report/api/routes/utils/validators.js.html +1 -1
  73. package/coverage/lcov-report/api/routes/view.js.html +1 -1
  74. package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
  75. package/coverage/lcov-report/automations/actions.js.html +1 -1
  76. package/coverage/lcov-report/automations/automationUtils.js.html +1 -1
  77. package/coverage/lcov-report/automations/bullboard.js.html +1 -1
  78. package/coverage/lcov-report/automations/index.html +24 -24
  79. package/coverage/lcov-report/automations/index.js.html +18 -9
  80. package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
  81. package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
  82. package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
  83. package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
  84. package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
  85. package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
  86. package/coverage/lcov-report/automations/steps/index.html +1 -1
  87. package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
  88. package/coverage/lcov-report/automations/steps/loop.js.html +1 -1
  89. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
  90. package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
  91. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
  92. package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
  93. package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
  94. package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
  95. package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
  96. package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
  97. package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
  98. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
  99. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  100. package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
  101. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
  102. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
  103. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
  104. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
  105. package/coverage/lcov-report/automations/triggers.js.html +126 -24
  106. package/coverage/lcov-report/constants/index.html +1 -1
  107. package/coverage/lcov-report/constants/index.js.html +1 -1
  108. package/coverage/lcov-report/constants/layouts.js.html +1 -1
  109. package/coverage/lcov-report/constants/screens.js.html +1 -1
  110. package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
  111. package/coverage/lcov-report/db/index.html +18 -18
  112. package/coverage/lcov-report/db/index.js.html +1 -1
  113. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
  114. package/coverage/lcov-report/db/linkedRows/index.html +1 -1
  115. package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
  116. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
  117. package/coverage/lcov-report/db/newid.js.html +2 -2
  118. package/coverage/lcov-report/db/utils.js.html +56 -20
  119. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  120. package/coverage/lcov-report/events/index.html +1 -1
  121. package/coverage/lcov-report/events/index.js.html +1 -1
  122. package/coverage/lcov-report/events/utils.js.html +1 -1
  123. package/coverage/lcov-report/index.html +48 -48
  124. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  125. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  126. package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
  127. package/coverage/lcov-report/middleware/builder.js.html +12 -12
  128. package/coverage/lcov-report/middleware/currentapp.js.html +33 -33
  129. package/coverage/lcov-report/middleware/index.html +1 -1
  130. package/coverage/lcov-report/middleware/joi-validator.js.html +1 -1
  131. package/coverage/lcov-report/middleware/publicApi.js.html +1 -1
  132. package/coverage/lcov-report/middleware/resourceId.js.html +7 -7
  133. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  134. package/coverage/lcov-report/middleware/utils.js.html +2 -2
  135. package/coverage/lcov-report/utilities/budibaseDir.js.html +3 -3
  136. package/coverage/lcov-report/utilities/centralPath.js.html +4 -4
  137. package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
  138. package/coverage/lcov-report/utilities/global.js.html +16 -16
  139. package/coverage/lcov-report/utilities/index.html +19 -19
  140. package/coverage/lcov-report/utilities/index.js.html +66 -42
  141. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +2 -2
  142. package/coverage/lcov-report/utilities/queue/index.html +1 -1
  143. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  144. package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
  145. package/coverage/lcov-report/utilities/rowProcessor/index.html +5 -5
  146. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
  147. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +4 -4
  148. package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
  149. package/coverage/lcov-report/utilities/security.js.html +2 -2
  150. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  151. package/coverage/lcov-report/utilities/usageQuota/index.html +1 -1
  152. package/coverage/lcov-report/utilities/usageQuota/rows.js.html +6 -3
  153. package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +1 -1
  154. package/coverage/lcov-report/utilities/users.js.html +1 -1
  155. package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
  156. package/coverage/lcov.info +692 -564
  157. package/dist/api/controllers/application.js +6 -9
  158. package/dist/api/controllers/backup.js +2 -1
  159. package/dist/api/controllers/component.js +18 -1
  160. package/dist/api/controllers/datasource.js +4 -4
  161. package/dist/api/controllers/deploy/index.js +17 -2
  162. package/dist/api/controllers/integration.js +4 -4
  163. package/dist/api/controllers/plugin/file.js +24 -0
  164. package/dist/api/controllers/plugin/github.js +69 -0
  165. package/dist/api/controllers/plugin/index.js +189 -0
  166. package/dist/api/controllers/plugin/npm.js +58 -0
  167. package/dist/api/controllers/plugin/uploaders.js +11 -0
  168. package/dist/api/controllers/plugin/url.js +24 -0
  169. package/dist/api/controllers/plugin/utils.js +26 -0
  170. package/dist/api/controllers/row/ExternalRequest.js +1 -1
  171. package/dist/api/controllers/screen.js +51 -2
  172. package/dist/api/controllers/static/index.js +1 -0
  173. package/dist/api/controllers/static/templates/BudibaseApp.svelte +10 -0
  174. package/dist/api/controllers/static/templates/preview.hbs +3 -1
  175. package/dist/api/routes/index.js +2 -0
  176. package/dist/api/routes/plugin.js +39 -0
  177. package/dist/app.js +9 -0
  178. package/dist/automations/index.js +10 -5
  179. package/dist/automations/triggers.js +39 -5
  180. package/dist/automations/utils.js +16 -7
  181. package/dist/db/utils.js +10 -1
  182. package/dist/environment.js +1 -0
  183. package/dist/integrations/airtable.js +126 -129
  184. package/dist/integrations/arangodb.js +86 -89
  185. package/dist/integrations/base/query.js +2 -2
  186. package/dist/integrations/couchdb.js +80 -83
  187. package/dist/integrations/dynamodb.js +161 -164
  188. package/dist/integrations/elasticsearch.js +135 -138
  189. package/dist/integrations/firebase.js +154 -157
  190. package/dist/integrations/googlesheets.js +323 -327
  191. package/dist/integrations/index.js +93 -51
  192. package/dist/integrations/microsoftSqlServer.js +240 -243
  193. package/dist/integrations/mongodb.js +239 -242
  194. package/dist/integrations/mysql.js +215 -219
  195. package/dist/integrations/oracle.js +318 -322
  196. package/dist/integrations/postgres.js +262 -265
  197. package/dist/integrations/redis.js +134 -139
  198. package/dist/integrations/rest.js +293 -281
  199. package/dist/integrations/s3.js +61 -64
  200. package/dist/integrations/snowflake.js +83 -86
  201. package/dist/package.json +8 -6
  202. package/dist/threads/automation.js +6 -0
  203. package/dist/threads/query.js +2 -2
  204. package/dist/tsconfig.build.tsbuildinfo +1 -1
  205. package/dist/utilities/fileSystem/index.js +116 -7
  206. package/dist/utilities/index.js +8 -0
  207. package/dist/utilities/rowProcessor/utils.js +1 -1
  208. package/dist/utilities/usageQuota/rows.js +2 -1
  209. package/dist/watch.js +77 -0
  210. package/package.json +9 -7
  211. package/scripts/dev/manage.js +1 -0
  212. package/src/api/controllers/application.ts +2 -6
  213. package/src/api/controllers/backup.js +2 -1
  214. package/src/api/controllers/component.js +25 -2
  215. package/src/api/controllers/datasource.js +4 -4
  216. package/src/api/controllers/deploy/index.ts +17 -4
  217. package/src/api/controllers/integration.js +4 -4
  218. package/src/api/controllers/plugin/file.ts +15 -0
  219. package/src/api/controllers/plugin/github.ts +75 -0
  220. package/src/api/controllers/plugin/index.ts +202 -0
  221. package/src/api/controllers/plugin/npm.ts +56 -0
  222. package/src/api/controllers/plugin/uploaders.ts +4 -0
  223. package/src/api/controllers/plugin/url.ts +12 -0
  224. package/src/api/controllers/plugin/utils.ts +19 -0
  225. package/src/api/controllers/row/ExternalRequest.ts +1 -1
  226. package/src/api/controllers/screen.js +65 -1
  227. package/src/api/controllers/static/index.ts +1 -0
  228. package/src/api/controllers/static/templates/BudibaseApp.svelte +10 -0
  229. package/src/api/controllers/static/templates/preview.hbs +3 -1
  230. package/src/api/routes/index.ts +2 -0
  231. package/src/api/routes/plugin.ts +14 -0
  232. package/src/api/routes/tests/query.spec.js +11 -28
  233. package/src/app.ts +12 -0
  234. package/src/automations/index.js +6 -3
  235. package/src/automations/triggers.js +44 -10
  236. package/src/automations/utils.ts +21 -9
  237. package/src/db/utils.js +12 -0
  238. package/src/definitions/datasource.ts +1 -0
  239. package/src/environment.js +1 -0
  240. package/src/integrations/airtable.ts +116 -118
  241. package/src/integrations/arangodb.ts +87 -89
  242. package/src/integrations/base/query.ts +2 -2
  243. package/src/integrations/couchdb.ts +83 -89
  244. package/src/integrations/dynamodb.ts +173 -175
  245. package/src/integrations/elasticsearch.ts +120 -122
  246. package/src/integrations/firebase.ts +157 -159
  247. package/src/integrations/googlesheets.ts +328 -338
  248. package/src/integrations/index.ts +59 -21
  249. package/src/integrations/microsoftSqlServer.ts +249 -251
  250. package/src/integrations/mongodb.ts +261 -258
  251. package/src/integrations/mysql.ts +229 -234
  252. package/src/integrations/oracle.ts +373 -380
  253. package/src/integrations/postgres.ts +264 -268
  254. package/src/integrations/redis.ts +128 -132
  255. package/src/integrations/rest.ts +325 -317
  256. package/src/integrations/s3.ts +68 -71
  257. package/src/integrations/snowflake.ts +80 -82
  258. package/src/integrations/tests/{airtable.spec.js → airtable.spec.ts} +21 -18
  259. package/src/integrations/tests/{arangodb.spec.js → arangodb.spec.ts} +12 -9
  260. package/src/integrations/tests/{couchdb.spec.js → couchdb.spec.ts} +33 -27
  261. package/src/integrations/tests/{dynamodb.spec.js → dynamodb.spec.ts} +45 -41
  262. package/src/integrations/tests/{elasticsearch.spec.js → elasticsearch.spec.ts} +22 -21
  263. package/src/integrations/tests/{firebase.spec.js → firebase.spec.ts} +33 -28
  264. package/src/integrations/tests/{microsoftSqlServer.spec.js → microsoftSqlServer.spec.ts} +12 -11
  265. package/src/integrations/tests/{mongo.spec.js → mongo.spec.ts} +46 -35
  266. package/src/integrations/tests/{mysql.spec.js → mysql.spec.ts} +17 -15
  267. package/src/integrations/tests/{oracle.spec.js → oracle.spec.ts} +19 -17
  268. package/src/integrations/tests/{postgres.spec.js → postgres.spec.ts} +19 -17
  269. package/src/integrations/tests/{redis.spec.js → redis.spec.ts} +27 -18
  270. package/src/integrations/tests/{rest.spec.js → rest.spec.ts} +57 -14
  271. package/src/integrations/tests/{s3.spec.js → s3.spec.ts} +10 -8
  272. package/src/integrations/tests/{sql.spec.js → sql.spec.ts} +228 -177
  273. package/src/module.d.ts +2 -0
  274. package/src/threads/automation.ts +6 -0
  275. package/src/threads/query.ts +2 -2
  276. package/src/utilities/fileSystem/index.js +135 -6
  277. package/src/utilities/index.js +8 -0
  278. package/src/utilities/rowProcessor/utils.js +1 -1
  279. package/src/utilities/usageQuota/rows.js +2 -1
  280. package/src/watch.ts +38 -0
  281. package/builder/assets/index.fb22f106.js +0 -875
  282. package/builder/assets/vendor.5e1fe644.css +0 -1
  283. package/builder/assets/vendor.67dbd8fa.js +0 -248
@@ -0,0 +1,202 @@
1
+ import { ObjectStoreBuckets } from "../../../constants"
2
+ import { loadJSFile } from "../../../utilities/fileSystem"
3
+ import { npmUpload, urlUpload, githubUpload, fileUpload } from "./uploaders"
4
+ import { getGlobalDB } from "@budibase/backend-core/tenancy"
5
+ import { validate } from "@budibase/backend-core/plugins"
6
+ import { generatePluginID, getPluginParams } from "../../../db/utils"
7
+ import {
8
+ uploadDirectory,
9
+ deleteFolder,
10
+ } from "@budibase/backend-core/objectStore"
11
+ import { PluginType, FileType, PluginSource } from "@budibase/types"
12
+ import env from "../../../environment"
13
+
14
+ export async function getPlugins(type?: PluginType) {
15
+ const db = getGlobalDB()
16
+ const response = await db.allDocs(
17
+ getPluginParams(null, {
18
+ include_docs: true,
19
+ })
20
+ )
21
+ const plugins = response.rows.map((row: any) => row.doc)
22
+ if (type) {
23
+ return plugins.filter((plugin: any) => plugin.schema?.type === type)
24
+ } else {
25
+ return plugins
26
+ }
27
+ }
28
+
29
+ export async function upload(ctx: any) {
30
+ const plugins: FileType[] =
31
+ ctx.request.files.file.length > 1
32
+ ? Array.from(ctx.request.files.file)
33
+ : [ctx.request.files.file]
34
+ try {
35
+ let docs = []
36
+ // can do single or multiple plugins
37
+ for (let plugin of plugins) {
38
+ const doc = await processPlugin(plugin, PluginSource.FILE)
39
+ docs.push(doc)
40
+ }
41
+ ctx.body = {
42
+ message: "Plugin(s) uploaded successfully",
43
+ plugins: docs,
44
+ }
45
+ } catch (err: any) {
46
+ const errMsg = err?.message ? err?.message : err
47
+
48
+ ctx.throw(400, `Failed to import plugin: ${errMsg}`)
49
+ }
50
+ }
51
+
52
+ export async function create(ctx: any) {
53
+ const { source, url, headers, githubToken } = ctx.request.body
54
+
55
+ if (!env.SELF_HOSTED) {
56
+ ctx.throw(400, "Plugins not supported outside of self-host.")
57
+ }
58
+
59
+ try {
60
+ let metadata
61
+ let directory
62
+ // Generating random name as a backup and needed for url
63
+ let name = "PLUGIN_" + Math.floor(100000 + Math.random() * 900000)
64
+
65
+ switch (source) {
66
+ case PluginSource.NPM:
67
+ const { metadata: metadataNpm, directory: directoryNpm } =
68
+ await npmUpload(url, name)
69
+ metadata = metadataNpm
70
+ directory = directoryNpm
71
+ break
72
+ case PluginSource.GITHUB:
73
+ const { metadata: metadataGithub, directory: directoryGithub } =
74
+ await githubUpload(url, name, githubToken)
75
+ metadata = metadataGithub
76
+ directory = directoryGithub
77
+ break
78
+ case PluginSource.URL:
79
+ const headersObj = headers || {}
80
+ const { metadata: metadataUrl, directory: directoryUrl } =
81
+ await urlUpload(url, name, headersObj)
82
+ metadata = metadataUrl
83
+ directory = directoryUrl
84
+ break
85
+ }
86
+
87
+ validate(metadata?.schema)
88
+
89
+ const doc = await storePlugin(metadata, directory, source)
90
+
91
+ ctx.body = {
92
+ message: "Plugin uploaded successfully",
93
+ plugins: [doc],
94
+ }
95
+ } catch (err: any) {
96
+ const errMsg = err?.message ? err?.message : err
97
+
98
+ ctx.throw(400, `Failed to import plugin: ${errMsg}`)
99
+ }
100
+ ctx.status = 200
101
+ }
102
+
103
+ export async function fetch(ctx: any) {
104
+ ctx.body = await getPlugins()
105
+ }
106
+
107
+ export async function destroy(ctx: any) {
108
+ const db = getGlobalDB()
109
+ const { pluginId } = ctx.params
110
+
111
+ try {
112
+ const plugin = await db.get(pluginId)
113
+ const bucketPath = `${plugin.name}/`
114
+ await deleteFolder(ObjectStoreBuckets.PLUGINS, bucketPath)
115
+
116
+ await db.remove(pluginId, plugin._rev)
117
+ } catch (err: any) {
118
+ const errMsg = err?.message ? err?.message : err
119
+
120
+ ctx.throw(400, `Failed to delete plugin: ${errMsg}`)
121
+ }
122
+
123
+ ctx.message = `Plugin ${ctx.params.pluginId} deleted.`
124
+ ctx.status = 200
125
+ }
126
+
127
+ export async function storePlugin(
128
+ metadata: any,
129
+ directory: any,
130
+ source?: string
131
+ ) {
132
+ const db = getGlobalDB()
133
+ const version = metadata.package.version,
134
+ name = metadata.package.name,
135
+ description = metadata.package.description
136
+
137
+ // first open the tarball into tmp directory
138
+ const bucketPath = `${name}/`
139
+ const files = await uploadDirectory(
140
+ ObjectStoreBuckets.PLUGINS,
141
+ directory,
142
+ bucketPath
143
+ )
144
+ const jsFile = files.find((file: any) => file.name.endsWith(".js"))
145
+ if (!jsFile) {
146
+ throw new Error(`Plugin missing .js file.`)
147
+ }
148
+ // validate the JS for a datasource
149
+ if (metadata.schema.type === PluginType.DATASOURCE) {
150
+ const js = loadJSFile(directory, jsFile.name)
151
+ // TODO: this isn't safe - but we need full node environment
152
+ // in future we should do this in a thread for safety
153
+ try {
154
+ eval(js)
155
+ } catch (err: any) {
156
+ const message = err?.message ? err.message : JSON.stringify(err)
157
+ throw new Error(`JS invalid: ${message}`)
158
+ }
159
+ }
160
+ const jsFileName = jsFile.name
161
+ const pluginId = generatePluginID(name)
162
+
163
+ // overwrite existing docs entirely if they exist
164
+ let rev
165
+ try {
166
+ const existing = await db.get(pluginId)
167
+ rev = existing._rev
168
+ } catch (err) {
169
+ rev = undefined
170
+ }
171
+ let doc = {
172
+ _id: pluginId,
173
+ _rev: rev,
174
+ ...metadata,
175
+ name,
176
+ version,
177
+ description,
178
+ jsUrl: `${bucketPath}${jsFileName}`,
179
+ }
180
+
181
+ if (source) {
182
+ doc = {
183
+ ...doc,
184
+ source,
185
+ }
186
+ }
187
+
188
+ const response = await db.put(doc)
189
+ return {
190
+ ...doc,
191
+ _rev: response.rev,
192
+ }
193
+ }
194
+
195
+ export async function processPlugin(plugin: FileType, source?: string) {
196
+ if (!env.SELF_HOSTED) {
197
+ throw new Error("Plugins not supported outside of self-host.")
198
+ }
199
+
200
+ const { metadata, directory } = await fileUpload(plugin)
201
+ return await storePlugin(metadata, directory, source)
202
+ }
@@ -0,0 +1,56 @@
1
+ import {
2
+ getPluginMetadata,
3
+ findFileRec,
4
+ extractTarball,
5
+ deleteFolderFileSystem,
6
+ } from "../../../utilities/fileSystem"
7
+ import fetch from "node-fetch"
8
+ import { join } from "path"
9
+ import { downloadUnzipTarball } from "./utils"
10
+
11
+ export async function npmUpload(url: string, name: string, headers = {}) {
12
+ let npmTarballUrl = url
13
+ let pluginName = name
14
+
15
+ if (
16
+ !npmTarballUrl.includes("https://www.npmjs.com") &&
17
+ !npmTarballUrl.includes("https://registry.npmjs.org")
18
+ ) {
19
+ throw new Error("The plugin origin must be from NPM")
20
+ }
21
+
22
+ if (!npmTarballUrl.includes(".tgz")) {
23
+ const npmPackageURl = url.replace(
24
+ "https://www.npmjs.com/package/",
25
+ "https://registry.npmjs.org/"
26
+ )
27
+ const response = await fetch(npmPackageURl)
28
+ if (response.status !== 200) {
29
+ throw new Error("NPM Package not found")
30
+ }
31
+
32
+ let npmDetails = await response.json()
33
+ pluginName = npmDetails.name
34
+ const npmVersion = npmDetails["dist-tags"].latest
35
+ npmTarballUrl = npmDetails?.versions?.[npmVersion]?.dist?.tarball
36
+
37
+ if (!npmTarballUrl) {
38
+ throw new Error("NPM tarball url not found")
39
+ }
40
+ }
41
+
42
+ const path = await downloadUnzipTarball(npmTarballUrl, pluginName, headers)
43
+ const tarballPluginFile = findFileRec(path, ".tar.gz")
44
+ if (!tarballPluginFile) {
45
+ throw new Error("Tarball plugin file not found")
46
+ }
47
+
48
+ try {
49
+ await extractTarball(tarballPluginFile, path)
50
+ deleteFolderFileSystem(join(path, "package"))
51
+ } catch (err: any) {
52
+ throw new Error(err)
53
+ }
54
+
55
+ return await getPluginMetadata(path)
56
+ }
@@ -0,0 +1,4 @@
1
+ export { fileUpload } from "./file"
2
+ export { githubUpload } from "./github"
3
+ export { npmUpload } from "./npm"
4
+ export { urlUpload } from "./url"
@@ -0,0 +1,12 @@
1
+ import { downloadUnzipTarball } from "./utils"
2
+ import { getPluginMetadata } from "../../../utilities/fileSystem"
3
+
4
+ export async function urlUpload(url: string, name = "", headers = {}) {
5
+ if (!url.includes(".tar.gz")) {
6
+ throw new Error("Plugin must be compressed into a gzipped tarball.")
7
+ }
8
+
9
+ const path = await downloadUnzipTarball(url, name, headers)
10
+
11
+ return await getPluginMetadata(path)
12
+ }
@@ -0,0 +1,19 @@
1
+ import {
2
+ createTempFolder,
3
+ downloadTarballDirect,
4
+ } from "../../../utilities/fileSystem"
5
+
6
+ export async function downloadUnzipTarball(
7
+ url: string,
8
+ name: string,
9
+ headers = {}
10
+ ) {
11
+ try {
12
+ const path = createTempFolder(name)
13
+ await downloadTarballDirect(url, path, headers)
14
+
15
+ return path
16
+ } catch (e: any) {
17
+ throw new Error(e.message)
18
+ }
19
+ }
@@ -534,7 +534,7 @@ module External {
534
534
  })
535
535
  // this is the response from knex if no rows found
536
536
  const rows = !response[0].read ? response : []
537
- const storeTo = isMany ? field.throughFrom || linkPrimaryKey : manyKey
537
+ const storeTo = isMany ? field.throughFrom || linkPrimaryKey : fieldName
538
538
  related[storeTo] = { rows, isMany, tableId }
539
539
  }
540
540
  return related
@@ -1,7 +1,14 @@
1
- const { getScreenParams, generateScreenID } = require("../../db/utils")
1
+ const {
2
+ getScreenParams,
3
+ generateScreenID,
4
+ getPluginParams,
5
+ DocumentType,
6
+ } = require("../../db/utils")
2
7
  const { AccessController } = require("@budibase/backend-core/roles")
3
8
  const { getAppDB } = require("@budibase/backend-core/context")
4
9
  const { events } = require("@budibase/backend-core")
10
+ const { getGlobalDB } = require("@budibase/backend-core/tenancy")
11
+ const { updateAppPackage } = require("./application")
5
12
 
6
13
  exports.fetch = async ctx => {
7
14
  const db = getAppDB()
@@ -32,6 +39,47 @@ exports.save = async ctx => {
32
39
 
33
40
  const response = await db.put(screen)
34
41
 
42
+ // Find any custom components being used
43
+ let pluginNames = []
44
+ let pluginAdded = false
45
+ findPlugins(screen.props, pluginNames)
46
+ if (pluginNames.length) {
47
+ const globalDB = getGlobalDB()
48
+ const pluginsResponse = await globalDB.allDocs(
49
+ getPluginParams(null, {
50
+ include_docs: true,
51
+ })
52
+ )
53
+ const requiredPlugins = pluginsResponse.rows
54
+ .map(row => row.doc)
55
+ .filter(plugin => {
56
+ return (
57
+ plugin.schema.type === "component" &&
58
+ pluginNames.includes(`plugin/${plugin.name}`)
59
+ )
60
+ })
61
+
62
+ // Update the app metadata
63
+ const application = await db.get(DocumentType.APP_METADATA)
64
+ let usedPlugins = application.usedPlugins || []
65
+
66
+ requiredPlugins.forEach(plugin => {
67
+ if (!usedPlugins.find(x => x._id === plugin._id)) {
68
+ pluginAdded = true
69
+ usedPlugins.push({
70
+ _id: plugin._id,
71
+ name: plugin.name,
72
+ version: plugin.version,
73
+ jsUrl: plugin.jsUrl,
74
+ })
75
+ }
76
+ })
77
+
78
+ if (pluginAdded) {
79
+ await updateAppPackage({ usedPlugins }, ctx.appId)
80
+ }
81
+ }
82
+
35
83
  if (eventFn) {
36
84
  await eventFn(screen)
37
85
  }
@@ -40,6 +88,7 @@ exports.save = async ctx => {
40
88
  ...screen,
41
89
  _id: response.id,
42
90
  _rev: response.rev,
91
+ pluginAdded,
43
92
  }
44
93
  }
45
94
 
@@ -56,3 +105,18 @@ exports.destroy = async ctx => {
56
105
  }
57
106
  ctx.status = 200
58
107
  }
108
+
109
+ const findPlugins = (component, foundPlugins) => {
110
+ if (!component) {
111
+ return
112
+ }
113
+ if (component._component.startsWith("plugin")) {
114
+ if (!foundPlugins.includes(component._component)) {
115
+ foundPlugins.push(component._component)
116
+ }
117
+ }
118
+ if (!component._children || !component._children.length) {
119
+ return
120
+ }
121
+ component._children.forEach(child => findPlugins(child, foundPlugins))
122
+ }
@@ -113,6 +113,7 @@ export const serveApp = async function (ctx: any) {
113
113
  production: env.isProd(),
114
114
  appId,
115
115
  clientLibPath: clientLibraryPath(appId, appInfo.version, ctx),
116
+ usedPlugins: appInfo.usedPlugins,
116
117
  })
117
118
 
118
119
  const appHbs = loadHandlebarsFile(`${__dirname}/templates/app.hbs`)
@@ -3,6 +3,7 @@
3
3
  export let favicon = ""
4
4
 
5
5
  export let clientLibPath
6
+ export let usedPlugins
6
7
  </script>
7
8
 
8
9
  <svelte:head>
@@ -83,6 +84,15 @@
83
84
  </script>
84
85
  <script type="application/javascript" src={clientLibPath}>
85
86
  </script>
87
+ <!-- Custom components need inserted after the core client library -->
88
+ <!-- But before loadBudibase is called -->
89
+ {#if usedPlugins?.length}
90
+ {#each usedPlugins as plugin}
91
+ <script
92
+ type="application/javascript"
93
+ src={`/plugins/${plugin.jsUrl}`}></script>
94
+ {/each}
95
+ {/if}
86
96
  <script type="application/javascript">
87
97
  if (window.loadBudibase) {
88
98
  window.loadBudibase()
@@ -66,7 +66,8 @@
66
66
  customTheme,
67
67
  previewDevice,
68
68
  navigation,
69
- hiddenComponentIds
69
+ hiddenComponentIds,
70
+ usedPlugins
70
71
  } = parsed
71
72
 
72
73
  // Set some flags so the app knows we're in the builder
@@ -81,6 +82,7 @@
81
82
  window["##BUDIBASE_PREVIEW_DEVICE##"] = previewDevice
82
83
  window["##BUDIBASE_PREVIEW_NAVIGATION##"] = navigation
83
84
  window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"] = hiddenComponentIds
85
+ window["##BUDIBASE_USED_PLUGINS##"] = usedPlugins
84
86
 
85
87
  // Initialise app
86
88
  try {
@@ -24,6 +24,7 @@ import metadataRoutes from "./metadata"
24
24
  import devRoutes from "./dev"
25
25
  import cloudRoutes from "./cloud"
26
26
  import migrationRoutes from "./migrations"
27
+ import pluginRoutes from "./plugin"
27
28
 
28
29
  export { default as staticRoutes } from "./static"
29
30
  export { default as publicRoutes } from "./public"
@@ -57,4 +58,5 @@ export const mainRoutes = [
57
58
  tableRoutes,
58
59
  rowRoutes,
59
60
  migrationRoutes,
61
+ pluginRoutes,
60
62
  ]
@@ -0,0 +1,14 @@
1
+ import Router from "@koa/router"
2
+ import * as controller from "../controllers/plugin"
3
+ import authorized from "../../middleware/authorized"
4
+ import { BUILDER } from "@budibase/backend-core/permissions"
5
+
6
+ const router = new Router()
7
+
8
+ router
9
+ .post("/api/plugin/upload", authorized(BUILDER), controller.upload)
10
+ .post("/api/plugin", authorized(BUILDER), controller.create)
11
+ .get("/api/plugin", authorized(BUILDER), controller.fetch)
12
+ .delete("/api/plugin/:pluginId", authorized(BUILDER), controller.destroy)
13
+
14
+ export default router
@@ -231,20 +231,6 @@ describe("/queries", () => {
231
231
  url: `/api/queries/preview`,
232
232
  })
233
233
  })
234
-
235
- it("should fail with invalid integration type", async () => {
236
- const { datasource } = await createInvalidIntegration()
237
- await request
238
- .post(`/api/queries/preview`)
239
- .send({
240
- datasourceId: datasource._id,
241
- parameters: {},
242
- fields: {},
243
- queryVerb: "read",
244
- })
245
- .set(config.defaultHeaders())
246
- .expect(400)
247
- })
248
234
  })
249
235
 
250
236
  describe("execute", () => {
@@ -261,17 +247,14 @@ describe("/queries", () => {
261
247
  })
262
248
 
263
249
  it("should fail with invalid integration type", async () => {
264
- const { query, datasource } = await createInvalidIntegration()
265
- await request
266
- .post(`/api/queries/${query._id}`)
267
- .send({
268
- datasourceId: datasource._id,
269
- parameters: {},
270
- fields: {},
271
- queryVerb: "read",
272
- })
273
- .set(config.defaultHeaders())
274
- .expect(400)
250
+ let error
251
+ try {
252
+ await createInvalidIntegration()
253
+ } catch (err) {
254
+ error = err
255
+ }
256
+ expect(error).toBeDefined()
257
+ expect(error.message).toBe("No datasource implementation found.")
275
258
  })
276
259
  })
277
260
 
@@ -311,7 +294,7 @@ describe("/queries", () => {
311
294
  "url": "string",
312
295
  "value": "string"
313
296
  })
314
- expect(res.body.rows[0].url).toContain("doctype html")
297
+ expect(res.body.rows[0].url).toContain("doctype%20html")
315
298
  })
316
299
 
317
300
  it("check that it automatically retries on fail with cached dynamics", async () => {
@@ -396,7 +379,7 @@ describe("/queries", () => {
396
379
  "queryHdr": userDetails.firstName,
397
380
  "secondHdr" : "1234"
398
381
  })
399
- expect(res.body.rows[0].url).toEqual("http://www.google.com?email=" + userDetails.email)
382
+ expect(res.body.rows[0].url).toEqual("http://www.google.com?email=" + userDetails.email.replace("@", "%40"))
400
383
  })
401
384
 
402
385
  it("should bind the current user to query parameters", async () => {
@@ -413,7 +396,7 @@ describe("/queries", () => {
413
396
  "testParam" : "1234"
414
397
  })
415
398
 
416
- expect(res.body.rows[0].url).toEqual("http://www.google.com?test=" + userDetails.email +
399
+ expect(res.body.rows[0].url).toEqual("http://www.google.com?test=" + userDetails.email.replace("@", "%40") +
417
400
  "&testName=" + userDetails.firstName + "&testParam=1234")
418
401
  })
419
402
 
package/src/app.ts CHANGED
@@ -26,10 +26,12 @@ const bullboard = require("./automations/bullboard")
26
26
  const { logAlert } = require("@budibase/backend-core/logging")
27
27
  const { pinoSettings } = require("@budibase/backend-core")
28
28
  const { Thread } = require("./threads")
29
+ const fs = require("fs")
29
30
  import redis from "./utilities/redis"
30
31
  import * as migrations from "./migrations"
31
32
  import { events, installation, tenancy } from "@budibase/backend-core"
32
33
  import { createAdminUser, getChecklist } from "./utilities/workerRequests"
34
+ import { watch } from "./watch"
33
35
 
34
36
  const app = new Koa()
35
37
 
@@ -139,6 +141,16 @@ module.exports = server.listen(env.PORT || 0, async () => {
139
141
  }
140
142
  }
141
143
 
144
+ // monitor plugin directory if required
145
+ if (
146
+ env.SELF_HOSTED &&
147
+ !env.MULTI_TENANCY &&
148
+ env.PLUGINS_DIR &&
149
+ fs.existsSync(env.PLUGINS_DIR)
150
+ ) {
151
+ watch()
152
+ }
153
+
142
154
  // check for version updates
143
155
  await installation.checkInstallVersion()
144
156
 
@@ -1,16 +1,19 @@
1
1
  const { processEvent } = require("./utils")
2
2
  const { queue, shutdown } = require("./bullboard")
3
- const { TRIGGER_DEFINITIONS } = require("./triggers")
3
+ const { TRIGGER_DEFINITIONS, rebootTrigger } = require("./triggers")
4
4
  const { ACTION_DEFINITIONS } = require("./actions")
5
5
 
6
6
  /**
7
7
  * This module is built purely to kick off the worker farm and manage the inputs/outputs
8
8
  */
9
- exports.init = function () {
9
+ exports.init = async function () {
10
10
  // this promise will not complete
11
- return queue.process(async job => {
11
+ const promise = queue.process(async job => {
12
12
  await processEvent(job)
13
13
  })
14
+ // on init we need to trigger any reboot automations
15
+ await rebootTrigger()
16
+ return promise
14
17
  }
15
18
 
16
19
  exports.getQueues = () => {
@@ -9,6 +9,7 @@ const { checkTestFlag } = require("../utilities/redis")
9
9
  const utils = require("./utils")
10
10
  const env = require("../environment")
11
11
  const { doInAppContext, getAppDB } = require("@budibase/backend-core/context")
12
+ const { getAllApps } = require("@budibase/backend-core/db")
12
13
 
13
14
  const TRIGGER_DEFINITIONS = definitions
14
15
  const JOB_OPTS = {
@@ -16,24 +17,27 @@ const JOB_OPTS = {
16
17
  removeOnFail: true,
17
18
  }
18
19
 
20
+ async function getAllAutomations() {
21
+ const db = getAppDB()
22
+ let automations = await db.allDocs(
23
+ getAutomationParams(null, { include_docs: true })
24
+ )
25
+ return automations.rows.map(row => row.doc)
26
+ }
27
+
19
28
  async function queueRelevantRowAutomations(event, eventType) {
20
29
  if (event.appId == null) {
21
30
  throw `No appId specified for ${eventType} - check event emitters.`
22
31
  }
23
32
 
24
33
  doInAppContext(event.appId, async () => {
25
- const db = getAppDB()
26
- let automations = await db.allDocs(
27
- getAutomationParams(null, { include_docs: true })
28
- )
34
+ let automations = await getAllAutomations()
29
35
 
30
36
  // filter down to the correct event type
31
- automations = automations.rows
32
- .map(automation => automation.doc)
33
- .filter(automation => {
34
- const trigger = automation.definition.trigger
35
- return trigger && trigger.event === eventType
36
- })
37
+ automations = automations.filter(automation => {
38
+ const trigger = automation.definition.trigger
39
+ return trigger && trigger.event === eventType
40
+ })
37
41
 
38
42
  for (let automation of automations) {
39
43
  let automationDef = automation.definition
@@ -110,4 +114,34 @@ exports.externalTrigger = async function (
110
114
  }
111
115
  }
112
116
 
117
+ exports.rebootTrigger = async () => {
118
+ // reboot cron option is only available on the main thread at
119
+ // startup and only usable in self host
120
+ if (env.isInThread() || !env.SELF_HOSTED) {
121
+ return
122
+ }
123
+ // iterate through all production apps, find the reboot crons
124
+ // and trigger events for them
125
+ const appIds = await getAllApps({ dev: false, idsOnly: true })
126
+ for (let prodAppId of appIds) {
127
+ await doInAppContext(prodAppId, async () => {
128
+ let automations = await getAllAutomations()
129
+ let rebootEvents = []
130
+ for (let automation of automations) {
131
+ if (utils.isRebootTrigger(automation)) {
132
+ const job = {
133
+ automation,
134
+ event: {
135
+ appId: prodAppId,
136
+ timestamp: Date.now(),
137
+ },
138
+ }
139
+ rebootEvents.push(queue.add(job, JOB_OPTS))
140
+ }
141
+ }
142
+ await Promise.all(rebootEvents)
143
+ })
144
+ }
145
+ }
146
+
113
147
  exports.TRIGGER_DEFINITIONS = TRIGGER_DEFINITIONS