@budibase/server 1.0.46-alpha.5 → 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 (195) hide show
  1. package/builder/assets/{index.298ba3d3.js → index.00221ff2.js} +234 -234
  2. package/builder/assets/{index.243ca52c.css → index.dd9e386f.css} +1 -1
  3. package/builder/index.html +2 -2
  4. package/coverage/clover.xml +157 -145
  5. package/coverage/coverage-final.json +4 -4
  6. package/coverage/lcov-report/api/controllers/analytics.js.html +1 -1
  7. package/coverage/lcov-report/api/controllers/apikeys.js.html +1 -1
  8. package/coverage/lcov-report/api/controllers/application.js.html +1 -1
  9. package/coverage/lcov-report/api/controllers/auth.js.html +1 -1
  10. package/coverage/lcov-report/api/controllers/automation.js.html +1 -1
  11. package/coverage/lcov-report/api/controllers/backup.js.html +1 -1
  12. package/coverage/lcov-report/api/controllers/cloud.js.html +1 -1
  13. package/coverage/lcov-report/api/controllers/component.js.html +1 -1
  14. package/coverage/lcov-report/api/controllers/datasource.js.html +44 -8
  15. package/coverage/lcov-report/api/controllers/dev.js.html +1 -1
  16. package/coverage/lcov-report/api/controllers/index.html +15 -15
  17. package/coverage/lcov-report/api/controllers/integration.js.html +1 -1
  18. package/coverage/lcov-report/api/controllers/layout.js.html +1 -1
  19. package/coverage/lcov-report/api/controllers/metadata.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 +15 -15
  22. package/coverage/lcov-report/api/controllers/query/index.js.html +44 -11
  23. package/coverage/lcov-report/api/controllers/query/validation.js.html +1 -1
  24. package/coverage/lcov-report/api/controllers/role.js.html +1 -1
  25. package/coverage/lcov-report/api/controllers/routing.js.html +1 -1
  26. package/coverage/lcov-report/api/controllers/row/external.js.html +1 -1
  27. package/coverage/lcov-report/api/controllers/row/index.html +5 -5
  28. package/coverage/lcov-report/api/controllers/row/index.js.html +1 -1
  29. package/coverage/lcov-report/api/controllers/row/internal.js.html +1 -1
  30. package/coverage/lcov-report/api/controllers/row/internalSearch.js.html +1 -1
  31. package/coverage/lcov-report/api/controllers/row/utils.js.html +5 -14
  32. package/coverage/lcov-report/api/controllers/screen.js.html +1 -1
  33. package/coverage/lcov-report/api/controllers/script.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/table/internal.js.html +1 -1
  38. package/coverage/lcov-report/api/controllers/table/utils.js.html +1 -1
  39. package/coverage/lcov-report/api/controllers/templates.js.html +1 -1
  40. package/coverage/lcov-report/api/controllers/user.js.html +1 -1
  41. package/coverage/lcov-report/api/controllers/view/exporters.js.html +1 -1
  42. package/coverage/lcov-report/api/controllers/view/index.html +1 -1
  43. package/coverage/lcov-report/api/controllers/view/index.js.html +1 -1
  44. package/coverage/lcov-report/api/controllers/view/utils.js.html +1 -1
  45. package/coverage/lcov-report/api/controllers/view/viewBuilder.js.html +1 -1
  46. package/coverage/lcov-report/api/controllers/webhook.js.html +1 -1
  47. package/coverage/lcov-report/api/index.html +1 -1
  48. package/coverage/lcov-report/api/index.js.html +1 -1
  49. package/coverage/lcov-report/api/routes/analytics.js.html +1 -1
  50. package/coverage/lcov-report/api/routes/apikeys.js.html +1 -1
  51. package/coverage/lcov-report/api/routes/application.js.html +1 -1
  52. package/coverage/lcov-report/api/routes/auth.js.html +1 -1
  53. package/coverage/lcov-report/api/routes/automation.js.html +1 -1
  54. package/coverage/lcov-report/api/routes/backup.js.html +1 -1
  55. package/coverage/lcov-report/api/routes/cloud.js.html +1 -1
  56. package/coverage/lcov-report/api/routes/component.js.html +1 -1
  57. package/coverage/lcov-report/api/routes/datasource.js.html +1 -1
  58. package/coverage/lcov-report/api/routes/deploy.js.html +1 -1
  59. package/coverage/lcov-report/api/routes/dev.js.html +1 -1
  60. package/coverage/lcov-report/api/routes/index.html +1 -1
  61. package/coverage/lcov-report/api/routes/index.js.html +1 -1
  62. package/coverage/lcov-report/api/routes/integration.js.html +1 -1
  63. package/coverage/lcov-report/api/routes/layout.js.html +1 -1
  64. package/coverage/lcov-report/api/routes/metadata.js.html +1 -1
  65. package/coverage/lcov-report/api/routes/permission.js.html +1 -1
  66. package/coverage/lcov-report/api/routes/query.js.html +1 -1
  67. package/coverage/lcov-report/api/routes/role.js.html +1 -1
  68. package/coverage/lcov-report/api/routes/routing.js.html +1 -1
  69. package/coverage/lcov-report/api/routes/row.js.html +1 -1
  70. package/coverage/lcov-report/api/routes/screen.js.html +1 -1
  71. package/coverage/lcov-report/api/routes/script.js.html +1 -1
  72. package/coverage/lcov-report/api/routes/static.js.html +1 -1
  73. package/coverage/lcov-report/api/routes/table.js.html +1 -1
  74. package/coverage/lcov-report/api/routes/templates.js.html +1 -1
  75. package/coverage/lcov-report/api/routes/user.js.html +1 -1
  76. package/coverage/lcov-report/api/routes/view.js.html +1 -1
  77. package/coverage/lcov-report/api/routes/webhook.js.html +1 -1
  78. package/coverage/lcov-report/automations/actions.js.html +1 -1
  79. package/coverage/lcov-report/automations/automationUtils.js.html +1 -1
  80. package/coverage/lcov-report/automations/bullboard.js.html +1 -1
  81. package/coverage/lcov-report/automations/index.html +1 -1
  82. package/coverage/lcov-report/automations/index.js.html +1 -1
  83. package/coverage/lcov-report/automations/steps/bash.js.html +1 -1
  84. package/coverage/lcov-report/automations/steps/createRow.js.html +1 -1
  85. package/coverage/lcov-report/automations/steps/delay.js.html +1 -1
  86. package/coverage/lcov-report/automations/steps/deleteRow.js.html +1 -1
  87. package/coverage/lcov-report/automations/steps/discord.js.html +1 -1
  88. package/coverage/lcov-report/automations/steps/executeQuery.js.html +1 -1
  89. package/coverage/lcov-report/automations/steps/executeScript.js.html +1 -1
  90. package/coverage/lcov-report/automations/steps/filter.js.html +1 -1
  91. package/coverage/lcov-report/automations/steps/index.html +1 -1
  92. package/coverage/lcov-report/automations/steps/integromat.js.html +1 -1
  93. package/coverage/lcov-report/automations/steps/outgoingWebhook.js.html +1 -1
  94. package/coverage/lcov-report/automations/steps/queryRows.js.html +1 -1
  95. package/coverage/lcov-report/automations/steps/sendSmtpEmail.js.html +1 -1
  96. package/coverage/lcov-report/automations/steps/serverLog.js.html +1 -1
  97. package/coverage/lcov-report/automations/steps/slack.js.html +1 -1
  98. package/coverage/lcov-report/automations/steps/updateRow.js.html +1 -1
  99. package/coverage/lcov-report/automations/steps/utils.js.html +1 -1
  100. package/coverage/lcov-report/automations/steps/zapier.js.html +1 -1
  101. package/coverage/lcov-report/automations/triggerInfo/app.js.html +1 -1
  102. package/coverage/lcov-report/automations/triggerInfo/cron.js.html +1 -1
  103. package/coverage/lcov-report/automations/triggerInfo/index.html +1 -1
  104. package/coverage/lcov-report/automations/triggerInfo/index.js.html +1 -1
  105. package/coverage/lcov-report/automations/triggerInfo/rowDeleted.js.html +1 -1
  106. package/coverage/lcov-report/automations/triggerInfo/rowSaved.js.html +1 -1
  107. package/coverage/lcov-report/automations/triggerInfo/rowUpdated.js.html +1 -1
  108. package/coverage/lcov-report/automations/triggerInfo/webhook.js.html +1 -1
  109. package/coverage/lcov-report/automations/triggers.js.html +1 -1
  110. package/coverage/lcov-report/automations/utils.js.html +1 -1
  111. package/coverage/lcov-report/constants/index.html +5 -5
  112. package/coverage/lcov-report/constants/index.js.html +16 -4
  113. package/coverage/lcov-report/constants/layouts.js.html +1 -1
  114. package/coverage/lcov-report/constants/screens.js.html +1 -1
  115. package/coverage/lcov-report/db/client.js.html +1 -1
  116. package/coverage/lcov-report/db/inMemoryView.js.html +1 -1
  117. package/coverage/lcov-report/db/index.html +1 -1
  118. package/coverage/lcov-report/db/index.js.html +1 -1
  119. package/coverage/lcov-report/db/linkedRows/LinkController.js.html +1 -1
  120. package/coverage/lcov-report/db/linkedRows/index.html +1 -1
  121. package/coverage/lcov-report/db/linkedRows/index.js.html +1 -1
  122. package/coverage/lcov-report/db/linkedRows/linkUtils.js.html +1 -1
  123. package/coverage/lcov-report/db/newid.js.html +1 -1
  124. package/coverage/lcov-report/db/utils.js.html +1 -1
  125. package/coverage/lcov-report/events/AutomationEmitter.js.html +1 -1
  126. package/coverage/lcov-report/events/index.html +1 -1
  127. package/coverage/lcov-report/events/index.js.html +1 -1
  128. package/coverage/lcov-report/events/utils.js.html +1 -1
  129. package/coverage/lcov-report/index.html +26 -26
  130. package/coverage/lcov-report/integrations/tests/TestConfiguration.js.html +1 -1
  131. package/coverage/lcov-report/integrations/tests/index.html +1 -1
  132. package/coverage/lcov-report/middleware/appInfo.js.html +1 -1
  133. package/coverage/lcov-report/middleware/authorized.js.html +1 -1
  134. package/coverage/lcov-report/middleware/builder.js.html +1 -1
  135. package/coverage/lcov-report/middleware/currentapp.js.html +1 -1
  136. package/coverage/lcov-report/middleware/index.html +1 -1
  137. package/coverage/lcov-report/middleware/joi-validator.js.html +1 -1
  138. package/coverage/lcov-report/middleware/resourceId.js.html +1 -1
  139. package/coverage/lcov-report/middleware/selfhost.js.html +1 -1
  140. package/coverage/lcov-report/middleware/usageQuota.js.html +1 -1
  141. package/coverage/lcov-report/middleware/utils.js.html +1 -1
  142. package/coverage/lcov-report/migrations/usageQuotas/index.html +1 -1
  143. package/coverage/lcov-report/migrations/usageQuotas/index.js.html +1 -1
  144. package/coverage/lcov-report/migrations/usageQuotas/syncApps.js.html +1 -1
  145. package/coverage/lcov-report/migrations/usageQuotas/syncRows.js.html +1 -1
  146. package/coverage/lcov-report/threads/automation.js.html +1 -1
  147. package/coverage/lcov-report/threads/index.html +1 -1
  148. package/coverage/lcov-report/threads/index.js.html +1 -1
  149. package/coverage/lcov-report/threads/query.js.html +1 -1
  150. package/coverage/lcov-report/threads/utils.js.html +1 -1
  151. package/coverage/lcov-report/utilities/bcrypt.js.html +1 -1
  152. package/coverage/lcov-report/utilities/budibaseDir.js.html +1 -1
  153. package/coverage/lcov-report/utilities/centralPath.js.html +1 -1
  154. package/coverage/lcov-report/utilities/csvParser.js.html +1 -1
  155. package/coverage/lcov-report/utilities/global.js.html +1 -1
  156. package/coverage/lcov-report/utilities/index.html +1 -1
  157. package/coverage/lcov-report/utilities/index.js.html +1 -1
  158. package/coverage/lcov-report/utilities/queue/inMemoryQueue.js.html +1 -1
  159. package/coverage/lcov-report/utilities/queue/index.html +1 -1
  160. package/coverage/lcov-report/utilities/routing/index.html +1 -1
  161. package/coverage/lcov-report/utilities/routing/index.js.html +1 -1
  162. package/coverage/lcov-report/utilities/rowProcessor/index.html +1 -1
  163. package/coverage/lcov-report/utilities/rowProcessor/index.js.html +1 -1
  164. package/coverage/lcov-report/utilities/rowProcessor/utils.js.html +1 -1
  165. package/coverage/lcov-report/utilities/scriptRunner.js.html +1 -1
  166. package/coverage/lcov-report/utilities/security.js.html +1 -1
  167. package/coverage/lcov-report/utilities/statusCodes.js.html +1 -1
  168. package/coverage/lcov-report/utilities/usageQuota/index.html +1 -1
  169. package/coverage/lcov-report/utilities/usageQuota/index.js.html +1 -1
  170. package/coverage/lcov-report/utilities/usageQuota/rows.js.html +1 -1
  171. package/coverage/lcov-report/utilities/usageQuota/usageQuoteReset.js.html +1 -1
  172. package/coverage/lcov-report/utilities/users.js.html +1 -1
  173. package/coverage/lcov-report/utilities/workerRequests.js.html +1 -1
  174. package/coverage/lcov.info +253 -226
  175. package/dist/api/controllers/datasource.js +11 -0
  176. package/dist/api/controllers/query/index.js +11 -3
  177. package/dist/api/controllers/row/utils.js +1 -2
  178. package/dist/constants/index.js +3 -0
  179. package/dist/definitions/datasource.js +1 -0
  180. package/dist/integrations/googlesheets.js +340 -0
  181. package/dist/integrations/index.js +3 -0
  182. package/dist/integrations/rest.js +5 -5
  183. package/dist/integrations/s3.js +1 -1
  184. package/dist/package.json +7 -4
  185. package/dist/tsconfig.tsbuildinfo +1 -1
  186. package/package.json +8 -5
  187. package/src/api/controllers/datasource.js +12 -0
  188. package/src/api/controllers/query/index.js +14 -3
  189. package/src/api/controllers/row/utils.js +1 -4
  190. package/src/constants/index.js +4 -0
  191. package/src/definitions/datasource.ts +3 -0
  192. package/src/integrations/googlesheets.ts +353 -0
  193. package/src/integrations/index.ts +3 -0
  194. package/src/integrations/rest.ts +28 -9
  195. package/src/integrations/s3.ts +1 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/server",
