@budibase/server 2.7.21-alpha.3 → 2.7.23

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/server",
3
3
  "email": "hi@budibase.com",
4
- "version": "2.7.21-alpha.3",
4
+ "version": "2.7.23",
5
5
  "description": "Budibase Web Server",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -46,12 +46,12 @@
46
46
  "license": "GPL-3.0",
47
47
  "dependencies": {
48
48
  "@apidevtools/swagger-parser": "10.0.3",
49
- "@budibase/backend-core": "2.7.21-alpha.3",
50
- "@budibase/client": "2.7.21-alpha.3",
51
- "@budibase/pro": "2.7.21-alpha.3",
52
- "@budibase/shared-core": "2.7.21-alpha.3",
53
- "@budibase/string-templates": "2.7.21-alpha.3",
54
- "@budibase/types": "2.7.21-alpha.3",
49
+ "@budibase/backend-core": "2.7.23",
50
+ "@budibase/client": "2.7.23",
51
+ "@budibase/pro": "2.7.23",
52
+ "@budibase/shared-core": "2.7.23",
53
+ "@budibase/string-templates": "2.7.23",
54
+ "@budibase/types": "2.7.23",
55
55
  "@bull-board/api": "3.7.0",
56
56
  "@bull-board/koa": "3.9.4",
57
57
  "@elastic/elasticsearch": "7.10.0",
@@ -97,7 +97,7 @@
97
97
  "koa2-ratelimit": "1.1.1",
98
98
  "lodash": "4.17.21",
99
99
  "memorystream": "0.3.1",
100
- "mongodb": "5.6",
100
+ "mongodb": "4.9",
101
101
  "mssql": "6.2.3",
102
102
  "mysql2": "2.3.3",
103
103
  "node-fetch": "2.6.7",
@@ -117,7 +117,7 @@
117
117
  "socket.io": "4.6.1",
118
118
  "svelte": "3.49.0",
119
119
  "swagger-parser": "10.0.3",
120
- "tar": "6.1.15",
120
+ "tar": "6.1.11",
121
121
  "to-json-schema": "0.2.5",
122
122
  "uuid": "3.3.2",
123
123
  "validate.js": "0.13.1",
@@ -150,7 +150,7 @@
150
150
  "@types/redis": "4.0.11",
151
151
  "@types/server-destroy": "1.0.1",
152
152
  "@types/supertest": "2.0.12",
153
- "@types/tar": "6.1.5",
153
+ "@types/tar": "6.1.3",
154
154
  "@typescript-eslint/parser": "5.45.0",
155
155
  "apidoc": "0.50.4",
156
156
  "babel-jest": "29.5.0",
@@ -195,5 +195,5 @@
195
195
  }
196
196
  }
197
197
  },
198
- "gitHead": "4c79ad23a8dac8fe3fd165602fae40db1d256fb5"
198
+ "gitHead": "807e9a80d749b8beac7fc57d38499b59036fdeb7"
199
199
  }
@@ -1,31 +1,17 @@
1
1
  import sdk from "../../sdk"
2
- import { events, context, db } from "@budibase/backend-core"
2
+ import { events, context } from "@budibase/backend-core"
3
3
  import { DocumentType } from "../../db/utils"
