@budibase/server 1.3.18 → 1.3.19-alpha.0

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 (282) hide show
  1. package/__mocks__/mongodb.ts +1 -1
  2. package/builder/assets/{index.a70e216a.css → index.3850883f.css} +2 -2
  3. package/builder/assets/index.fdade753.js +995 -0
  4. package/builder/index.html +2 -4
  5. package/coverage/clover.xml +409 -361
  6. package/coverage/coverage-final.json +18 -18
  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 +1 -1
  11. package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
  12. package/coverage/lcov-report/api/controllers/component.js.html +68 -11
  13. package/coverage/lcov-report/api/controllers/datasource.js.html +73 -34
  14. package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
  15. package/coverage/lcov-report/api/controllers/index.html +38 -38
  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 +2 -2
  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 +207 -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 +2 -2
  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 +5 -5
  72. package/coverage/lcov-report/api/routes/utils/validators.js.html +9 -6
  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 +1 -1
  79. package/coverage/lcov-report/automations/index.js.html +1 -1
  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 +1 -1
  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 +30 -30
  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 +1 -1
  140. package/coverage/lcov-report/utilities/index.js.html +24 -24
  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 +1 -1
  146. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
  147. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
  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 +1 -1
  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 +725 -623
  157. package/dist/api/controllers/application.js +5 -4
  158. package/dist/api/controllers/component.js +14 -1
  159. package/dist/api/controllers/datasource.js +21 -8
  160. package/dist/api/controllers/integration.js +4 -4
  161. package/dist/api/controllers/plugin/file.js +24 -0
  162. package/dist/api/controllers/plugin/github.js +69 -0
  163. package/dist/api/controllers/plugin/index.js +197 -0
  164. package/dist/api/controllers/plugin/npm.js +58 -0
  165. package/dist/api/controllers/plugin/uploaders.js +11 -0
  166. package/dist/api/controllers/plugin/url.js +24 -0
  167. package/dist/api/controllers/plugin/utils.js +26 -0
  168. package/dist/api/controllers/row/external.js +1 -1
  169. package/dist/api/controllers/screen.js +52 -2
  170. package/dist/api/controllers/static/index.js +1 -0
  171. package/dist/api/controllers/static/templates/BudibaseApp.svelte +10 -0
  172. package/dist/api/controllers/static/templates/preview.hbs +5 -1
  173. package/dist/api/routes/application.js +3 -2
  174. package/dist/api/routes/index.js +2 -0
  175. package/dist/api/routes/plugin.js +39 -0
  176. package/dist/api/routes/public/tables.js +1 -1
  177. package/dist/api/routes/table.js +1 -1
  178. package/dist/api/routes/utils/validators.js +2 -1
  179. package/dist/app.js +11 -0
  180. package/dist/db/utils.js +10 -1
  181. package/dist/environment.js +1 -0
  182. package/dist/integrations/airtable.js +126 -129
  183. package/dist/integrations/arangodb.js +86 -89
  184. package/dist/integrations/base/query.js +2 -2
  185. package/dist/integrations/base/sqlTable.js +3 -1
  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 +99 -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 +289 -292
  199. package/dist/integrations/s3.js +61 -64
  200. package/dist/integrations/snowflake.js +83 -86
  201. package/dist/package.json +9 -6
  202. package/dist/tests/utilities/TestConfiguration.js +1 -1
  203. package/dist/threads/query.js +2 -2
  204. package/dist/tsconfig.build.tsbuildinfo +1 -1
  205. package/dist/utilities/fileSystem/index.js +110 -1
  206. package/dist/watch.js +77 -0
  207. package/dist/websocket.js +22 -0
  208. package/package.json +10 -7
  209. package/scripts/dev/manage.js +1 -0
  210. package/specs/openapi.json +3 -3
  211. package/specs/openapi.yaml +3 -3
  212. package/specs/resources/query.js +1 -1
  213. package/src/api/controllers/application.ts +1 -1
  214. package/src/api/controllers/component.js +21 -2
  215. package/src/api/controllers/datasource.js +21 -8
  216. package/src/api/controllers/integration.js +4 -4
  217. package/src/api/controllers/plugin/file.ts +15 -0
  218. package/src/api/controllers/plugin/github.ts +75 -0
  219. package/src/api/controllers/plugin/index.ts +212 -0
  220. package/src/api/controllers/plugin/npm.ts +56 -0
  221. package/src/api/controllers/plugin/uploaders.ts +4 -0
  222. package/src/api/controllers/plugin/url.ts +12 -0
  223. package/src/api/controllers/plugin/utils.ts +19 -0
  224. package/src/api/controllers/row/external.js +1 -1
  225. package/src/api/controllers/screen.js +66 -1
  226. package/src/api/controllers/static/index.ts +1 -0
  227. package/src/api/controllers/static/templates/BudibaseApp.svelte +10 -0
  228. package/src/api/controllers/static/templates/preview.hbs +5 -1
  229. package/src/api/routes/application.ts +13 -2
  230. package/src/api/routes/index.ts +2 -0
  231. package/src/api/routes/plugin.ts +14 -0
  232. package/src/api/routes/public/tables.ts +1 -1
  233. package/src/api/routes/table.js +1 -1
  234. package/src/api/routes/tests/query.spec.js +8 -25
  235. package/src/api/routes/utils/validators.js +2 -1
  236. package/src/app.ts +14 -0
  237. package/src/db/utils.js +12 -0
  238. package/src/definitions/openapi.ts +3 -3
  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/base/sqlTable.ts +3 -1
  244. package/src/integrations/couchdb.ts +83 -89
  245. package/src/integrations/dynamodb.ts +173 -175
  246. package/src/integrations/elasticsearch.ts +120 -122
  247. package/src/integrations/firebase.ts +157 -159
  248. package/src/integrations/googlesheets.ts +328 -338
  249. package/src/integrations/index.ts +64 -21
  250. package/src/integrations/microsoftSqlServer.ts +249 -251
  251. package/src/integrations/mongodb.ts +261 -258
  252. package/src/integrations/mysql.ts +229 -234
  253. package/src/integrations/oracle.ts +373 -380
  254. package/src/integrations/postgres.ts +264 -268
  255. package/src/integrations/redis.ts +128 -132
  256. package/src/integrations/rest.ts +324 -329
  257. package/src/integrations/s3.ts +68 -71
  258. package/src/integrations/snowflake.ts +80 -82
  259. package/src/integrations/tests/{airtable.spec.js → airtable.spec.ts} +21 -18
  260. package/src/integrations/tests/{arangodb.spec.js → arangodb.spec.ts} +12 -9
  261. package/src/integrations/tests/{couchdb.spec.js → couchdb.spec.ts} +33 -27
  262. package/src/integrations/tests/{dynamodb.spec.js → dynamodb.spec.ts} +45 -41
  263. package/src/integrations/tests/{elasticsearch.spec.js → elasticsearch.spec.ts} +22 -21
  264. package/src/integrations/tests/{firebase.spec.js → firebase.spec.ts} +33 -28
  265. package/src/integrations/tests/{microsoftSqlServer.spec.js → microsoftSqlServer.spec.ts} +12 -11
  266. package/src/integrations/tests/{mongo.spec.js → mongo.spec.ts} +46 -35
  267. package/src/integrations/tests/{mysql.spec.js → mysql.spec.ts} +17 -15
  268. package/src/integrations/tests/{oracle.spec.js → oracle.spec.ts} +19 -17
  269. package/src/integrations/tests/{postgres.spec.js → postgres.spec.ts} +19 -17
  270. package/src/integrations/tests/{redis.spec.js → redis.spec.ts} +27 -18
  271. package/src/integrations/tests/{rest.spec.js → rest.spec.ts} +22 -14
  272. package/src/integrations/tests/{s3.spec.js → s3.spec.ts} +10 -8
  273. package/src/integrations/tests/{sql.spec.js → sql.spec.ts} +228 -177
  274. package/src/module.d.ts +2 -0
  275. package/src/tests/utilities/TestConfiguration.js +1 -1
  276. package/src/threads/query.ts +2 -2
  277. package/src/utilities/fileSystem/index.js +129 -0
  278. package/src/watch.ts +38 -0
  279. package/src/websocket.ts +26 -0
  280. package/builder/assets/index.a934251d.js +0 -876
  281. package/builder/assets/vendor.5e1fe644.css +0 -1
  282. package/builder/assets/vendor.67dbd8fa.js +0 -248
