@budibase/server 1.0.46-alpha.2 → 1.0.46-alpha.6

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 (248) hide show
  1. package/builder/assets/{index.89280093.js → index.00221ff2.js} +244 -244
  2. package/builder/assets/{index.0df4f7ab.css → index.dd9e386f.css} +1 -1
  3. package/builder/assets/{vendor.10e2ad1b.js → vendor.3bd2f661.js} +1 -1
  4. package/builder/index.html +3 -3
  5. package/coverage/clover.xml +1802 -1639
  6. package/coverage/coverage-final.json +98 -94
  7. package/coverage/lcov-report/api/controllers/analytics.js.html +1 -1
  8. package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
  9. package/coverage/lcov-report/api/controllers/application.js.html +192 -129
  10. package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
  11. package/coverage/lcov-report/api/controllers/automation.js.html +21 -21
  12. package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
  13. package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
  14. package/coverage/lcov-report/api/controllers/component.js.html +4 -4
  15. package/coverage/lcov-report/api/controllers/datasource.js.html +72 -36
  16. package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
  17. package/coverage/lcov-report/api/controllers/index.html +27 -42
  18. package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
  19. package/coverage/lcov-report/api/controllers/layout.js.html +6 -6
  20. package/coverage/lcov-report/api/controllers/metadata.js.html +1 -1
  21. package/coverage/lcov-report/api/controllers/permission.js.html +15 -15
  22. package/coverage/lcov-report/api/controllers/query/index.html +15 -15
  23. package/coverage/lcov-report/api/controllers/query/index.js.html +61 -28
  24. package/coverage/lcov-report/api/controllers/query/validation.js.html +6 -6
  25. package/coverage/lcov-report/api/controllers/role.js.html +9 -9
  26. package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
  27. package/coverage/lcov-report/api/controllers/row/external.js.html +16 -16
  28. package/coverage/lcov-report/api/controllers/row/index.html +5 -5
  29. package/coverage/lcov-report/api/controllers/row/index.js.html +26 -26
  30. package/coverage/lcov-report/api/controllers/row/internal.js.html +44 -44
  31. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +8 -8
  32. package/coverage/lcov-report/api/controllers/row/utils.js.html +29 -38
  33. package/coverage/lcov-report/api/controllers/screen.js.html +7 -7
  34. package/coverage/lcov-report/api/controllers/script.js.html +4 -4
  35. package/coverage/lcov-report/api/controllers/table/external.js.html +12 -12
  36. package/coverage/lcov-report/api/controllers/table/index.html +16 -16
  37. package/coverage/lcov-report/api/controllers/table/index.js.html +25 -25
  38. package/coverage/lcov-report/api/controllers/table/internal.js.html +40 -34
  39. package/coverage/lcov-report/api/controllers/table/utils.js.html +76 -61
  40. package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
  41. package/coverage/lcov-report/api/controllers/user.js.html +20 -20
  42. package/coverage/lcov-report/api/controllers/view/exporters.js.html +4 -4
  43. package/coverage/lcov-report/api/controllers/view/index.html +1 -1
  44. package/coverage/lcov-report/api/controllers/view/index.js.html +14 -14
  45. package/coverage/lcov-report/api/controllers/view/utils.js.html +11 -11
  46. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +7 -7
  47. package/coverage/lcov-report/api/controllers/webhook.js.html +15 -15
  48. package/coverage/lcov-report/api/index.html +1 -1
  49. package/coverage/lcov-report/api/index.js.html +10 -10
  50. package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
  51. package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
  52. package/coverage/lcov-report/api/routes/application.js.html +1 -1
  53. package/coverage/lcov-report/api/routes/auth.js.html +1 -1
  54. package/coverage/lcov-report/api/routes/automation.js.html +1 -1
  55. package/coverage/lcov-report/api/routes/backup.js.html +1 -1
  56. package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
  57. package/coverage/lcov-report/api/routes/component.js.html +1 -1
  58. package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
  59. package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
  60. package/coverage/lcov-report/api/routes/dev.js.html +1 -1
  61. package/coverage/lcov-report/api/routes/index.html +7 -22
  62. package/coverage/lcov-report/api/routes/index.js.html +4 -10
  63. package/coverage/lcov-report/api/routes/integration.js.html +1 -1
  64. package/coverage/lcov-report/api/routes/layout.js.html +1 -1
  65. package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
  66. package/coverage/lcov-report/api/routes/permission.js.html +1 -1
  67. package/coverage/lcov-report/api/routes/query.js.html +1 -1
  68. package/coverage/lcov-report/api/routes/role.js.html +1 -1
  69. package/coverage/lcov-report/api/routes/routing.js.html +1 -1
  70. package/coverage/lcov-report/api/routes/row.js.html +1 -1
  71. package/coverage/lcov-report/api/routes/screen.js.html +1 -1
  72. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  73. package/coverage/lcov-report/api/routes/static.js.html +17 -2
  74. package/coverage/lcov-report/api/routes/table.js.html +1 -1
  75. package/coverage/lcov-report/api/routes/templates.js.html +1 -1
  76. package/coverage/lcov-report/api/routes/user.js.html +1 -1
  77. package/coverage/lcov-report/api/routes/view.js.html +1 -1
  78. package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
  79. package/coverage/lcov-report/automations/actions.js.html +24 -24
  80. package/coverage/lcov-report/automations/automationUtils.js.html +5 -5
  81. package/coverage/lcov-report/automations/bullboard.js.html +16 -16
  82. package/coverage/lcov-report/automations/index.html +1 -1
  83. package/coverage/lcov-report/automations/index.js.html +1 -1
  84. package/coverage/lcov-report/automations/steps/bash.js.html +6 -6
  85. package/coverage/lcov-report/automations/steps/createRow.js.html +13 -19
  86. package/coverage/lcov-report/automations/steps/delay.js.html +4 -4
  87. package/coverage/lcov-report/automations/steps/deleteRow.js.html +11 -20
  88. package/coverage/lcov-report/automations/steps/discord.js.html +7 -7
  89. package/coverage/lcov-report/automations/steps/executeQuery.js.html +6 -6
  90. package/coverage/lcov-report/automations/steps/executeScript.js.html +6 -6
  91. package/coverage/lcov-report/automations/steps/filter.js.html +7 -7
  92. package/coverage/lcov-report/automations/steps/index.html +13 -13
  93. package/coverage/lcov-report/automations/steps/integromat.js.html +5 -5
  94. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +8 -8
  95. package/coverage/lcov-report/automations/steps/queryRows.js.html +10 -10
  96. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +5 -5
  97. package/coverage/lcov-report/automations/steps/serverLog.js.html +3 -3
  98. package/coverage/lcov-report/automations/steps/slack.js.html +5 -5
  99. package/coverage/lcov-report/automations/steps/updateRow.js.html +6 -6
  100. package/coverage/lcov-report/automations/steps/utils.js.html +3 -3
  101. package/coverage/lcov-report/automations/steps/zapier.js.html +5 -5
  102. package/coverage/lcov-report/automations/triggerInfo/app.js.html +2 -2
  103. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +2 -2
  104. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  105. package/coverage/lcov-report/automations/triggerInfo/index.js.html +8 -8
  106. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +2 -2
  107. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +2 -2
  108. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +2 -2
  109. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +2 -2
  110. package/coverage/lcov-report/automations/triggers.js.html +18 -18
  111. package/coverage/lcov-report/automations/utils.js.html +32 -32
  112. package/coverage/lcov-report/constants/index.html +5 -5
  113. package/coverage/lcov-report/constants/index.js.html +36 -24
  114. package/coverage/lcov-report/constants/layouts.js.html +5 -5
  115. package/coverage/lcov-report/constants/screens.js.html +4 -4
  116. package/coverage/lcov-report/db/client.js.html +18 -18
  117. package/coverage/lcov-report/db/inMemoryView.js.html +7 -7
  118. package/coverage/lcov-report/db/index.html +15 -15
  119. package/coverage/lcov-report/db/index.js.html +3 -3
  120. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +18 -18
  121. package/coverage/lcov-report/db/linkedRows/index.html +1 -1
  122. package/coverage/lcov-report/db/linkedRows/index.js.html +31 -31
  123. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +15 -15
  124. package/coverage/lcov-report/db/newid.js.html +4 -4
  125. package/coverage/lcov-report/db/utils.js.html +85 -85
  126. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  127. package/coverage/lcov-report/events/index.html +1 -1
  128. package/coverage/lcov-report/events/index.js.html +5 -5
  129. package/coverage/lcov-report/events/utils.js.html +3 -3
  130. package/coverage/lcov-report/index.html +110 -80
  131. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  132. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  133. package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
  134. package/coverage/lcov-report/middleware/authorized.js.html +11 -11
  135. package/coverage/lcov-report/middleware/builder.js.html +8 -8
  136. package/coverage/lcov-report/middleware/currentapp.js.html +34 -61
  137. package/coverage/lcov-report/middleware/index.html +25 -25
  138. package/coverage/lcov-report/middleware/joi-validator.js.html +3 -3
  139. package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
  140. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  141. package/coverage/lcov-report/middleware/usageQuota.js.html +275 -41
  142. package/coverage/lcov-report/middleware/utils.js.html +2 -2
  143. package/coverage/lcov-report/migrations/usageQuotas/index.html +141 -0
  144. package/coverage/lcov-report/migrations/usageQuotas/index.js.html +152 -0
  145. package/coverage/lcov-report/{api/routes/hosting.js.html → migrations/usageQuotas/syncApps.js.html} +42 -27
  146. package/coverage/lcov-report/{api/controllers/hosting.js.html → migrations/usageQuotas/syncRows.js.html} +41 -44
  147. package/coverage/lcov-report/threads/automation.js.html +1 -1
  148. package/coverage/lcov-report/threads/index.html +15 -15
  149. package/coverage/lcov-report/threads/index.js.html +10 -10
  150. package/coverage/lcov-report/threads/query.js.html +23 -8
  151. package/coverage/lcov-report/threads/utils.js.html +14 -14
  152. package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
  153. package/coverage/lcov-report/utilities/budibaseDir.js.html +7 -7
  154. package/coverage/lcov-report/utilities/centralPath.js.html +4 -4
  155. package/coverage/lcov-report/utilities/csvParser.js.html +6 -6
  156. package/coverage/lcov-report/utilities/global.js.html +26 -26
  157. package/coverage/lcov-report/utilities/index.html +19 -19
  158. package/coverage/lcov-report/utilities/index.js.html +25 -25
  159. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +8 -8
  160. package/coverage/lcov-report/utilities/queue/index.html +1 -1
  161. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  162. package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
  163. package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
  164. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +53 -53
  165. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +10 -10
  166. package/coverage/lcov-report/utilities/scriptRunner.js.html +5 -5
  167. package/coverage/lcov-report/utilities/security.js.html +8 -8
  168. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  169. package/coverage/lcov-report/utilities/usageQuota/index.html +141 -0
  170. package/coverage/lcov-report/utilities/usageQuota/index.js.html +356 -0
  171. package/coverage/lcov-report/utilities/usageQuota/rows.js.html +302 -0
  172. package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +134 -0
  173. package/coverage/lcov-report/utilities/users.js.html +5 -5
  174. package/coverage/lcov-report/utilities/workerRequests.js.html +39 -108
  175. package/coverage/lcov.info +2644 -2356
  176. package/dist/api/controllers/application.js +45 -24
  177. package/dist/api/controllers/datasource.js +11 -0
  178. package/dist/api/controllers/query/index.js +11 -3
  179. package/dist/api/controllers/row/utils.js +1 -2
  180. package/dist/api/controllers/static/index.js +59 -10
  181. package/dist/api/controllers/table/internal.js +2 -0
  182. package/dist/api/controllers/table/utils.js +5 -0
  183. package/dist/api/routes/index.js +0 -2
  184. package/dist/api/routes/static.js +2 -1
  185. package/dist/automations/steps/createRow.js +2 -4
  186. package/dist/automations/steps/deleteRow.js +1 -4
  187. package/dist/constants/index.js +3 -0
  188. package/dist/definitions/datasource.js +1 -0
  189. package/dist/environment.js +1 -0
  190. package/dist/integrations/googlesheets.js +340 -0
  191. package/dist/integrations/index.js +3 -0
  192. package/dist/integrations/utils.js +6 -3
  193. package/dist/middleware/currentapp.js +0 -9
  194. package/dist/middleware/usageQuota.js +82 -13
  195. package/dist/migrations/usageQuotas/index.js +24 -0
  196. package/dist/migrations/usageQuotas/syncApps.js +26 -0
  197. package/dist/migrations/usageQuotas/syncRows.js +29 -0
  198. package/dist/package.json +8 -5
  199. package/dist/tests/utilities/TestConfiguration.js +3 -1
  200. package/dist/threads/query.js +4 -0
  201. package/dist/tsconfig.tsbuildinfo +1 -1
  202. package/dist/utilities/{usageQuota.js → usageQuota/index.js} +44 -31
  203. package/dist/utilities/usageQuota/rows.js +77 -0
  204. package/dist/utilities/usageQuota/usageQuoteReset.js +16 -0
  205. package/dist/utilities/workerRequests.js +0 -20
  206. package/nodemon.json +1 -1
  207. package/package.json +9 -6
  208. package/src/api/controllers/application.js +48 -27
  209. package/src/api/controllers/datasource.js +12 -0
  210. package/src/api/controllers/query/index.js +14 -3
  211. package/src/api/controllers/row/utils.js +1 -4
  212. package/src/api/controllers/static/index.js +63 -10
  213. package/src/api/controllers/table/internal.js +2 -0
  214. package/src/api/controllers/table/utils.js +5 -0
  215. package/src/api/routes/index.js +0 -2
  216. package/src/api/routes/static.js +5 -0
  217. package/src/api/routes/tests/application.spec.js +2 -2
  218. package/src/api/routes/tests/static.spec.js +98 -0
  219. package/src/automations/steps/createRow.js +2 -4
  220. package/src/automations/steps/deleteRow.js +1 -4
  221. package/src/constants/index.js +4 -0
  222. package/src/definitions/datasource.ts +3 -0
  223. package/src/environment.js +1 -0
  224. package/src/integrations/googlesheets.ts +353 -0
  225. package/src/integrations/index.ts +3 -0
  226. package/src/integrations/utils.ts +18 -5
  227. package/src/middleware/currentapp.js +0 -9
  228. package/src/middleware/tests/usageQuota.spec.js +18 -24
  229. package/src/middleware/usageQuota.js +93 -15
  230. package/src/migrations/tests/usageQuotas/index.spec.js +27 -0
  231. package/src/migrations/tests/usageQuotas/syncApps.spec.js +37 -0
  232. package/src/migrations/tests/usageQuotas/syncRows.spec.js +43 -0
  233. package/src/migrations/usageQuotas/index.js +24 -0
  234. package/src/migrations/usageQuotas/syncApps.js +18 -0
  235. package/src/migrations/usageQuotas/syncRows.js +21 -0
  236. package/src/tests/utilities/TestConfiguration.js +3 -1
  237. package/src/threads/query.js +5 -0
  238. package/src/utilities/tests/usageQuota/usageQuota.spec.js +72 -0
  239. package/src/utilities/usageQuota/index.js +92 -0
  240. package/src/utilities/usageQuota/rows.js +74 -0
  241. package/src/utilities/usageQuota/usageQuoteReset.js +18 -0
  242. package/src/utilities/workerRequests.js +0 -23
  243. package/dist/api/controllers/hosting.js +0 -30
  244. package/dist/api/routes/hosting.js +0 -11
  245. package/src/api/controllers/hosting.js +0 -22
  246. package/src/api/routes/hosting.js +0 -13
  247. package/src/api/routes/tests/hosting.spec.js +0 -36
  248. package/src/utilities/usageQuota.js +0 -73