3
3
  "email": "hi@budibase.com",
4
- "version": "1.0.46-alpha.5",
4
+ "version": "1.0.46-alpha.6",
5
5
  "description": "Budibase Web Server",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -70,9 +70,9 @@
70
70
  "license": "GPL-3.0",
71
71
  "dependencies": {
72
72
  "@apidevtools/swagger-parser": "^10.0.3",
73
- "@budibase/backend-core": "^1.0.46-alpha.5",
74
- "@budibase/client": "^1.0.46-alpha.5",
75
- "@budibase/string-templates": "^1.0.46-alpha.5",
73
+ "@budibase/backend-core": "^1.0.46-alpha.6",
74
+ "@budibase/client": "^1.0.46-alpha.6",
75
+ "@budibase/string-templates": "^1.0.46-alpha.6",
76
76
  "@bull-board/api": "^3.7.0",
77
77
  "@bull-board/koa": "^3.7.0",
78
78
  "@elastic/elasticsearch": "7.10.0",
@@ -92,6 +92,8 @@
92
92
  "fix-path": "3.0.0",
93
93
  "form-data": "^4.0.0",
94
94
  "fs-extra": "8.1.0",
95
+ "google-auth-library": "^7.11.0",
96
+ "google-spreadsheet": "^3.2.0",
95
97
  "jimp": "0.16.1",
96
98
  "joi": "17.2.1",
97
99
  "js-yaml": "^4.1.0",
@@ -139,6 +141,7 @@
139
141
  "@jest/test-sequencer": "^24.8.0",
140
142
  "@types/apidoc": "^0.50.0",
141
143
  "@types/bull": "^3.15.1",
144
+ "@types/google-spreadsheet": "^3.1.5",
142
145
  "@types/jest": "^26.0.23",
143
146
  "@types/koa": "^2.13.3",
144
147
  "@types/koa-router": "^7.4.2",
@@ -165,5 +168,5 @@
165
168
  "optionalDependencies": {
166
169
  "oracledb": "^5.3.0"
167
170
  },
168
- "gitHead": "cbfb0f8a13790f223580d3fee84e334eeda5b56e"
171
+ "gitHead": "a3e5c5af7f047b46ba1d7875ad489f357632b267"
169
172
  }