@@ -0,0 +1,212 @@
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
+ import { ClientAppSocket } from "../../../websocket"
14
+
15
+ export async function getPlugins(type?: PluginType) {
16
+ const db = getGlobalDB()
17
+ const response = await db.allDocs(
18
+ getPluginParams(null, {
19
+ include_docs: true,
20
+ })
21
+ )
22
+ const plugins = response.rows.map((row: any) => row.doc)
23
+ if (type) {
24
+ return plugins.filter((plugin: any) => plugin.schema?.type === type)
25
+ } else {
26
+ return plugins
27
+ }
28
+ }
29
+
30
+ export async function upload(ctx: any) {
31
+ const plugins: FileType[] =
32
+ ctx.request.files.file.length > 1
33
+ ? Array.from(ctx.request.files.file)
34
+ : [ctx.request.files.file]
35
+ try {
36
+ let docs = []
37
+ // can do single or multiple plugins
38
+ for (let plugin of plugins) {
39
+ const doc = await processPlugin(plugin, PluginSource.FILE)
40
+ docs.push(doc)
41
+ }
42
+ ctx.body = {
43
+ message: "Plugin(s) uploaded successfully",
44
+ plugins: docs,
45
+ }
46
+ } catch (err: any) {
47
+ const errMsg = err?.message ? err?.message : err
48
+
49
+ ctx.throw(400, `Failed to import plugin: ${errMsg}`)
50
+ }
51
+ }
52
+
53
+ export async function create(ctx: any) {
54
+ const { source, url, headers, githubToken } = ctx.request.body
55
+
56
+ try {
57
+ let metadata
58
+ let directory
59
+ // Generating random name as a backup and needed for url
60
+ let name = "PLUGIN_" + Math.floor(100000 + Math.random() * 900000)
61
+
62
+ switch (source) {
63
+ case PluginSource.NPM:
64
+ const { metadata: metadataNpm, directory: directoryNpm } =
65
+ await npmUpload(url, name)
66
+ metadata = metadataNpm
67
+ directory = directoryNpm
68
+ break
69
+ case PluginSource.GITHUB:
70
+ const { metadata: metadataGithub, directory: directoryGithub } =
71
+ await githubUpload(url, name, githubToken)
72
+ metadata = metadataGithub
73
+ directory = directoryGithub
74
+ break
75
+ case PluginSource.URL:
76
+ const headersObj = headers || {}
77
+ const { metadata: metadataUrl, directory: directoryUrl } =
78
+ await urlUpload(url, name, headersObj)
79
+ metadata = metadataUrl
80
+ directory = directoryUrl
81
+ break
82
+ }
83
+
84
+ validate(metadata?.schema)
85
+
86
+ // Only allow components in cloud
87
+ if (!env.SELF_HOSTED && metadata?.schema?.type !== PluginType.COMPONENT) {
88
+ throw new Error(
89
+ "Only component plugins are supported outside of self-host"
90
+ )
91
+ }
92
+
93
+ const doc = await storePlugin(metadata, directory, source)
94
+
95
+ ctx.body = {
96
+ message: "Plugin uploaded successfully",
97
+ plugins: [doc],
98
+ }
99
+ } catch (err: any) {
100
+ const errMsg = err?.message ? err?.message : err
101
+
102
+ ctx.throw(400, `Failed to import plugin: ${errMsg}`)
103
+ }
104
+ ctx.status = 200
105
+ }
106
+
107
+ export async function fetch(ctx: any) {
108
+ ctx.body = await getPlugins()
109
+ }
110
+
111
+ export async function destroy(ctx: any) {
112
+ const db = getGlobalDB()
113
+ const { pluginId } = ctx.params
114
+
115
+ try {
116
+ const plugin = await db.get(pluginId)
117
+ const bucketPath = `${plugin.name}/`
118
+ await deleteFolder(ObjectStoreBuckets.PLUGINS, bucketPath)
119
+
120
+ await db.remove(pluginId, plugin._rev)
121
+ } catch (err: any) {
122
+ const errMsg = err?.message ? err?.message : err
123
+
124
+ ctx.throw(400, `Failed to delete plugin: ${errMsg}`)
125
+ }
126
+
127
+ ctx.message = `Plugin ${ctx.params.pluginId} deleted.`
128
+ ctx.status = 200
129
+ }
130
+
131
+ export async function storePlugin(
132
+ metadata: any,
133
+ directory: any,
134
+ source?: string
135
+ ) {
136
+ const db = getGlobalDB()
137
+ const version = metadata.package.version,
138
+ name = metadata.package.name,
139
+ description = metadata.package.description,
140
+ hash = metadata.schema.hash
141
+
142
+ // first open the tarball into tmp directory
143
+ const bucketPath = `${name}/`
144
+ const files = await uploadDirectory(
145
+ ObjectStoreBuckets.PLUGINS,
146
+ directory,
147
+ bucketPath
148
+ )
149
+ const jsFile = files.find((file: any) => file.name.endsWith(".js"))
150
+ if (!jsFile) {
151
+ throw new Error(`Plugin missing .js file.`)
152
+ }
153
+ // validate the JS for a datasource
154
+ if (metadata.schema.type === PluginType.DATASOURCE) {
155
+ const js = loadJSFile(directory, jsFile.name)
156
+ // TODO: this isn't safe - but we need full node environment
157
+ // in future we should do this in a thread for safety
158
+ try {
159
+ eval(js)
160
+ } catch (err: any) {
161
+ const message = err?.message ? err.message : JSON.stringify(err)
162
+ throw new Error(`JS invalid: ${message}`)
163
+ }
164
+ }
165
+ const jsFileName = jsFile.name
166
+ const pluginId = generatePluginID(name)
167
+
168
+ // overwrite existing docs entirely if they exist
169
+ let rev
170
+ try {
171
+ const existing = await db.get(pluginId)
172
+ rev = existing._rev
173
+ } catch (err) {
174
+ rev = undefined
175
+ }
176
+ let doc = {
177
+ _id: pluginId,
178
+ _rev: rev,
179
+ ...metadata,
180
+ name,
181
+ version,
182
+ hash,
183
+ description,
184
+ jsUrl: `${bucketPath}${jsFileName}`,
185
+ }
186
+
187
+ if (source) {
188
+ doc = {
189
+ ...doc,
190
+ source,
191
+ }
192
+ }
193
+
194
+ const response = await db.put(doc)
195
+ ClientAppSocket.emit("plugin-update", { name, hash })
196
+ return {
197
+ ...doc,
198
+ _rev: response.rev,
199
+ }
200
+ }
201
+
202
+ export async function processPlugin(plugin: FileType, source?: string) {
203
+ const { metadata, directory } = await fileUpload(plugin)
204
+ validate(metadata?.schema)
205
+
206
+ // Only allow components in cloud
207
+ if (!env.SELF_HOSTED && metadata?.schema?.type !== PluginType.COMPONENT) {
208
+ throw new Error("Only component plugins are supported outside of self-host")
209
+ }
210
+
211
+ return await storePlugin(metadata, directory, source)
212
+ }
@@ -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
+ }
@@ -53,7 +53,7 @@ exports.save = async ctx => {
53
53
  }
