@budibase/server 1.0.48 → 1.0.49-alpha.3

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 (289) hide show
  1. package/builder/assets/{index.e083dcaf.js → index.21e57eb4.js} +234 -231
  2. package/builder/assets/index.83c0944f.css +1 -0
  3. package/builder/assets/{vendor.f9faf6ab.js → vendor.d526bfe8.js} +12 -12
  4. package/builder/index.html +3 -3
  5. package/coverage/clover.xml +2338 -2028
  6. package/coverage/coverage-final.json +100 -95
  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 +210 -126
  10. package/coverage/lcov-report/api/controllers/auth.js.html +18 -6
  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 +53 -53
  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/migrations.js.html +119 -0
  22. package/coverage/lcov-report/api/controllers/permission.js.html +15 -15
  23. package/coverage/lcov-report/api/controllers/query/index.html +1 -1
  24. package/coverage/lcov-report/api/controllers/query/index.js.html +19 -19
  25. package/coverage/lcov-report/api/controllers/query/validation.js.html +6 -6
  26. package/coverage/lcov-report/api/controllers/role.js.html +9 -9
  27. package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
  28. package/coverage/lcov-report/api/controllers/row/external.js.html +16 -16
  29. package/coverage/lcov-report/api/controllers/row/index.html +48 -33
  30. package/coverage/lcov-report/api/controllers/row/index.js.html +26 -26
  31. package/coverage/lcov-report/api/controllers/row/internal.js.html +95 -158
  32. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +41 -17
  33. package/coverage/lcov-report/api/controllers/row/staticFormula.js.html +551 -0
  34. package/coverage/lcov-report/api/controllers/row/utils.js.html +39 -57
  35. package/coverage/lcov-report/api/controllers/screen.js.html +7 -7
  36. package/coverage/lcov-report/api/controllers/script.js.html +4 -4
  37. package/coverage/lcov-report/api/controllers/table/bulkFormula.js.html +629 -0
  38. package/coverage/lcov-report/api/controllers/table/external.js.html +12 -12
  39. package/coverage/lcov-report/api/controllers/table/index.html +46 -31
  40. package/coverage/lcov-report/api/controllers/table/index.js.html +53 -77
  41. package/coverage/lcov-report/api/controllers/table/internal.js.html +59 -35
  42. package/coverage/lcov-report/api/controllers/table/utils.js.html +217 -73
  43. package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
  44. package/coverage/lcov-report/api/controllers/user.js.html +30 -24
  45. package/coverage/lcov-report/api/controllers/view/exporters.js.html +4 -4
  46. package/coverage/lcov-report/api/controllers/view/index.html +1 -1
  47. package/coverage/lcov-report/api/controllers/view/index.js.html +14 -14
  48. package/coverage/lcov-report/api/controllers/view/utils.js.html +11 -11
  49. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +7 -7
  50. package/coverage/lcov-report/api/controllers/webhook.js.html +15 -15
  51. package/coverage/lcov-report/api/index.html +1 -1
  52. package/coverage/lcov-report/api/index.js.html +8 -8
  53. package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
  54. package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
  55. package/coverage/lcov-report/api/routes/application.js.html +1 -1
  56. package/coverage/lcov-report/api/routes/auth.js.html +1 -1
  57. package/coverage/lcov-report/api/routes/automation.js.html +1 -1
  58. package/coverage/lcov-report/api/routes/backup.js.html +1 -1
  59. package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
  60. package/coverage/lcov-report/api/routes/component.js.html +1 -1
  61. package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
  62. package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
  63. package/coverage/lcov-report/api/routes/dev.js.html +1 -1
  64. package/coverage/lcov-report/api/routes/index.html +18 -18
  65. package/coverage/lcov-report/api/routes/index.js.html +3 -3
  66. package/coverage/lcov-report/api/routes/integration.js.html +1 -1
  67. package/coverage/lcov-report/api/routes/layout.js.html +1 -1
  68. package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
  69. package/coverage/lcov-report/api/routes/{hosting.js.html → migrations.js.html} +17 -14
  70. package/coverage/lcov-report/api/routes/permission.js.html +1 -1
  71. package/coverage/lcov-report/api/routes/query.js.html +1 -1
  72. package/coverage/lcov-report/api/routes/role.js.html +1 -1
  73. package/coverage/lcov-report/api/routes/routing.js.html +1 -1
  74. package/coverage/lcov-report/api/routes/row.js.html +1 -1
  75. package/coverage/lcov-report/api/routes/screen.js.html +1 -1
  76. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  77. package/coverage/lcov-report/api/routes/static.js.html +17 -2
  78. package/coverage/lcov-report/api/routes/table.js.html +1 -1
  79. package/coverage/lcov-report/api/routes/templates.js.html +1 -1
  80. package/coverage/lcov-report/api/routes/user.js.html +1 -1
  81. package/coverage/lcov-report/api/routes/view.js.html +1 -1
  82. package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
  83. package/coverage/lcov-report/automations/actions.js.html +24 -24
  84. package/coverage/lcov-report/automations/automationUtils.js.html +5 -5
  85. package/coverage/lcov-report/automations/bullboard.js.html +16 -16
  86. package/coverage/lcov-report/automations/index.html +1 -1
  87. package/coverage/lcov-report/automations/index.js.html +1 -1
  88. package/coverage/lcov-report/automations/steps/bash.js.html +6 -6
  89. package/coverage/lcov-report/automations/steps/createRow.js.html +13 -19
  90. package/coverage/lcov-report/automations/steps/delay.js.html +4 -4
  91. package/coverage/lcov-report/automations/steps/deleteRow.js.html +11 -20
  92. package/coverage/lcov-report/automations/steps/discord.js.html +7 -7
  93. package/coverage/lcov-report/automations/steps/executeQuery.js.html +6 -6
  94. package/coverage/lcov-report/automations/steps/executeScript.js.html +6 -6
  95. package/coverage/lcov-report/automations/steps/filter.js.html +7 -7
  96. package/coverage/lcov-report/automations/steps/index.html +13 -13
  97. package/coverage/lcov-report/automations/steps/integromat.js.html +5 -5
  98. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +8 -8
  99. package/coverage/lcov-report/automations/steps/queryRows.js.html +10 -10
  100. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +5 -5
  101. package/coverage/lcov-report/automations/steps/serverLog.js.html +3 -3
  102. package/coverage/lcov-report/automations/steps/slack.js.html +5 -5
  103. package/coverage/lcov-report/automations/steps/updateRow.js.html +6 -6
  104. package/coverage/lcov-report/automations/steps/utils.js.html +3 -3
  105. package/coverage/lcov-report/automations/steps/zapier.js.html +5 -5
  106. package/coverage/lcov-report/automations/triggerInfo/app.js.html +2 -2
  107. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +2 -2
  108. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  109. package/coverage/lcov-report/automations/triggerInfo/index.js.html +8 -8
  110. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +2 -2
  111. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +2 -2
  112. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +2 -2
  113. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +2 -2
  114. package/coverage/lcov-report/automations/triggers.js.html +18 -18
  115. package/coverage/lcov-report/automations/utils.js.html +32 -32
  116. package/coverage/lcov-report/constants/index.html +5 -5
  117. package/coverage/lcov-report/constants/index.js.html +53 -26
  118. package/coverage/lcov-report/constants/layouts.js.html +5 -5
  119. package/coverage/lcov-report/constants/screens.js.html +4 -4
  120. package/coverage/lcov-report/db/client.js.html +18 -18
  121. package/coverage/lcov-report/db/inMemoryView.js.html +7 -7
  122. package/coverage/lcov-report/db/index.html +15 -15
  123. package/coverage/lcov-report/db/index.js.html +3 -3
  124. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +18 -18
  125. package/coverage/lcov-report/db/linkedRows/index.html +10 -10
  126. package/coverage/lcov-report/db/linkedRows/index.js.html +38 -41
  127. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +15 -15
  128. package/coverage/lcov-report/db/newid.js.html +4 -4
  129. package/coverage/lcov-report/db/utils.js.html +86 -86
  130. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  131. package/coverage/lcov-report/events/index.html +1 -1
  132. package/coverage/lcov-report/events/index.js.html +5 -5
  133. package/coverage/lcov-report/events/utils.js.html +3 -3
  134. package/coverage/lcov-report/index.html +114 -99
  135. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  136. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  137. package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
  138. package/coverage/lcov-report/middleware/authorized.js.html +166 -64
  139. package/coverage/lcov-report/middleware/builder.js.html +8 -8
  140. package/coverage/lcov-report/middleware/currentapp.js.html +29 -29
  141. package/coverage/lcov-report/middleware/index.html +30 -30
  142. package/coverage/lcov-report/middleware/joi-validator.js.html +3 -3
  143. package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
  144. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  145. package/coverage/lcov-report/middleware/usageQuota.js.html +270 -42
  146. package/coverage/lcov-report/middleware/utils.js.html +2 -2
  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 +27 -27
  164. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +115 -79
  165. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +75 -27
  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/{api/controllers/hosting.js.html → utilities/usageQuota/usageQuoteReset.js.html} +36 -48
  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 +3656 -3057
  176. package/dist/api/controllers/application.js +61 -34
  177. package/dist/api/controllers/auth.js +4 -0
  178. package/dist/api/controllers/datasource.js +11 -0
  179. package/dist/api/controllers/migrations.js +21 -0
  180. package/dist/api/controllers/row/internal.js +30 -53
  181. package/dist/api/controllers/row/internalSearch.js +12 -4
  182. package/dist/api/controllers/row/staticFormula.js +147 -0
  183. package/dist/api/controllers/row/utils.js +6 -8
  184. package/dist/api/controllers/static/index.js +59 -10
  185. package/dist/api/controllers/table/bulkFormula.js +160 -0
  186. package/dist/api/controllers/table/index.js +12 -9
  187. package/dist/api/controllers/table/internal.js +9 -0
  188. package/dist/api/controllers/table/utils.js +41 -12
  189. package/dist/api/controllers/user.js +2 -0
  190. package/dist/api/routes/index.js +2 -2
  191. package/dist/api/routes/migrations.js +9 -0
  192. package/dist/api/routes/static.js +2 -1
  193. package/dist/app.js +35 -5
  194. package/dist/automations/steps/createRow.js +2 -4
  195. package/dist/automations/steps/deleteRow.js +1 -4
  196. package/dist/constants/index.js +7 -0
  197. package/dist/db/linkedRows/index.js +4 -5
  198. package/dist/definitions/datasource.js +1 -0
  199. package/dist/environment.js +2 -0
  200. package/dist/integrations/base/sql.js +13 -6
  201. package/dist/integrations/googlesheets.js +340 -0
  202. package/dist/integrations/index.js +3 -0
  203. package/dist/integrations/utils.js +57 -9
  204. package/dist/middleware/authorized.js +55 -30
  205. package/dist/middleware/usageQuota.js +80 -13
  206. package/dist/migrations/functions/appUrls.js +46 -0
  207. package/dist/migrations/functions/quotas1.js +50 -0
  208. package/dist/migrations/functions/usageQuotas/index.js +20 -0
  209. package/dist/migrations/functions/usageQuotas/syncApps.js +32 -0
  210. package/dist/migrations/functions/usageQuotas/syncRows.js +35 -0
  211. package/dist/migrations/functions/userEmailViewCasing.js +24 -0
  212. package/dist/migrations/index.js +62 -0
  213. package/dist/package.json +8 -5
  214. package/dist/tests/utilities/TestConfiguration.js +13 -6
  215. package/dist/threads/query.js +4 -0
  216. package/dist/tsconfig.tsbuildinfo +1 -1
  217. package/dist/utilities/rowProcessor/index.js +18 -11
  218. package/dist/utilities/rowProcessor/utils.js +14 -4
  219. package/dist/utilities/{usageQuota.js → usageQuota/index.js} +44 -31
  220. package/dist/utilities/usageQuota/rows.js +77 -0
  221. package/dist/utilities/usageQuota/usageQuoteReset.js +16 -0
  222. package/dist/utilities/workerRequests.js +0 -20
  223. package/nodemon.json +1 -1
  224. package/package.json +9 -6
  225. package/src/api/controllers/application.js +52 -24
  226. package/src/api/controllers/auth.js +4 -0
  227. package/src/api/controllers/datasource.js +12 -0
  228. package/src/api/controllers/migrations.js +13 -0
  229. package/src/api/controllers/row/internal.js +29 -50
  230. package/src/api/controllers/row/internalSearch.js +12 -4
  231. package/src/api/controllers/row/staticFormula.js +157 -0
  232. package/src/api/controllers/row/utils.js +6 -12
  233. package/src/api/controllers/static/index.js +63 -10
  234. package/src/api/controllers/table/bulkFormula.js +183 -0
  235. package/src/api/controllers/table/index.js +16 -24
  236. package/src/api/controllers/table/internal.js +9 -1
  237. package/src/api/controllers/table/utils.js +57 -9
  238. package/src/api/controllers/user.js +2 -0
  239. package/src/api/routes/index.js +2 -2
  240. package/src/api/routes/migrations.js +14 -0
  241. package/src/api/routes/static.js +5 -0
  242. package/src/api/routes/tests/application.spec.js +2 -2
  243. package/src/api/routes/tests/auth.spec.js +1 -2
  244. package/src/api/routes/tests/static.spec.js +98 -0
  245. package/src/app.ts +18 -5
  246. package/src/automations/steps/createRow.js +2 -4
  247. package/src/automations/steps/deleteRow.js +1 -4
  248. package/src/constants/index.js +9 -0
  249. package/src/db/linkedRows/index.js +4 -5
  250. package/src/definitions/common.ts +3 -0
  251. package/src/definitions/datasource.ts +3 -0
  252. package/src/environment.js +2 -0
  253. package/src/integrations/base/sql.ts +14 -7
  254. package/src/integrations/googlesheets.ts +353 -0
  255. package/src/integrations/index.ts +3 -0
  256. package/src/integrations/tests/sql.spec.js +16 -0
  257. package/src/integrations/utils.ts +71 -11
  258. package/src/middleware/authorized.js +64 -30
  259. package/src/middleware/tests/authorized.spec.js +6 -4
  260. package/src/middleware/tests/usageQuota.spec.js +18 -24
  261. package/src/middleware/usageQuota.js +91 -15
  262. package/src/migrations/functions/appUrls.ts +34 -0
  263. package/src/migrations/functions/quotas1.ts +20 -0
  264. package/src/migrations/functions/tests/appUrls.spec.js +29 -0
  265. package/src/migrations/functions/tests/quotas1.spec.js +27 -0
  266. package/src/migrations/functions/tests/userEmailViewCasing.spec.js +25 -0
  267. package/src/migrations/functions/usageQuotas/index.ts +8 -0
  268. package/src/migrations/functions/usageQuotas/syncApps.ts +18 -0
  269. package/src/migrations/functions/usageQuotas/syncRows.ts +21 -0
  270. package/src/migrations/functions/usageQuotas/tests/syncApps.spec.js +37 -0
  271. package/src/migrations/functions/usageQuotas/tests/syncRows.spec.js +43 -0
  272. package/src/migrations/functions/userEmailViewCasing.ts +13 -0
  273. package/src/migrations/index.ts +56 -0
  274. package/src/tests/utilities/TestConfiguration.js +14 -6
  275. package/src/threads/query.js +5 -0
  276. package/src/utilities/rowProcessor/index.js +24 -12
  277. package/src/utilities/rowProcessor/utils.js +23 -7
  278. package/src/utilities/tests/usageQuota/usageQuota.spec.js +72 -0
  279. package/src/utilities/usageQuota/index.js +92 -0
  280. package/src/utilities/usageQuota/rows.js +74 -0
  281. package/src/utilities/usageQuota/usageQuoteReset.js +18 -0
  282. package/src/utilities/workerRequests.js +0 -23
  283. package/builder/assets/index.39e01193.css +0 -1
  284. package/dist/api/controllers/hosting.js +0 -30
  285. package/dist/api/routes/hosting.js +0 -11
  286. package/src/api/controllers/hosting.js +0 -22
  287. package/src/api/routes/hosting.js +0 -13
  288. package/src/api/routes/tests/hosting.spec.js +0 -36
  289. package/src/utilities/usageQuota.js +0 -73