@@ -46,5 +46,10 @@ router
46
46
  )
47
47
  // TODO: this likely needs to be secured in some way
48
48
  .get("/:appId/:path*", controller.serveApp)
49
+ .post(
50
+ "/api/attachments/:datasourceId/url",
51
+ authorized(PermissionTypes.TABLE, PermissionLevels.READ),
52
+ controller.getSignedUploadURL
53
+ )
49
54
 
50
55
  module.exports = router
@@ -53,8 +53,8 @@ describe("/applications", () => {
53
53
 
54
54
  describe("fetch", () => {
55
55
  it("lists all applications", async () => {
56
- await config.createApp(request, "app1")
57
- await config.createApp(request, "app2")
56
+ await config.createApp("app1")
57
+ await config.createApp("app2")
58
58
 
59
59
  const res = await request
60
60
  .get(`/api/applications?status=${AppStatus.DEV}`)
@@ -0,0 +1,98 @@
1
+ jest.mock("node-fetch")
2
+ jest.mock("aws-sdk", () => ({
3
+ config: {
4
+ update: jest.fn(),
5
+ },
6
+ DynamoDB: {
7
+ DocumentClient: jest.fn(),
8
+ },
9
+ S3: jest.fn(() => ({
10
+ getSignedUrl: jest.fn(() => {
11
+ return "my-url"
12
+ }),
13
+ })),
14
+ }))
15
+
16
+ const setup = require("./utilities")
17
+
18
+ describe("/attachments", () => {
19
+ let request = setup.getRequest()
20
+ let config = setup.getConfig()
21
+ let app
22
+
23
+ afterAll(setup.afterAll)
24
+
25
+ beforeEach(async () => {
26
+ app = await config.init()
27
+ })
28
+
29
+ describe("generateSignedUrls", () => {
30
+ let datasource
31
+
32
+ beforeEach(async () => {
33
+ datasource = await config.createDatasource({
34
+ datasource: {
35
+ type: "datasource",
36
+ name: "Test",
37
+ source: "S3",
38
+ config: {},
39
+ },
40
+ })
41
+ })
42
+
43
+ it("should be able to generate a signed upload URL", async () => {
44
+ const bucket = "foo"
45
+ const key = "bar"
46
+ const res = await request
47
+ .post(`/api/attachments/${datasource._id}/url`)
48
+ .send({ bucket, key })
49
+ .set(config.defaultHeaders())
50
+ .expect("Content-Type", /json/)
51
+ .expect(200)
52
+ expect(res.body.signedUrl).toEqual("my-url")
53
+ expect(res.body.publicUrl).toEqual(
54
+ `https://${bucket}.s3.eu-west-1.amazonaws.com/${key}`
55
+ )
56
+ })
57
+
58
+ it("should handle an invalid datasource ID", async () => {
59
+ const res = await request
60
+ .post(`/api/attachments/foo/url`)
61
+ .send({
62
+ bucket: "foo",
63
+ key: "bar",
64
+ })
65
+ .set(config.defaultHeaders())
66
+ .expect("Content-Type", /json/)
67
+ .expect(400)
68
+ expect(res.body.message).toEqual(
69
+ "The specified datasource could not be found"
70
+ )
71
+ })
72
+
73
+ it("should require a bucket parameter", async () => {
74
+ const res = await request
75
+ .post(`/api/attachments/${datasource._id}/url`)
76
+ .send({
77
+ bucket: undefined,
78
+ key: "bar",
79
+ })
80
+ .set(config.defaultHeaders())
81
+ .expect("Content-Type", /json/)
82
+ .expect(400)
83
+ expect(res.body.message).toEqual("bucket and key values are required")
84
+ })
85
+
86
+ it("should require a key parameter", async () => {
87
+ const res = await request
88
+ .post(`/api/attachments/${datasource._id}/url`)
89
+ .send({
90
+ bucket: "foo",
91
+ })
92
+ .set(config.defaultHeaders())
93
+ .expect("Content-Type", /json/)
94
+ .expect(400)
95
+ expect(res.body.message).toEqual("bucket and key values are required")
96
+ })
97
+ })
98
+ })
@@ -1,6 +1,5 @@
1
1
  const rowController = require("../../api/controllers/row")
2
2
  const automationUtils = require("../automationUtils")
3
- const env = require("../../environment")
4
3
  const usage = require("../../utilities/usageQuota")
5
4
  const { buildCtx } = require("./utils")
6
5
 
@@ -83,10 +82,9 @@ exports.run = async function ({ inputs, appId, emitter }) {
83
82
  inputs.row.tableId,
84
83
  inputs.row
85
84
  )
86
- if (env.USE_QUOTAS) {
87
- await usage.update(usage.Properties.ROW, 1)
88
- }
85
+ await usage.update(usage.Properties.ROW, 1, { dryRun: true })
89
86
  await rowController.save(ctx)
87
+ await usage.update(usage.Properties.ROW, 1)
90
88
  return {
91
89
  row: inputs.row,
92
90
  response: ctx.body,
@@ -1,5 +1,4 @@
1
1
  const rowController = require("../../api/controllers/row")
2
- const env = require("../../environment")
3
2
  const usage = require("../../utilities/usageQuota")
4
3
  const { buildCtx } = require("./utils")
5
4
  const automationUtils = require("../automationUtils")
@@ -74,9 +73,7 @@ exports.run = async function ({ inputs, appId, emitter }) {
74
73
  })
75
74
 
76
75
  try {
77
- if (env.isProd()) {
78
- await usage.update(usage.Properties.ROW, -1)
79
- }
76
+ await usage.update(usage.Properties.ROW, -1)
80
77
  await rowController.destroy(ctx)
81
78
  return {
82
79
  response: ctx.body,
@@ -75,6 +75,10 @@ exports.DataSourceOperation = {
75
75
  DELETE_TABLE: "DELETE_TABLE",
76
76
  }
77
77
 
78
+ exports.DatasourceAuthTypes = {
79
+ GOOGLE: "google",
80
+ }
81
+
78
82
  exports.SortDirection = {
79
83
  ASCENDING: "ASCENDING",
80
84
  DESCENDING: "DESCENDING",
@@ -47,6 +47,7 @@ export enum SourceNames {
47
47
  ARANGODB = "ARANGODB",
48
48
  REST = "REST",
49
49
  ORACLE = "ORACLE",
50
+ GOOGLE_SHEETS = "GOOGLE_SHEETS",
50
51
  }
51
52
 
52
53
  export enum IncludeRelationships {
@@ -86,6 +87,8 @@ export interface ExtraQueryConfig {
86
87
  export interface Integration {
87
88
  docs: string
88
89
  plus?: boolean
90
+ auth?: { type: string }
91
+ relationships?: boolean
89
92
  description: string
90
93
  friendlyName: string
91
94
  datasource: {}
@@ -38,6 +38,7 @@ module.exports = {
38
38
  MINIO_ACCESS_KEY: process.env.MINIO_ACCESS_KEY,
39
39
  MINIO_SECRET_KEY: process.env.MINIO_SECRET_KEY,
40
40
  USE_QUOTAS: process.env.USE_QUOTAS,
41
+ EXCLUDE_QUOTAS_TENANTS: process.env.EXCLUDE_QUOTAS_TENANTS,
41
42
  REDIS_URL: process.env.REDIS_URL,
42
43
  REDIS_PASSWORD: process.env.REDIS_PASSWORD,
43
44
  INTERNAL_API_KEY: process.env.INTERNAL_API_KEY,
@@ -0,0 +1,353 @@
1
+ import {
2
+ DatasourceFieldTypes,
3
+ Integration,
4
+ QueryJson,
5
+ QueryTypes,
6
+ } from "../definitions/datasource"
7
+ import { OAuth2Client } from "google-auth-library"
8
+ import { DatasourcePlus } from "./base/datasourcePlus"
9
+ import { Row, Table, TableSchema } from "../definitions/common"
10
+ import { buildExternalTableId } from "./utils"
11
+ import { DataSourceOperation, FieldTypes } from "../constants"
12
+ import { GoogleSpreadsheet } from "google-spreadsheet"
13
+ import { table } from "console"
14
+
15
+ module GoogleSheetsModule {
16
+ const { getGlobalDB } = require("@budibase/backend-core/tenancy")
17
+ const { getScopedConfig } = require("@budibase/backend-core/db")
18
+ const { Configs } = require("@budibase/backend-core/constants")
19
+
20
+ interface GoogleSheetsConfig {
21
+ spreadsheetId: string
22
+ auth: OAuthClientConfig
23
+ }
24
+
25
+ interface OAuthClientConfig {
26
+ appId: string
27
+ accessToken: string
28
+ refreshToken: string
29
+ }
30
+
31
+ const SCHEMA: Integration = {
32
+ plus: true,
33
+ auth: {
34
+ type: "google",
35
+ },
36
+ relationships: false,
37
+ docs: "https://developers.google.com/sheets/api/quickstart/nodejs",
38
+ description:
39
+ "Create and collaborate on online spreadsheets in real-time and from any device. ",
40
+ friendlyName: "Google Sheets",
41
+ datasource: {
42
+ spreadsheetId: {
43
+ type: DatasourceFieldTypes.STRING,
44
+ required: true,
45
+ },
46
+ },
47
+ query: {
48
+ create: {
49
+ type: QueryTypes.FIELDS,
50
+ fields: {
51
+ sheet: {
52
+ type: DatasourceFieldTypes.STRING,
53
+ required: true,
54
+ },
55
+ row: {
56
+ type: QueryTypes.JSON,
57
+ required: true,
58
+ },
59
+ },
60
+ },
61
+ read: {
62
+ type: QueryTypes.FIELDS,
63
+ fields: {
64
+ sheet: {
65
+ type: DatasourceFieldTypes.STRING,
66
+ required: true,
67
+ },
68
+ },
69
+ },
70
+ update: {
71
+ type: QueryTypes.FIELDS,
72
+ fields: {
73
+ sheet: {
74
+ type: DatasourceFieldTypes.STRING,
75
+ required: true,
76
+ },
77
+ rowIndex: {
78
+ type: DatasourceFieldTypes.STRING,
79
+ required: true,
80
+ },
81
+ row: {
82
+ type: QueryTypes.JSON,
83
+ required: true,
84
+ },
85
+ },
86
+ },
87
+ delete: {
88
+ type: QueryTypes.FIELDS,
89
+ fields: {
90
+ sheet: {
91
+ type: DatasourceFieldTypes.STRING,
92
+ required: true,
93
+ },
94
+ rowIndex: {
95
+ type: DatasourceFieldTypes.NUMBER,
96
+ required: true,
97
+ },
98
+ },
99
+ },
100
+ },
101
+ }
102
+
103
+ class GoogleSheetsIntegration implements DatasourcePlus {
104
+ private readonly config: GoogleSheetsConfig
105
+ private client: any
106
+ public tables: Record<string, Table> = {}
107
+ public schemaErrors: Record<string, string> = {}
108
+
109
+ constructor(config: GoogleSheetsConfig) {
110
+ this.config = config
111
+ const spreadsheetId = this.cleanSpreadsheetUrl(this.config.spreadsheetId)
112
+ this.client = new GoogleSpreadsheet(spreadsheetId)
113
+ }
114
+
115
+ /**
116
+ * Pull the spreadsheet ID out from a valid google sheets URL
117
+ * @param spreadsheetId - the URL or standard spreadsheetId of the google sheet
118
+ * @returns spreadsheet Id of the google sheet
119
+ */
120
+ cleanSpreadsheetUrl(spreadsheetId: string) {
121
+ if (!spreadsheetId) {
122
+ throw new Error(
123
+ "You must set a spreadsheet ID in your configuration to fetch tables."
124
+ )
125
+ }
126
+ const parts = spreadsheetId.split("/")
127
+ return parts.length > 5 ? parts[5] : spreadsheetId
128
+ }
129
+
130
+ async connect() {
131
+ try {
132
+ // Initialise oAuth client
133
+ const db = getGlobalDB()
134
+ const googleConfig = await getScopedConfig(db, {
135
+ type: Configs.GOOGLE,
136
+ })
137
+ const oauthClient = new OAuth2Client({
138
+ clientId: googleConfig.clientID,
139
+ clientSecret: googleConfig.clientSecret,
140
+ })
141
+ oauthClient.credentials.access_token = this.config.auth.accessToken
142
+ oauthClient.credentials.refresh_token = this.config.auth.refreshToken
143
+ this.client.useOAuth2Client(oauthClient)
144
+ await this.client.loadInfo()
145
+ } catch (err) {
146
+ console.error("Error connecting to google sheets", err)
147
+ throw err
148
+ }
149
+ }
150
+
151
+ async buildSchema(datasourceId: string) {
152
+ await this.connect()
153
+ const sheets = await this.client.sheetsByIndex
154
+ const tables: Record<string, Table> = {}
155
+ for (let sheet of sheets) {
156
+ // must fetch rows to determine schema
157
+ await sheet.getRows()
158
+ // build schema
159
+ const schema: TableSchema = {}
160
+
161
+ // build schema from headers
162
+ for (let header of sheet.headerValues) {
163
+ schema[header] = {
164
+ name: header,
165
+ type: FieldTypes.STRING,
166
+ }
167
+ }
168
+
169
+ // create tables
170
+ tables[sheet.title] = {
171
+ _id: buildExternalTableId(datasourceId, sheet.title),
172
+ name: sheet.title,
173
+ primary: ["rowNumber"],
174
+ schema,
175
+ }
176
+ }
177
+
178
+ this.tables = tables
179
+ }
180
+
181
+ async query(json: QueryJson) {
182
+ const sheet = json.endpoint.entityId
183
+
184
+ const handlers = {
185
+ [DataSourceOperation.CREATE]: () =>
186
+ this.create({ sheet, row: json.body }),
187
+ [DataSourceOperation.READ]: () => this.read({ sheet }),
188
+ [DataSourceOperation.UPDATE]: () =>
189
+ this.update({
190
+ // exclude the header row and zero index
191
+ rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
192
+ sheet,
193
+ row: json.body,
194
+ }),
195
+ [DataSourceOperation.DELETE]: () =>
196
+ this.delete({
197
+ // exclude the header row and zero index
198
+ rowIndex: json.extra?.idFilter?.equal?.rowNumber - 2,
199
+ sheet,
200
+ }),
201
+ [DataSourceOperation.CREATE_TABLE]: () =>
202
+ this.createTable(json?.table?.name),
203
+ [DataSourceOperation.UPDATE_TABLE]: () => this.updateTable(json.table),
204
+ [DataSourceOperation.DELETE_TABLE]: () =>
205
+ this.deleteTable(json?.table?.name),
206
+ }
207
+
208
+ const internalQueryMethod = handlers[json.endpoint.operation]
209
+
210
+ return await internalQueryMethod()
211
+ }
212
+
213
+ buildRowObject(headers: string[], values: string[], rowNumber: number) {
214
+ const rowObject: { rowNumber: number; [key: string]: any } = { rowNumber }
215
+ for (let i = 0; i < headers.length; i++) {
216
+ rowObject._id = rowNumber
217
+ rowObject[headers[i]] = values[i]
218
+ }
219
+ return rowObject
220
+ }
221
+
222
+ async createTable(name?: string) {
223
+ try {
224
+ await this.connect()
225
+ const sheet = await this.client.addSheet({ title: name })
226
+ return sheet
227
+ } catch (err) {
228
+ console.error("Error creating new table in google sheets", err)
229
+ throw err
230
+ }
231
+ }
232
+
233
+ async updateTable(table?: any) {
234
+ try {
235
+ await this.connect()
236
+ const sheet = await this.client.sheetsByTitle[table.name]
237
+ await sheet.loadHeaderRow()
238
+
239
+ if (table._rename) {
240
+ const headers = []
241
+ for (let header of sheet.headerValues) {
242
+ if (header === table._rename.old) {
243
+ headers.push(table._rename.updated)
244
+ } else {
245
+ headers.push(header)
246
+ }
247
+ }
248
+ await sheet.setHeaderRow(headers)
249
+ } else {
250
+ let newField = Object.keys(table.schema).find(
251
+ key => !sheet.headerValues.includes(key)
252
+ )
253
+ await sheet.setHeaderRow([...sheet.headerValues, newField])
254
+ }
255
+ } catch (err) {
256
+ console.error("Error updating table in google sheets", err)
257
+ throw err
258
+ }
259
+ }
260
+
261
+ async deleteTable(sheet: any) {
262
+ try {
263
+ await this.connect()
264
+ const sheetToDelete = await this.client.sheetsByTitle[sheet]
265
+ return await sheetToDelete.delete()
266
+ } catch (err) {
267
+ console.error("Error deleting table in google sheets", err)
268
+ throw err
269
+ }
270
+ }
271
+
272
+ async create(query: { sheet: string; row: any }) {
273
+ try {
274
+ await this.connect()
275
+ const sheet = await this.client.sheetsByTitle[query.sheet]
276
+ const rowToInsert =
277
+ typeof query.row === "string" ? JSON.parse(query.row) : query.row
278
+ const row = await sheet.addRow(rowToInsert)
279
+ return [
280
+ this.buildRowObject(sheet.headerValues, row._rawData, row._rowNumber),
281
+ ]
282
+ } catch (err) {
283
+ console.error("Error writing to google sheets", err)
284
+ throw err
285
+ }
286
+ }
287
+
288
+ async read(query: { sheet: string }) {
289
+ try {
290
+ await this.connect()
291
+ const sheet = await this.client.sheetsByTitle[query.sheet]
292
+ const rows = await sheet.getRows()
293
+ const headerValues = sheet.headerValues
294
+ const response = []
295
+ for (let row of rows) {
296
+ response.push(
297
+ this.buildRowObject(headerValues, row._rawData, row._rowNumber)
298
+ )
299
+ }
300
+ return response
301
+ } catch (err) {
302
+ console.error("Error reading from google sheets", err)
303
+ throw err
304
+ }
305
+ }
306
+
307
+ async update(query: { sheet: string; rowIndex: number; row: any }) {
308
+ try {
309
+ await this.connect()
310
+ const sheet = await this.client.sheetsByTitle[query.sheet]
311
+ const rows = await sheet.getRows()
312
+ const row = rows[query.rowIndex]
313
+ if (row) {
314
+ const updateValues = query.row
315
+ for (let key in updateValues) {
316
+ row[key] = updateValues[key]
317
+ }
318
+ await row.save()
319
+ return [
320
+ this.buildRowObject(
321
+ sheet.headerValues,
322
+ row._rawData,
323
+ row._rowNumber
324
+ ),
325
+ ]
326
+ } else {
327
+ throw new Error("Row does not exist.")
328
+ }
329
+ } catch (err) {
330
+ console.error("Error reading from google sheets", err)
331
+ throw err
332
+ }
333
+ }
334
+
335
+ async delete(query: { sheet: string; rowIndex: number }) {
336
+ await this.connect()
337
+ const sheet = await this.client.sheetsByTitle[query.sheet]
338
+ const rows = await sheet.getRows()
339
+ const row = rows[query.rowIndex]
340
+ if (row) {
341
+ await row.delete()
342
+ return [{ deleted: query.rowIndex }]
343
+ } else {
344
+ throw new Error("Row does not exist.")
345
+ }
346
+ }
347
+ }
348
+
349
+ module.exports = {
350
+ schema: SCHEMA,
351
+ integration: GoogleSheetsIntegration,
352
+ }
353
+ }
@@ -9,6 +9,7 @@ const airtable = require("./airtable")
9
9
  const mysql = require("./mysql")
10
10
  const arangodb = require("./arangodb")
11
11
  const rest = require("./rest")
12
+ const googlesheets = require("./googlesheets")
12
13
  const { SourceNames } = require("../definitions/datasource")
13
14
 
14
15
  const DEFINITIONS = {
@@ -23,6 +24,7 @@ const DEFINITIONS = {
23
24
  [SourceNames.MYSQL]: mysql.schema,
24
25
  [SourceNames.ARANGODB]: arangodb.schema,
25
26
  [SourceNames.REST]: rest.schema,
27
+ [SourceNames.GOOGLE_SHEETS]: googlesheets.schema,
26
28
  }
27
29
 
28
30
  const INTEGRATIONS = {
@@ -37,6 +39,7 @@ const INTEGRATIONS = {
37
39
  [SourceNames.MYSQL]: mysql.integration,
38
40
  [SourceNames.ARANGODB]: arangodb.integration,
39
41
  [SourceNames.REST]: rest.integration,
42
+ [SourceNames.GOOGLE_SHEETS]: googlesheets.integration,
40
43
  }
41
44
 
42
45
  // optionally add oracle integration if the oracle binary can be installed
@@ -153,8 +153,15 @@ export function isIsoDateString(str: string) {
153
153
  * @param column The column to check, to see if it is a valid relationship.
154
154
  * @param tableIds The IDs of the tables which currently exist.
155
155
  */
156
- function shouldCopyRelationship(column: { type: string, tableId?: string }, tableIds: [string]) {
157
- return column.type === FieldTypes.LINK && column.tableId && tableIds.includes(column.tableId)
156
+ function shouldCopyRelationship(
157
+ column: { type: string; tableId?: string },
158
+ tableIds: [string]
159
+ ) {
160
+ return (
161
+ column.type === FieldTypes.LINK &&
162
+ column.tableId &&
163
+ tableIds.includes(column.tableId)
164
+ )
158
165
  }
159
166
 
160
167
  /**
@@ -165,9 +172,15 @@ function shouldCopyRelationship(column: { type: string, tableId?: string }, tabl
165
172
  * @param column The column to check for options or boolean type.
166
173
  * @param fetchedColumn The fetched column to check for the type in the external database.
167
174
  */
168
- function shouldCopySpecialColumn(column: { type: string }, fetchedColumn: { type: string } | undefined) {
169
- return column.type === FieldTypes.OPTIONS ||
170
- ((!fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER) && column.type === FieldTypes.BOOLEAN)
175
+ function shouldCopySpecialColumn(
176
+ column: { type: string },
177
+ fetchedColumn: { type: string } | undefined
178
+ ) {
179
+ return (
180
+ column.type === FieldTypes.OPTIONS ||
181
+ ((!fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER) &&
182
+ column.type === FieldTypes.BOOLEAN)
183
+ )
171
184
  }
172
185
 
173
186
  /**
@@ -47,15 +47,6 @@ module.exports = async (ctx, next) => {
47
47
  (!ctx.user || !ctx.user.builder || !ctx.user.builder.global)
48
48
  ) {
49
49
  clearCookie(ctx, Cookies.CurrentApp)
50
- // have to set the return url on the server side as client side is not available
51
- setCookie(ctx, ctx.url, Cookies.RETURN_URL, {
52
- // don't sign so the browser can easily read
53
- sign: false,
54
- // use the request domain to match how ui handles the return url cookie.
55
- // it's important we don't use the shared domain here as the builder
56
- // can't delete from it without awareness of the domain.
57
- requestDomain: true,
58
- })
59
50
  return ctx.redirect("/")
60
51
  }
61
52
 
@@ -1,11 +1,5 @@
1
1
  jest.mock("../../db")
2
2
  jest.mock("../../utilities/usageQuota")
3
- jest.mock("../../environment", () => ({
4
- isTest: () => true,
5
- isProd: () => false,
6
- isDev: () => true,
7
- _set: () => {},
8
- }))
9
3
  jest.mock("@budibase/backend-core/tenancy", () => ({
10
4
  getTenantId: () => "testing123"
11
5
  }))
@@ -29,9 +23,10 @@ class TestConfiguration {
29
23
  },
30
24
  req: {
31
25
  method: "POST",
32
- url: "/rows"
26
+ url: "/applications"
33
27
  }
34
28
  }
29
+ usageQuota.useQuotas = () => true
35
30
  }
36
31
 
37
32
  executeMiddleware() {
@@ -113,7 +108,6 @@ describe("usageQuota middleware", () => {
113
108
 
114
109
  it("calculates and persists the correct usage quota for the relevant action", async () => {
115
110
  config.setUrl("/rows")
116
- config.setProd(true)
117
111
 
118
112
  await config.executeMiddleware()
119
113
 
@@ -121,20 +115,20 @@ describe("usageQuota middleware", () => {
121
115
  expect(config.next).toHaveBeenCalled()
122
116
  })
123
117
 
124
- it("calculates the correct file size from a file upload call and adds it to quota", async () => {
125
- config.setUrl("/upload")
126
- config.setProd(true)
127
- config.setFiles([
128
- {
129
- size: 100
130
- },
131
- {
132
- size: 10000
133
- },
134
- ])
135
- await config.executeMiddleware()
136
-
137
- expect(usageQuota.update).toHaveBeenCalledWith("storage", 10100)
138
- expect(config.next).toHaveBeenCalled()
139
- })
118
+ // it("calculates the correct file size from a file upload call and adds it to quota", async () => {
119
+ // config.setUrl("/upload")
120
+ // config.setProd(true)
121
+ // config.setFiles([
122
+ // {
123
+ // size: 100
124
+ // },
125
+ // {
126
+ // size: 10000
127
+ // },
128
+ // ])
129
+ // await config.executeMiddleware()
130
+
131
+ // expect(usageQuota.update).toHaveBeenCalledWith("storage", 10100)
132
+ // expect(config.next).toHaveBeenCalled()
133
+ // })
140
134
  })