@@ -38,6 +38,13 @@ exports.fetch = async function (ctx) {
38
38
  )
39
39
  ).rows.map(row => row.doc)
40
40
 
41
+ for (let datasource of datasources) {
42
+ if (datasource.config && datasource.config.auth) {
43
+ // strip secrets from response so they don't show in the network request
44
+ delete datasource.config.auth
45
+ }
46
+ }
47
+
41
48
  ctx.body = [bbInternalDb, ...datasources]
42
49
  }
43
50
 
@@ -94,8 +101,13 @@ exports.update = async function (ctx) {
94
101
  const db = new CouchDB(ctx.appId)
95
102
  const datasourceId = ctx.params.datasourceId
96
103
  let datasource = await db.get(datasourceId)
104
+ const auth = datasource.config.auth
97
105
  await invalidateVariables(datasource, ctx.request.body)
98
106
  datasource = { ...datasource, ...ctx.request.body }
107
+ if (auth && !ctx.request.body.auth) {
108
+ // don't strip auth config from DB
109
+ datasource.config.auth = auth
110
+ }
99
111
 
100
112
  const response = await db.put(datasource)
101
113
  datasource._rev = response.rev
@@ -141,6 +141,16 @@ async function execute(ctx, opts = { rowsOnly: false }) {
141
141
  const query = await db.get(ctx.params.queryId)
142
142
  const datasource = await db.get(query.datasourceId)
143
143
 
144
+ const enrichedParameters = ctx.request.body.parameters || {}
145
+ // make sure parameters are fully enriched with defaults
146
+ if (query && query.parameters) {
147
+ for (let parameter of query.parameters) {
148
+ if (!enrichedParameters[parameter.name]) {
149
+ enrichedParameters[parameter.name] = parameter.default
150
+ }
151
+ }
152
+ }
153
+
144
154
  // call the relevant CRUD method on the integration class
145
155
  try {
146
156
  const { rows, pagination, extra } = await Runner.run({
@@ -149,7 +159,7 @@ async function execute(ctx, opts = { rowsOnly: false }) {
149
159
  queryVerb: query.queryVerb,
150
160
  fields: query.fields,
151
161
  pagination: ctx.request.body.pagination,
152
- parameters: ctx.request.body.parameters,
162
+ parameters: enrichedParameters,
153
163
  transformer: query.transformer,
154
164
  queryId: ctx.params.queryId,
155
165
  })
@@ -178,8 +188,9 @@ const removeDynamicVariables = async (db, queryId) => {
178
188
 
179
189
  if (dynamicVariables) {
180
190
  // delete dynamic variables from the datasource
181
- const newVariables = dynamicVariables.filter(dv => dv.queryId !== queryId)
182
- datasource.config.dynamicVariables = newVariables
191
+ datasource.config.dynamicVariables = dynamicVariables.filter(
192
+ dv => dv.queryId !== queryId
193
+ )
183
194
  await db.put(datasource)
184
195
 
185
196
  // invalidate the deleted variables
@@ -52,10 +52,7 @@ exports.validate = async ({ appId, tableId, row, table }) => {
52
52
  const constraints = cloneDeep(table.schema[fieldName].constraints)
53
53
  const type = table.schema[fieldName].type
54
54
  // special case for options, need to always allow unselected (null)
55
- if (
56
- (type === FieldTypes.OPTIONS || type === FieldTypes.ARRAY) &&
57
- constraints.inclusion
58
- ) {
55
+ if (type === FieldTypes.OPTIONS && constraints.inclusion) {
59
56
  constraints.inclusion.push(null)
60
57
  }
61
58
  let res
@@ -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: {}
@@ -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
@@ -43,8 +43,8 @@ const coreFields = {
43
43
  enum: Object.values(BodyTypes),
44
44
  },
45
45
  pagination: {
46
- type: DatasourceFieldTypes.OBJECT
47
- }
46
+ type: DatasourceFieldTypes.OBJECT,
47
+ },
48
48
  }
49
49
 
50
50
  module RestModule {
@@ -178,12 +178,17 @@ module RestModule {
178
178
  headers,
179
179
  },
180
180
  pagination: {
181
- cursor: nextCursor
182
- }
181
+ cursor: nextCursor,
182
+ },
183
183
  }
184
184
  }
185
185
 
186
- getUrl(path: string, queryString: string, pagination: PaginationConfig | null, paginationValues: PaginationValues | null): string {
186
+ getUrl(
187
+ path: string,
188
+ queryString: string,
189
+ pagination: PaginationConfig | null,
190
+ paginationValues: PaginationValues | null
191
+ ): string {
187
192
  // Add pagination params to query string if required
188
193
  if (pagination?.location === "query" && paginationValues) {
189
194
  const { pageParam, sizeParam } = pagination
@@ -217,14 +222,22 @@ module RestModule {
217
222
  return complete
218
223
  }
219
224
 
220
- addBody(bodyType: string, body: string | any, input: any, pagination: PaginationConfig | null, paginationValues: PaginationValues | null) {
225
+ addBody(
226
+ bodyType: string,
227
+ body: string | any,
228
+ input: any,
229
+ pagination: PaginationConfig | null,
230
+ paginationValues: PaginationValues | null
231
+ ) {
221
232
  if (!input.headers) {
222
233
  input.headers = {}
223
234
  }
224
235
  if (bodyType === BodyTypes.NONE) {
225
236
  return input
226
237
  }
227
- let error, object: any = {}, string = ""
238
+ let error,
239
+ object: any = {},
240
+ string = ""
228
241
  try {
229
242
  if (body) {
230
243
  string = typeof body !== "string" ? JSON.stringify(body) : body
@@ -333,7 +346,7 @@ module RestModule {
333
346
  requestBody,
334
347
  authConfigId,
335
348
  pagination,
336
- paginationValues
349
+ paginationValues,
337
350
  } = query
338
351
  const authHeaders = this.getAuthHeaders(authConfigId)
339
352
 
@@ -352,7 +365,13 @@ module RestModule {
352
365
  }
353
366
 
354
367
  let input: any = { method, headers: this.headers }
355
- input = this.addBody(bodyType, requestBody, input, pagination, paginationValues)
368
+ input = this.addBody(
369
+ bodyType,
370
+ requestBody,
371
+ input,
372
+ pagination,
373
+ paginationValues
374
+ )
356
375
 
357
376
  this.startTimeMs = performance.now()
358
377
  const url = this.getUrl(path, queryString, pagination, paginationValues)
@@ -38,7 +38,7 @@ module S3Module {
38
38
  signatureVersion: {
39
39
  type: "string",
40
40
  required: false,
41
- default: "v4"
41
+ default: "v4",
42
42
  },
43
43
  },
44
44
  query: {