54
54
 
55
55
  exports.fetchView = async ctx => {
56
- // there are no views in external data sources, shouldn't ever be called
56
+ // there are no views in external datasources, shouldn't ever be called
57
57
  // for now just fetch
58
58
  const split = ctx.params.viewName.split("all_")
59
59
  ctx.params.tableId = split[1] ? split[1] : split[0]
@@ -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,48 @@ 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
+ hash: plugin.hash,
75
+ })
76
+ }
77
+ })
78
+
79
+ if (pluginAdded) {
80
+ await updateAppPackage({ usedPlugins }, ctx.appId)
81
+ }
82
+ }
83
+
35
84
  if (eventFn) {
36
85
  await eventFn(screen)
37
86
  }
@@ -40,6 +89,7 @@ exports.save = async ctx => {
40
89
  ...screen,
41
90
  _id: response.id,
42
91
  _rev: response.rev,
92
+ pluginAdded,
43
93
  }
44
94
  }
45
95
 
@@ -56,3 +106,18 @@ exports.destroy = async ctx => {
56
106
  }
57
107
  ctx.status = 200
58
108
  }
109
+
110
+ const findPlugins = (component, foundPlugins) => {
111
+ if (!component) {
112
+ return
113
+ }
114
+ if (component._component.startsWith("plugin")) {
115
+ if (!foundPlugins.includes(component._component)) {
116
+ foundPlugins.push(component._component)
117
+ }
118
+ }
119
+ if (!component._children || !component._children.length) {
120
+ return
121
+ }
122
+ component._children.forEach(child => findPlugins(child, foundPlugins))
123
+ }
@@ -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,9 @@
66
66
  customTheme,