4
- import { Ctx } from "@budibase/types"
5
-
6
- interface ExportAppDumpRequest {
7
- excludeRows: boolean
8
- encryptPassword?: string
9
- }
10
-
11
- export async function exportAppDump(ctx: Ctx<ExportAppDumpRequest>) {
12
- const { appId } = ctx.query as any
13
- const { excludeRows, encryptPassword } = ctx.request.body
14
-
15
- const [app] = await db.getAppsByIDs([appId])
16
- const appName = app.name
4
+ import { isQsTrue } from "../../utilities"
17
5
 
6
+ export async function exportAppDump(ctx: any) {
7
+ let { appId, excludeRows } = ctx.query
18
8
  // remove the 120 second limit for the request
19
9
  ctx.req.setTimeout(0)
20
-
21
- const extension = encryptPassword ? "enc.tar.gz" : "tar.gz"
22
- const backupIdentifier = `${appName}-export-${new Date().getTime()}.${extension}`
10
+ const appName = decodeURI(ctx.query.appname)
11
+ excludeRows = isQsTrue(excludeRows)
12
+ const backupIdentifier = `${appName}-export-${new Date().getTime()}.tar.gz`
23
13
  ctx.attachment(backupIdentifier)
24
- ctx.body = await sdk.backups.streamExportApp({
25
- appId,
26
- excludeRows,
27
- encryptPassword,
28
- })
14
+ ctx.body = await sdk.backups.streamExportApp(appId, excludeRows)
29
15
 
30
16
  await context.doInAppContext(appId, async () => {
31
17
  const appDb = context.getAppDB()
@@ -11,7 +11,7 @@ import { BuildSchemaErrors, InvalidColumns } from "../../constants"
11
11
  import { getIntegration } from "../../integrations"
12
12
  import { getDatasourceAndQuery } from "./row/utils"
13
13
  import { invalidateDynamicVariables } from "../../threads/utils"
14
- import { db as dbCore, context, events, cache } from "@budibase/backend-core"
14
+ import { db as dbCore, context, events } from "@budibase/backend-core"
15
15
  import {
16
16
  UserCtx,
17
17
  Datasource,
@@ -25,11 +25,9 @@ import {
25
25
  FetchDatasourceInfoResponse,
26
26
  IntegrationBase,
27
27
  DatasourcePlus,
28
- SourceName,
29
28
  } from "@budibase/types"
30
29
  import sdk from "../../sdk"
31
30
  import { builderSocket } from "../../websockets"
32
- import { setupCreationAuth as googleSetupCreationAuth } from "../../integrations/googlesheets"
33
31
 
34
32
  function getErrorTables(errors: any, errorType: string) {
35
33
  return Object.entries(errors)
@@ -103,22 +101,6 @@ async function buildSchemaHelper(datasource: Datasource) {
103
101
  return { tables: connector.tables, error }
104
102
  }
105
103
 
106
- async function buildFilteredSchema(datasource: Datasource, filter?: string[]) {
107
- let { tables, error } = await buildSchemaHelper(datasource)
108
- let finalTables = tables
109
- if (filter) {
110
- finalTables = {}
111
- for (let key in tables) {
112
- if (
113
- filter.some((filter: any) => filter.toLowerCase() === key.toLowerCase())
114
- ) {
115
- finalTables[key] = tables[key]
116
- }
117
- }
118
- }
119
- return { tables: finalTables, error }
120
- }
121
-
122
104
  export async function fetch(ctx: UserCtx) {
123
105
  // Get internal tables
124
106
  const db = context.getAppDB()
@@ -190,28 +172,43 @@ export async function information(
190
172
  }
191
173
  const tableNames = await connector.getTableNames()
192
174
  ctx.body = {
193
- tableNames: tableNames.sort(),
175
+ tableNames,
194
176
  }
195
177
  }
196
178
 
197
179
  export async function buildSchemaFromDb(ctx: UserCtx) {
198
180
  const db = context.getAppDB()
199
- const tablesFilter = ctx.request.body.tablesFilter
200
181
  const datasource = await sdk.datasources.get(ctx.params.datasourceId)
182
+ const tablesFilter = ctx.request.body.tablesFilter
201
183
 
202
- const { tables, error } = await buildFilteredSchema(datasource, tablesFilter)
203
- datasource.entities = tables
184
+ let { tables, error } = await buildSchemaHelper(datasource)
185
+ if (tablesFilter) {
186
+ if (!datasource.entities) {
187
+ datasource.entities = {}
188
+ }
189
+ for (let key in tables) {
190
+ if (
191
+ tablesFilter.some(
192
+ (filter: any) => filter.toLowerCase() === key.toLowerCase()
193
+ )
194
+ ) {
195
+ datasource.entities[key] = tables[key]
196
+ }
197
+ }
198
+ } else {
199
+ datasource.entities = tables
200
+ }
204
201
 
205
202
  setDefaultDisplayColumns(datasource)
206
203
  const dbResp = await db.put(datasource)
207
204
  datasource._rev = dbResp.rev
208
205
  const cleanedDatasource = await sdk.datasources.removeSecretSingle(datasource)
209
206
 
210
- const res: any = { datasource: cleanedDatasource }
207
+ const response: any = { datasource: cleanedDatasource }
211
208
  if (error) {
212
- res.error = error
209
+ response.error = error
213
210
  }
214
- ctx.body = res
211
+ ctx.body = response
215
212
  }
216
213
 
217
214
  /**
@@ -309,19 +306,12 @@ export async function update(ctx: UserCtx<any, UpdateDatasourceResponse>) {
309
306
  builderSocket?.emitDatasourceUpdate(ctx, datasource)
310
307
  }
311
308
 
312
- const preSaveAction: Partial<Record<SourceName, any>> = {
313
- [SourceName.GOOGLE_SHEETS]: async (datasource: Datasource) => {
314
- await googleSetupCreationAuth(datasource.config as any)
315
- },
316
- }
317
-
318
309
  export async function save(
319
310
  ctx: UserCtx<CreateDatasourceRequest, CreateDatasourceResponse>
320
311
  ) {
321
312
  const db = context.getAppDB()
322
313
  const plus = ctx.request.body.datasource.plus
323
314
  const fetchSchema = ctx.request.body.fetchSchema
324
- const tablesFilter = ctx.request.body.tablesFilter
325
315
 
326
316
  const datasource = {
327
317
  _id: generateDatasourceID({ plus }),
@@ -331,19 +321,12 @@ export async function save(
331
321
 
332
322
  let schemaError = null
333
323
  if (fetchSchema) {
334
- const { tables, error } = await buildFilteredSchema(
335
- datasource,
336
- tablesFilter
337
- )
324
+ const { tables, error } = await buildSchemaHelper(datasource)
338
325
  schemaError = error
339
326
  datasource.entities = tables
340
327
  setDefaultDisplayColumns(datasource)
341
328
  }
342
329
 
343
- if (preSaveAction[datasource.source]) {
344
- await preSaveAction[datasource.source](datasource)
345
- }
346
-
347
330
  const dbResp = await db.put(datasource)
348
331
  await events.datasource.created(datasource)
349
332
  datasource._rev = dbResp.rev
@@ -4,7 +4,7 @@ import {
4
4
  getUserMetadataParams,
5
5
  InternalTables,
6
6
  } from "../../db/utils"
7
- import { UserCtx, Database } from "@budibase/types"
7
+ import { BBContext, Database } from "@budibase/types"
8
8
 
9
9
  const UpdateRolesOptions = {
10
10
  CREATED: "created",
@@ -38,15 +38,15 @@ async function updateRolesOnUserTable(
38
38
  }
39
39
  }
40
40
 
41
- export async function fetch(ctx: UserCtx) {
41
+ export async function fetch(ctx: BBContext) {
42
42
  ctx.body = await roles.getAllRoles()
43
43
  }
44
44
 
45
- export async function find(ctx: UserCtx) {
45
+ export async function find(ctx: BBContext) {
46
46
  ctx.body = await roles.getRole(ctx.params.roleId)
47
47
  }
48
48
 
49
- export async function save(ctx: UserCtx) {
49
+ export async function save(ctx: BBContext) {
50
50
  const db = context.getAppDB()
51
51
  let { _id, name, inherits, permissionId } = ctx.request.body
52
52
  let isCreate = false
@@ -72,7 +72,7 @@ export async function save(ctx: UserCtx) {
72
72
  ctx.message = `Role '${role.name}' created successfully.`
73
73
  }
74
74
 
75
- export async function destroy(ctx: UserCtx) {
75
+ export async function destroy(ctx: BBContext) {
76
76
  const db = context.getAppDB()
77
77
  const roleId = ctx.params.roleId
78
78
  const role = await db.get(roleId)
@@ -1,6 +1,6 @@
1
1
  import { getRoutingInfo } from "../../utilities/routing"
2
2
  import { roles } from "@budibase/backend-core"
3
- import { UserCtx } from "@budibase/types"
3
+ import { BBContext } from "@budibase/types"
4
4
 
5
5
  const URL_SEPARATOR = "/"
6
6
 
@@ -56,11 +56,11 @@ async function getRoutingStructure() {
56
56
  return { routes: routing.json }
57
57
  }
58
58
 
59
- export async function fetch(ctx: UserCtx) {
59
+ export async function fetch(ctx: BBContext) {
60
60
  ctx.body = await getRoutingStructure()
61
61
  }
62
62
 
63
- export async function clientFetch(ctx: UserCtx) {
63
+ export async function clientFetch(ctx: BBContext) {
64
64
  const routing = await getRoutingStructure()
65
65
  let roleId = ctx.user?.role?._id
66
66
  const roleIds = (await roles.getUserRoleHierarchy(roleId, {
@@ -5,7 +5,7 @@ import { permissions } from "@budibase/backend-core"
5
5
 
6
6
  const router: Router = new Router()
7
7
 
8
- router.post(
8
+ router.get(
9
9
  "/api/backups/export",
10
10
  authorized(permissions.BUILDER),
11
11
  controller.exportAppDump
@@ -1,9 +1,7 @@
1
- import tk from "timekeeper"
2
1
  import * as setup from "./utilities"
3
2
  import { events } from "@budibase/backend-core"
4
3
  import sdk from "../../../sdk"
5
4
  import { checkBuilderEndpoint } from "./utilities/TestFunctions"
6
- import { mocks } from "@budibase/backend-core/tests"
7
5
 
8
6
  describe("/backups", () => {
9
7
  let request = setup.getRequest()
@@ -18,7 +16,7 @@ describe("/backups", () => {
18
16
  describe("exportAppDump", () => {
19
17
  it("should be able to export app", async () => {
20
18
  const res = await request
21
- .post(`/api/backups/export?appId=${config.getAppId()}`)
19
+ .get(`/api/backups/export?appId=${config.getAppId()}&appname=test`)
22
20
  .set(config.defaultHeaders())
23
21
  .expect(200)
24
22
  expect(res.headers["content-type"]).toEqual("application/gzip")
@@ -28,24 +26,10 @@ describe("/backups", () => {
28
26
  it("should apply authorization to endpoint", async () => {
29
27
  await checkBuilderEndpoint({
30
28
  config,
31
- method: "POST",
29
+ method: "GET",
32
30
  url: `/api/backups/export?appId=${config.getAppId()}`,
33
31
  })
34
32
  })
35
-
36
- it("should infer the app name from the app", async () => {
37
- tk.freeze(mocks.date.MOCK_DATE)
38
-
39
- const res = await request
40
- .post(`/api/backups/export?appId=${config.getAppId()}`)
41
- .set(config.defaultHeaders())
42
-
43
- expect(res.headers["content-disposition"]).toEqual(
44
- `attachment; filename="${
45
- config.getApp()!.name
46
- }-export-${mocks.date.MOCK_DATE.getTime()}.tar.gz"`
47
- )
48
- })
49
33
  })
50
34
 
51
35
  describe("calculateBackupStats", () => {
@@ -48,35 +48,6 @@ export const definition: AutomationStepSchema = {
48
48
  type: AutomationIOType.STRING,
49
49
  title: "HTML Contents",
50
50
  },
51
- addInvite: {
52
- type: AutomationIOType.BOOLEAN,
53
- title: "Add calendar invite",
54
- },
55
- startTime: {
56
- type: AutomationIOType.DATE,
57
- title: "Start Time",
58
- dependsOn: "addInvite",
59
- },
60
- endTime: {
61
- type: AutomationIOType.DATE,
62
- title: "End Time",
63
- dependsOn: "addInvite",
64
- },
65
- summary: {
66
- type: AutomationIOType.STRING,
67
- title: "Meeting Summary",
68
- dependsOn: "addInvite",
69
- },
70
- location: {
71
- type: AutomationIOType.STRING,
72
- title: "Location",
73
- dependsOn: "addInvite",
74
- },
75
- url: {
76
- type: AutomationIOType.STRING,
77
- title: "URL",
78
- dependsOn: "addInvite",
79
- },
80
51
  },
81
52
  required: ["to", "from", "subject", "contents"],
82
53
  },
@@ -97,43 +68,21 @@ export const definition: AutomationStepSchema = {
97
68
  }
98
69
 
99
70
  export async function run({ inputs }: AutomationStepInput) {
100
- let {
101
- to,
102
- from,
103
- subject,
104
- contents,
105
- cc,
106
- bcc,
107
- addInvite,
108
- startTime,
109
- endTime,
110
- summary,
111
- location,
112
- url,
113
- } = inputs
71
+ let { to, from, subject, contents, cc, bcc } = inputs
114
72
  if (!contents) {
115
73
  contents = "<h1>No content</h1>"
116
74
  }
117
75
  to = to || undefined
118
76
  try {
119
- let response = await sendSmtpEmail({
77
+ let response = await sendSmtpEmail(
120
78
  to,
121
79
  from,
122
80
  subject,
123
81
  contents,
124
82
  cc,
125
83
  bcc,
126
- automation: true,
127
- invite: addInvite
128
- ? {
129
- startTime,
130
- endTime,
131
- summary,
132
- location,
133
- url,
134
- }
135
- : undefined,
136
- })
84
+ true
85
+ )
137
86
  return {
138
87
  success: true,
139
88
  response,
@@ -0,0 +1,71 @@
1
+
2
+ function generateResponse(to, from) {
3
+ return {
4
+ "success": true,
5
+ "response": {
6
+ "accepted": [
7
+ to
8
+ ],
9
+ "envelope": {
10
+ "from": from,
11
+ "to": [
12
+ to
13
+ ]
14
+ },
15
+ "message": `Email sent to ${to}.`
16
+ }
17
+
18
+ }
19
+ }
20
+
21
+ const mockFetch = jest.fn(() => ({
22
+ headers: {
23
+ raw: () => {
24
+ return { "content-type": ["application/json"] }
25
+ },
26
+ get: () => ["application/json"],
27
+ },
28
+ json: jest.fn(() => response),
29
+ status: 200,
30
+ text: jest.fn(),
31
+ }))
32
+ jest.mock("node-fetch", () => mockFetch)
33
+ const setup = require("./utilities")
34
+
35
+
36
+ describe("test the outgoing webhook action", () => {
37
+ let inputs
38
+ let config = setup.getConfig()
39
+ beforeAll(async () => {
40
+ await config.init()
41
+ })
42
+
43
+ afterAll(setup.afterAll)
44
+
45
+ it("should be able to run the action", async () => {
46
+ inputs = {
47
+ to: "user1@test.com",
48
+ from: "admin@test.com",
49
+ subject: "hello",
50
+ contents: "testing",
51
+ }
52
+ let resp = generateResponse(inputs.to, inputs.from)
53
+ mockFetch.mockImplementationOnce(() => ({
54
+ headers: {
55
+ raw: () => {
56
+ return { "content-type": ["application/json"] }
57
+ },
58
+ get: () => ["application/json"],
59
+ },
60
+ json: jest.fn(() => resp),
61
+ status: 200,
62
+ text: jest.fn(),
63
+ }))
64
+ const res = await setup.runStep(setup.actions.SEND_EMAIL_SMTP.stepId, inputs)
65
+ expect(res.response).toEqual(resp)
66
+ expect(res.success).toEqual(true)
67
+
68
+ })
69
+
70
+
71
+ })
@@ -26,10 +26,6 @@ export default function process(updateCb?: UpdateCallback) {
26
26
  // if something not found - no changes to perform
27
27
  if (err?.status === 404) {
28
28
  return
29
- }
30
- // The user has already been sync in another process
31
- else if (err?.status === 409) {
32
- return
33
29
  } else {
34
30
  logging.logAlert("Failed to perform user/group app sync", err)
35
31
  }
@@ -1,6 +1,5 @@
1
1
  import {
2
2
  ConnectionInfo,
3
- Datasource,
4
3
  DatasourceFeature,
5
4
  DatasourceFieldType,
6
5
  DatasourcePlus,
@@ -20,15 +19,13 @@ import { OAuth2Client } from "google-auth-library"
20
19
  import { buildExternalTableId, finaliseExternalTables } from "./utils"
21
20
  import { GoogleSpreadsheet, GoogleSpreadsheetRow } from "google-spreadsheet"
22
21
  import fetch from "node-fetch"
23
- import { cache, configs, context, HTTPError } from "@budibase/backend-core"
24
- import { dataFilters, utils } from "@budibase/shared-core"
22
+ import { configs, HTTPError } from "@budibase/backend-core"
23
+ import { dataFilters } from "@budibase/shared-core"
25
24
  import { GOOGLE_SHEETS_PRIMARY_KEY } from "../constants"
26
- import sdk from "../sdk"
27
25
 
28
26
  interface GoogleSheetsConfig {
29
27
  spreadsheetId: string
30
28
  auth: OAuthClientConfig
31
- continueSetupId?: string
32
29
  }
33
30
 
34
31
  interface OAuthClientConfig {
@@ -75,7 +72,7 @@ const SCHEMA: Integration = {
75
72
  },
76
73
  datasource: {
77
74
  spreadsheetId: {
78
- display: "Spreadsheet URL",
75
+ display: "Google Sheet URL",
79
76
  type: DatasourceFieldType.STRING,
80
77
  required: true,
81
78
  },
@@ -210,8 +207,6 @@ class GoogleSheetsIntegration implements DatasourcePlus {
210
207
 
211
208
  async connect() {
212
209
  try {
213
- await setupCreationAuth(this.config)
214
-
215
210
  // Initialise oAuth client
216
211
  let googleConfig = await configs.getGoogleDatasourceConfig()
217
212
  if (!googleConfig) {
@@ -274,24 +269,24 @@ class GoogleSheetsIntegration implements DatasourcePlus {
274
269
  }
275
270
 
276
271
  async buildSchema(datasourceId: string, entities: Record<string, Table>) {
272
+ // not fully configured yet
273
+ if (!this.config.auth) {
274
+ return
275
+ }
277
276
  await this.connect()
278
277
  const sheets = this.client.sheetsByIndex
279
278
  const tables: Record<string, Table> = {}
280
- await utils.parallelForeach(
281
- sheets,
282
- async sheet => {
283
- // must fetch rows to determine schema
284
- await sheet.getRows({ limit: 0, offset: 0 })
285
-
286
- const id = buildExternalTableId(datasourceId, sheet.title)
287
- tables[sheet.title] = this.getTableSchema(
288
- sheet.title,
289
- sheet.headerValues,
290
- id
291
- )
292
- },
293
- 10
294
- )
279
+ for (let sheet of sheets) {
280
+ // must fetch rows to determine schema
281
+ await sheet.getRows()
282
+
283
+ const id = buildExternalTableId(datasourceId, sheet.title)
284
+ tables[sheet.title] = this.getTableSchema(
285
+ sheet.title,
286
+ sheet.headerValues,
287
+ id
288
+ )
289
+ }
295
290
  const final = finaliseExternalTables(tables, entities)
296
291
  this.tables = final.tables
297
292
  this.schemaErrors = final.errors
@@ -571,18 +566,6 @@ class GoogleSheetsIntegration implements DatasourcePlus {
571
566
  }
572
567
  }
573
568
 
574
- export async function setupCreationAuth(datasouce: GoogleSheetsConfig) {
575
- if (datasouce.continueSetupId) {
576
- const appId = context.getAppId()
577
- const tokens = await cache.get(
578
- `datasource:creation:${appId}:google:${datasouce.continueSetupId}`
579
- )
580
-
581
- datasouce.auth = tokens.tokens
582
- delete datasouce.continueSetupId
583
- }
584
- }
585
-
586
569
  export default {
587
570
  schema: SCHEMA,
588
571
  integration: GoogleSheetsIntegration,
@@ -351,7 +351,7 @@ const SCHEMA: Integration = getSchema()
351
351
 
352
352
  class MongoIntegration implements IntegrationBase {
353
353
  private config: MongoDBConfig
354
- private client: MongoClient
354
+ private client: any
355
355
 
356
356
  constructor(config: MongoDBConfig) {
357
357
  this.config = config
@@ -372,8 +372,6 @@ class MongoIntegration implements IntegrationBase {
372
372
  response.connected = true
373
373
  } catch (e: any) {
374
374
  response.error = e.message as string
375
- } finally {
376
- await this.client.close()
377
375
  }
378
376
  return response
379
377
  }
@@ -382,7 +380,7 @@ class MongoIntegration implements IntegrationBase {
382
380
  return this.client.connect()
383
381
  }
384
382
 
385
- createObjectIds(json: any) {
383
+ createObjectIds(json: any): object {
386
384
  const self = this
387
385
  function interpolateObjectIds(json: any) {
388
386
  for (let field of Object.keys(json)) {