@@ -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
@@ -119,6 +119,22 @@ describe("SQL query builder", () => {
119
119
  })
120
120
  })
121
121
 
122
+ it("should allow filtering on a related field", () => {
123
+ const query = sql._query(generateReadJson({
124
+ filters: {
125
+ equal: {
126
+ age: 10,
127
+ "task.name": "task 1",
128
+ },
129
+ },
130
+ }))
131
+ // order of bindings changes because relationship filters occur outside inner query
132
+ expect(query).toEqual({
133
+ bindings: [10, limit, "task 1"],
134
+ sql: `select * from (select * from "${TABLE_NAME}" where "${TABLE_NAME}"."age" = $1 limit $2) as "${TABLE_NAME}" where "task"."name" = $3`
135
+ })
136
+ })
137
+
122
138
  it("should test an create statement", () => {
123
139
  const query = sql._query(generateCreateJson(TABLE_NAME, {
124
140
  name: "Michael",
@@ -1,6 +1,5 @@
1
- import { SqlQuery } from "../definitions/datasource"
1
+ import { SourceNames, SqlQuery } from "../definitions/datasource"
2
2
  import { Datasource, Table } from "../definitions/common"
3
- import { SourceNames } from "../definitions/datasource"
4
3
  import { DocumentTypes, SEPARATOR } from "../db/utils"
5
4
  import { FieldTypes, BuildSchemaErrors, InvalidColumns } from "../constants"
6
5
 
@@ -127,7 +126,12 @@ export function isSQL(datasource: Datasource): boolean {
127
126
  if (!datasource || !datasource.source) {
128
127
  return false
129
128
  }
130
- const SQL = [SourceNames.POSTGRES, SourceNames.SQL_SERVER, SourceNames.MYSQL]
129
+ const SQL = [
130
+ SourceNames.POSTGRES,
131
+ SourceNames.SQL_SERVER,
132
+ SourceNames.MYSQL,
133
+ SourceNames.ORACLE,
134
+ ]
131
135
  return SQL.indexOf(datasource.source) !== -1
132
136
  }
133
137
 
@@ -139,11 +143,60 @@ export function isIsoDateString(str: string) {
139
143
  return d.toISOString() === str
140
144
  }
141
145
 
142
- // add the existing relationships from the entities if they exist, to prevent them from being overridden
146
+ /**
147
+ * This function will determine whether a column is a relationship and whether it
148
+ * is currently valid. The reason for the validity check is that tables can be deleted
149
+ * outside of Budibase control and if this is the case it will break Budibase relationships.
150
+ * The tableIds is a list passed down from the main finalise tables function, which is
151
+ * based on the tables that have just been fetched. This will only really be used on subsequent
152
+ * fetches to the first one - if the user is periodically refreshing Budibase knowledge of tables.
153
+ * @param column The column to check, to see if it is a valid relationship.
154
+ * @param tableIds The IDs of the tables which currently exist.
155
+ */
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
+ )
165
+ }
166
+
167
+ /**
168
+ * Similar function to the shouldCopyRelationship function, but instead this looks for options and boolean
169
+ * types. It is possible to switch a string -> options and a number -> boolean (and vice versus) need to make
170
+ * sure that these get copied over when tables are fetched. Also checks whether they are still valid, if a
171
+ * column has changed type in the external database then copying it over may not be possible.
172
+ * @param column The column to check for options or boolean type.
173
+ * @param fetchedColumn The fetched column to check for the type in the external database.
174
+ */
175
+ function shouldCopySpecialColumn(
176
+ column: { type: string },
177
+ fetchedColumn: { type: string } | undefined
178
+ ) {
179
+ return (
180
+ column.type === FieldTypes.OPTIONS ||
181
+ column.type === FieldTypes.ARRAY ||
182
+ ((!fetchedColumn || fetchedColumn.type === FieldTypes.NUMBER) &&
183
+ column.type === FieldTypes.BOOLEAN)
184
+ )
185
+ }
186
+
187
+ /**
188
+ * Looks for columns which need to be copied over into the new table definitions, like relationships
189
+ * and options types.
190
+ * @param tableName The name of the table which is being checked.
191
+ * @param table The specific table which is being checked.
192
+ * @param entities All the tables that existed before - the old table definitions.
193
+ * @param tableIds The IDs of the tables which exist now, to check if anything has been removed.
194
+ */
143
195
  function copyExistingPropsOver(
144
196
  tableName: string,
145
197
  table: Table,
146
- entities: { [key: string]: any }
198
+ entities: { [key: string]: any },
199
+ tableIds: [string]
147
200
  ) {
148
201
  if (entities && entities[tableName]) {
149
202
  if (entities[tableName].primaryDisplay) {
@@ -154,12 +207,10 @@ function copyExistingPropsOver(
154
207
  if (!existingTableSchema.hasOwnProperty(key)) {
155
208
  continue
156
209
  }
210
+ const column = existingTableSchema[key]
157
211
  if (
158
- existingTableSchema[key].type === FieldTypes.LINK ||
159
- existingTableSchema[key].type === FieldTypes.OPTIONS ||
160
- existingTableSchema[key].type === FieldTypes.ARRAY ||
161
- ((!table.schema[key] || table.schema[key].type === FieldTypes.NUMBER) &&
162
- existingTableSchema[key].type === FieldTypes.BOOLEAN)
212
+ shouldCopyRelationship(column, tableIds) ||
213
+ shouldCopySpecialColumn(column, table.schema[key])
163
214
  ) {
164
215
  table.schema[key] = existingTableSchema[key]
165
216
  }
@@ -168,6 +219,13 @@ function copyExistingPropsOver(
168
219
  return table
169
220
  }
170
221
 
222
+ /**
223
+ * Look through the final table definitions to see if anything needs to be
224
+ * copied over from the old and if any errors have occurred mark them so
225
+ * that the user can be made aware.
226
+ * @param tables The list of tables that have been retrieved from the external database.
227
+ * @param entities The old list of tables, if there was any to look for definitions in.
228
+ */
171
229
  export function finaliseExternalTables(
172
230
  tables: { [key: string]: any },
173
231
  entities: { [key: string]: any }
@@ -175,6 +233,8 @@ export function finaliseExternalTables(
175
233
  const invalidColumns = Object.values(InvalidColumns)
176
234
  let finalTables: { [key: string]: any } = {}
177
235
  const errors: { [key: string]: string } = {}
236
+ // @ts-ignore
237
+ const tableIds: [string] = Object.values(tables).map(table => table._id)
178
238
  for (let [name, table] of Object.entries(tables)) {
179
239
  const schemaFields = Object.keys(table.schema)
180
240
  // make sure every table has a key
@@ -186,7 +246,7 @@ export function finaliseExternalTables(
186
246
  continue
187
247
  }
188
248
  // make sure all previous props have been added back
189
- finalTables[name] = copyExistingPropsOver(name, table, entities)
249
+ finalTables[name] = copyExistingPropsOver(name, table, entities, tableIds)
190
250
  }
191
251
  // sort the tables by name
192
252
  finalTables = Object.entries(finalTables)
@@ -9,11 +9,59 @@ const {
9
9
  } = require("@budibase/backend-core/permissions")
10
10
  const builderMiddleware = require("./builder")
11
11
  const { isWebhookEndpoint } = require("./utils")
12
+ const { buildCsrfMiddleware } = require("@budibase/backend-core/auth")
12
13
 
13
14
  function hasResource(ctx) {
14
15
  return ctx.resourceId != null
15
16
  }
16
17
 
18
+ const csrf = buildCsrfMiddleware()
19
+
20
+ /**
21
+ * Apply authorization to the requested resource:
22
+ * - If this is a builder resource the user must be a builder.
23
+ * - Builders can access all resources.
24
+ * - Otherwise the user must have the required role.
25
+ */
26
+ const checkAuthorized = async (ctx, resourceRoles, permType, permLevel) => {
27
+ // check if this is a builder api and the user is not a builder
28
+ const isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
29
+ const isBuilderApi = permType === PermissionTypes.BUILDER
30
+ if (isBuilderApi && !isBuilder) {
31
+ return ctx.throw(403, "Not Authorized")
32
+ }
33
+
34
+ // check for resource authorization
35
+ if (!isBuilder) {
36
+ await checkAuthorizedResource(ctx, resourceRoles, permType, permLevel)
37
+ }
38
+ }
39
+
40
+ const checkAuthorizedResource = async (
41
+ ctx,
42
+ resourceRoles,
43
+ permType,
44
+ permLevel
45
+ ) => {
46
+ // get the user's roles
47
+ const roleId = ctx.roleId || BUILTIN_ROLE_IDS.PUBLIC
48
+ const userRoles = await getUserRoleHierarchy(ctx.appId, roleId, {
49
+ idOnly: false,
50
+ })
51
+ const permError = "User does not have permission"
52
+ // check if the user has the required role
53
+ if (resourceRoles.length > 0) {
54
+ // deny access if the user doesn't have the required resource role
55
+ const found = userRoles.find(role => resourceRoles.indexOf(role._id) !== -1)
56
+ if (!found) {
57
+ ctx.throw(403, permError)
58
+ }
59
+ // fallback to the base permissions when no resource roles are found
60
+ } else if (!doesHaveBasePermission(permType, permLevel, userRoles)) {
61
+ ctx.throw(403, permError)
62
+ }
63
+ }
64
+
17
65
  module.exports =
18
66
  (permType, permLevel = null) =>
19
67
  async (ctx, next) => {
@@ -31,40 +79,26 @@ module.exports =
31
79
  // to find API endpoints which are builder focused
32
80
  await builderMiddleware(ctx, permType)
33
81
 
34
- const isAuthed = ctx.isAuthenticated
35
- // builders for now have permission to do anything
36
- let isBuilder = ctx.user && ctx.user.builder && ctx.user.builder.global
37
- const isBuilderApi = permType === PermissionTypes.BUILDER
38
- if (isBuilder) {
39
- return next()
40
- } else if (isBuilderApi && !isBuilder) {
41
- return ctx.throw(403, "Not Authorized")
82
+ // get the resource roles
83
+ let resourceRoles = []
84
+ if (ctx.appId && hasResource(ctx)) {
85
+ resourceRoles = await getRequiredResourceRole(ctx.appId, permLevel, ctx)
42
86
  }
43
87
 
44
- // need to check this first, in-case public access, don't check authed until last
45
- const roleId = ctx.roleId || BUILTIN_ROLE_IDS.PUBLIC
46
- const hierarchy = await getUserRoleHierarchy(ctx.appId, roleId, {
47
- idOnly: false,
48
- })
49
- const permError = "User does not have permission"
50
- let possibleRoleIds = []
51
- if (hasResource(ctx)) {
52
- possibleRoleIds = await getRequiredResourceRole(ctx.appId, permLevel, ctx)
53
- }
54
- // check if we found a role, if not fallback to base permissions
55
- if (possibleRoleIds.length > 0) {
56
- const found = hierarchy.find(
57
- role => possibleRoleIds.indexOf(role._id) !== -1
58
- )
59
- return found ? next() : ctx.throw(403, permError)
60
- } else if (!doesHaveBasePermission(permType, permLevel, hierarchy)) {
61
- ctx.throw(403, permError)
88
+ // if the resource is public, proceed
89
+ const isPublicResource = resourceRoles.includes(BUILTIN_ROLE_IDS.PUBLIC)
90
+ if (isPublicResource) {
91
+ return next()
62
92
  }
63
93
 
64
- // if they are not authed, then anything using the authorized middleware will fail
65
- if (!isAuthed) {
66
- ctx.throw(403, "Session not authenticated")
94
+ // check authenticated
95
+ if (!ctx.isAuthenticated) {
96
+ return ctx.throw(403, "Session not authenticated")
67
97
  }
68
98
 
69
- return next()
99
+ // check authorized
100
+ await checkAuthorized(ctx, resourceRoles, permType, permLevel)
101
+
102
+ // csrf protection
103
+ return csrf(ctx, next)
70
104
  }
@@ -17,6 +17,7 @@ class TestConfiguration {
17
17
  this.middleware = authorizedMiddleware(role)
18
18
  this.next = jest.fn()
19
19
  this.throw = jest.fn()
20
+ this.headers = {}
20
21
  this.ctx = {
21
22
  headers: {},
22
23
  request: {
@@ -25,7 +26,8 @@ class TestConfiguration {
25
26
  appId: "",
26
27
  auth: {},
27
28
  next: this.next,
28
- throw: this.throw
29
+ throw: this.throw,
30
+ get: (name) => this.headers[name],
29
31
  }
30
32
  }
31
33
 
@@ -46,7 +48,7 @@ class TestConfiguration {
46
48
  }
47
49
 
48
50
  setAuthenticated(isAuthed) {
49
- this.ctx.auth = { authenticated: isAuthed }
51
+ this.ctx.isAuthenticated = isAuthed
50
52
  }
51
53
 
52
54
  setRequestUrl(url) {
@@ -107,7 +109,7 @@ describe("Authorization middleware", () => {
107
109
  expect(config.next).toHaveBeenCalled()
108
110
  })
109
111
 
110
- it("throws if the user has only builder permissions", async () => {
112
+ it("throws if the user does not have builder permissions", async () => {
111
113
  config.setEnvironment(false)
112
114
  config.setMiddlewareRequiredPermission(PermissionTypes.BUILDER)
113
115
  config.setUser({
@@ -133,7 +135,7 @@ describe("Authorization middleware", () => {
133
135
  expect(config.next).toHaveBeenCalled()
134
136
  })
135
137
 
136
- it("throws if the user session is not authenticated after permission checks", async () => {
138
+ it("throws if the user session is not authenticated", async () => {
137
139
  config.setUser({
138
140
  role: {
139
141
  _id: ""