67
67
  previewDevice,
68
68
  navigation,
69
- hiddenComponentIds
69
+ hiddenComponentIds,
70
+ usedPlugins,
71
+ location
70
72
  } = parsed
71
73
 
72
74
  // Set some flags so the app knows we're in the builder
@@ -81,6 +83,8 @@
81
83
  window["##BUDIBASE_PREVIEW_DEVICE##"] = previewDevice
82
84
  window["##BUDIBASE_PREVIEW_NAVIGATION##"] = navigation
83
85
  window["##BUDIBASE_HIDDEN_COMPONENT_IDS##"] = hiddenComponentIds
86
+ window["##BUDIBASE_USED_PLUGINS##"] = usedPlugins
87
+ window["##BUDIBASE_LOCATION##"] = location
84
88
 
85
89
  // Initialise app
86
90
  try {
@@ -2,16 +2,27 @@ import Router from "@koa/router"
2
2
  import * as controller from "../controllers/application"
3
3
  import authorized from "../../middleware/authorized"
4
4
  import { BUILDER } from "@budibase/backend-core/permissions"
5
+ import { applicationValidator } from "./utils/validators"
5
6
 
6
7
  const router = new Router()
7
8
 
8
9
  router
9
10
  .post("/api/applications/:appId/sync", authorized(BUILDER), controller.sync)
10
- .post("/api/applications", authorized(BUILDER), controller.create)
11
+ .post(
12
+ "/api/applications",
13
+ authorized(BUILDER),
14
+ applicationValidator(),
15
+ controller.create
16
+ )
11
17
  .get("/api/applications/:appId/definition", controller.fetchAppDefinition)
12
18
  .get("/api/applications", controller.fetch)
13
19
  .get("/api/applications/:appId/appPackage", controller.fetchAppPackage)
14
- .put("/api/applications/:appId", authorized(BUILDER), controller.update)
20
+ .put(
21
+ "/api/applications/:appId",
22
+ authorized(BUILDER),
23
+ applicationValidator(),
24
+ controller.update
25
+ )
15
26
  .post(
16
27
  "/api/applications/:appId/client/update",
17
28
  authorized(BUILDER),
@@ -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
@@ -26,7 +26,7 @@ const read = [],
26
26
  * responses:
27
27
  * 200:
28
28
  * description: Returns the created table, including the ID which has been generated for it. This can be
29
- * internal or external data sources.
29
+ * internal or external datasources.
30
30
  * content:
31
31
  * application/json:
32
32
  * schema:
@@ -55,7 +55,7 @@ router
55
55
  * @apiParam (Body) {string} [_rev] If updating an existing internal table then the revision must also be specified.
56
56
  * @apiParam (Body) {string} type] This should either be "internal" or "external" depending on the table type -
57
57
  * this will default to internal.
58
- * @apiParam (Body) {string} [sourceId] If creating an external table then this should be set to the data source ID. If
58
+ * @apiParam (Body) {string} [sourceId] If creating an external table then this should be set to the datasource ID. If
59
59
  * building an internal table this does not need to be set, although it will be returned as "bb_internal".
60
60
  * @apiParam (Body) {string} name The name of the table, this will be used in the UI. To rename the table simply
61
61
  * supply the table structure to this endpoint with the name changed.
@@ -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
 
@@ -10,6 +10,7 @@ const Joi = require("joi")
10
10
  const OPTIONAL_STRING = Joi.string().optional().allow(null).allow("")
11
11
  const OPTIONAL_NUMBER = Joi.number().optional().allow(null)
12
12
  const OPTIONAL_BOOLEAN = Joi.boolean().optional().allow(null)
13
+ const APP_NAME_REGEX = /^[\w\s]+$/
13
14
 
14
15
  exports.tableValidator = () => {
15
16
  // prettier-ignore
@@ -214,7 +215,7 @@ exports.applicationValidator = () => {
214
215
  return joiValidator.body(Joi.object({
215
216
  _id: OPTIONAL_STRING,
216
217
  _rev: OPTIONAL_STRING,
217
- name: Joi.string().required(),
218
+ name: Joi.string().pattern(new RegExp(APP_NAME_REGEX)).required().error(new Error('App name must be letters, numbers and spaces only')),
218
219
  url: OPTIONAL_STRING,
219
220
  template: Joi.object({
220
221
  templateString: OPTIONAL_STRING,
package/src/app.ts CHANGED
@@ -26,10 +26,13 @@ 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"
35
+ import { initialise as initialiseWebsockets } from "./websocket"
33
36
 
34
37
  const app = new Koa()
35
38
 
@@ -74,6 +77,7 @@ if (env.isProd()) {
74
77
 
75
78
  const server = http.createServer(app.callback())
76
79
  destroyable(server)
80
+ initialiseWebsockets(server)
77
81
 
78
82
  let shuttingDown = false,
79
83
  errCode = 0
@@ -139,6 +143,16 @@ module.exports = server.listen(env.PORT || 0, async () => {
139
143
  }
140
144
  }
141
145
 
146
+ // monitor plugin directory if required
147
+ if (
148
+ env.SELF_HOSTED &&
149
+ !env.MULTI_TENANCY &&
150
+ env.PLUGINS_DIR &&
151
+ fs.existsSync(env.PLUGINS_DIR)
152
+ ) {
153
+ watch()
154
+ }
155
+
142
156
  // check for version updates
143
157
  await installation.checkInstallVersion()
144
158
 
package/src/db/utils.js CHANGED
@@ -42,6 +42,7 @@ const DocumentType = {
42
42
  MEM_VIEW: "view",
43
43
  USER_FLAG: "flag",
44
44
  AUTOMATION_METADATA: "meta_au",
45
+ PLUGIN: "plg",
45
46
  }
46
47
 
47
48
  const InternalTables = {
@@ -370,6 +371,10 @@ exports.getMemoryViewParams = (otherProps = {}) => {
370
371
  return getDocParams(DocumentType.MEM_VIEW, null, otherProps)
371
372
  }
372
373
 
374
+ exports.generatePluginID = name => {
375
+ return `${DocumentType.PLUGIN}${SEPARATOR}${name}`
376
+ }
377
+
373
378
  /**
374
379
  * This can be used with the db.allDocs to get a list of IDs
375
380
  */
@@ -379,3 +384,10 @@ exports.getMultiIDParams = ids => {
379
384
  include_docs: true,
380
385
  }
381
386
  }
387
+
388
+ /**
389
+ * Gets parameters for retrieving automations, this is a utility function for the getDocParams function.
390
+ */
391
+ exports.getPluginParams = (pluginId = null, otherProps = {}) => {
392
+ return getDocParams(DocumentType.PLUGIN, pluginId, otherProps)
393
+ }