@budibase/server 2.3.18-alpha.3 → 2.3.18-alpha.30

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 (93) hide show
  1. package/__mocks__/node-fetch.ts +3 -0
  2. package/builder/assets/blankScreenPreview.72634dd1.png +0 -0
  3. package/builder/assets/{index.9f78787d.js → index.11e16372.js} +429 -414
  4. package/builder/assets/index.b24b9dea.css +6 -0
  5. package/builder/assets/listScreenPreview.599c0aae.png +0 -0
  6. package/builder/index.html +2 -2
  7. package/dist/api/controllers/automation.js +11 -2
  8. package/dist/api/controllers/cloud.js +2 -2
  9. package/dist/api/controllers/row/ExternalRequest.js +49 -24
  10. package/dist/api/controllers/row/external.js +1 -1
  11. package/dist/api/controllers/row/internalSearch.js +6 -450
  12. package/dist/api/controllers/row/utils.js +1 -3
  13. package/dist/api/routes/automation.js +1 -1
  14. package/dist/api/routes/public/applications.js +7 -7
  15. package/dist/api/routes/public/queries.js +2 -2
  16. package/dist/api/routes/public/rows.js +5 -5
  17. package/dist/api/routes/public/tables.js +5 -5
  18. package/dist/api/routes/public/users.js +5 -5
  19. package/dist/app.js +2 -0
  20. package/dist/db/index.js +25 -2
  21. package/dist/db/utils.js +2 -5
  22. package/dist/db/views/staticViews.js +2 -1
  23. package/dist/integrations/base/sql.js +4 -8
  24. package/dist/integrations/googlesheets.js +17 -20
  25. package/dist/middleware/authorized.js +5 -3
  26. package/dist/middleware/builder.js +6 -3
  27. package/dist/migrations/functions/backfill/global/configs.js +10 -4
  28. package/dist/migrations/tests/helpers.js +1 -1
  29. package/dist/migrations/tests/structures.js +1 -1
  30. package/dist/package.json +9 -8
  31. package/dist/sdk/app/backups/constants.js +2 -1
  32. package/dist/sdk/app/backups/exports.js +12 -5
  33. package/dist/sdk/app/rows/attachments.js +1 -1
  34. package/dist/startup.js +3 -0
  35. package/dist/tsconfig.build.tsbuildinfo +1 -1
  36. package/jest.config.ts +1 -0
  37. package/package.json +10 -9
  38. package/scripts/test.sh +12 -0
  39. package/specs/{generate.js → generate.ts} +7 -9
  40. package/specs/openapi.json +24 -24
  41. package/specs/openapi.yaml +24 -24
  42. package/specs/{parameters.js → parameters.ts} +6 -6
  43. package/specs/resources/{application.js → application.ts} +4 -4
  44. package/specs/resources/{index.js → index.ts} +8 -8
  45. package/specs/resources/{misc.js → misc.ts} +3 -3
  46. package/specs/resources/{query.js → query.ts} +4 -4
  47. package/specs/resources/{row.js → row.ts} +3 -4
  48. package/specs/resources/{table.js → table.ts} +5 -5
  49. package/specs/resources/{user.js → user.ts} +3 -3
  50. package/specs/resources/utils/Resource.ts +39 -0
  51. package/specs/resources/utils/{index.js → index.ts} +1 -1
  52. package/specs/{security.js → security.ts} +1 -1
  53. package/src/api/controllers/automation.ts +13 -2
  54. package/src/api/controllers/cloud.ts +2 -2
  55. package/src/api/controllers/row/ExternalRequest.ts +95 -28
  56. package/src/api/controllers/row/external.ts +1 -1
  57. package/src/api/controllers/row/internalSearch.ts +11 -524
  58. package/src/api/controllers/row/utils.ts +1 -2
  59. package/src/api/routes/automation.ts +1 -1
  60. package/src/api/routes/public/applications.ts +7 -7
  61. package/src/api/routes/public/queries.ts +2 -2
  62. package/src/api/routes/public/rows.ts +5 -5
  63. package/src/api/routes/public/tables.ts +5 -5
  64. package/src/api/routes/public/tests/{compare.spec.js → compare.spec.ts} +44 -25
  65. package/src/api/routes/public/users.ts +5 -5
  66. package/src/api/routes/tests/{cloud.seq.spec.ts → cloud.spec.ts} +13 -20
  67. package/src/api/routes/tests/utilities/TestFunctions.ts +1 -2
  68. package/src/app.ts +2 -0
  69. package/src/db/index.ts +2 -2
  70. package/src/db/utils.ts +0 -4
  71. package/src/db/views/staticViews.ts +3 -3
  72. package/src/definitions/openapi.ts +449 -63
  73. package/src/integration-test/postgres.spec.ts +351 -81
  74. package/src/integrations/base/sql.ts +4 -8
  75. package/src/integrations/googlesheets.ts +21 -22
  76. package/src/integrations/tests/googlesheets.spec.ts +122 -0
  77. package/src/middleware/authorized.ts +6 -4
  78. package/src/middleware/builder.ts +8 -3
  79. package/src/migrations/functions/backfill/global/configs.ts +15 -9
  80. package/src/migrations/functions/tests/userEmailViewCasing.spec.js +3 -4
  81. package/src/migrations/tests/helpers.ts +2 -2
  82. package/src/migrations/tests/structures.ts +1 -0
  83. package/src/sdk/app/backups/constants.ts +1 -0
  84. package/src/sdk/app/backups/exports.ts +24 -8
  85. package/src/sdk/app/rows/attachments.ts +1 -1
  86. package/src/startup.ts +4 -1
  87. package/src/tests/jestEnv.ts +1 -0
  88. package/src/tests/utilities/TestConfiguration.ts +42 -30
  89. package/src/tests/utilities/structures.ts +0 -2
  90. package/builder/assets/index.7e76c039.css +0 -6
  91. package/dist/integrations/base/utils.js +0 -16
  92. package/specs/resources/utils/Resource.js +0 -26
  93. package/src/integrations/base/utils.ts +0 -12
@@ -11,9 +11,8 @@ import { OAuth2Client } from "google-auth-library"
11
11
  import { buildExternalTableId } from "./utils"
12
12
  import { DataSourceOperation, FieldTypes } from "../constants"
13
13
  import { GoogleSpreadsheet } from "google-spreadsheet"
14
- import env from "../environment"
15
- import { tenancy, db as dbCore, constants } from "@budibase/backend-core"
16
- const fetch = require("node-fetch")
14
+ import fetch from "node-fetch"
15
+ import { configs, HTTPError } from "@budibase/backend-core"
17
16
 
18
17
  interface GoogleSheetsConfig {
19
18
  spreadsheetId: string
@@ -112,7 +111,7 @@ const SCHEMA: Integration = {
112
111
 
113
112
  class GoogleSheetsIntegration implements DatasourcePlus {
114
113
  private readonly config: GoogleSheetsConfig
115
- private client: any
114
+ private client: GoogleSpreadsheet
116
115
  public tables: Record<string, Table> = {}
117
116
  public schemaErrors: Record<string, string> = {}
118
117
 
@@ -173,16 +172,9 @@ class GoogleSheetsIntegration implements DatasourcePlus {
173
172
  async connect() {
174
173
  try {
175
174
  // Initialise oAuth client
176
- const db = tenancy.getGlobalDB()
177
- let googleConfig = await dbCore.getScopedConfig(db, {
178
- type: constants.Config.GOOGLE,
179
- })
180
-
175
+ let googleConfig = await configs.getGoogleDatasourceConfig()
181
176
  if (!googleConfig) {
182
- googleConfig = {
183
- clientID: env.GOOGLE_CLIENT_ID,
184
- clientSecret: env.GOOGLE_CLIENT_SECRET,
185
- }
177
+ throw new HTTPError("Google config not found", 400)
186
178
  }
187
179
 
188
180
  const oauthClient = new OAuth2Client({
@@ -211,7 +203,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
211
203
 
212
204
  async buildSchema(datasourceId: string) {
213
205
  await this.connect()
214
- const sheets = await this.client.sheetsByIndex
206
+ const sheets = this.client.sheetsByIndex
215
207
  const tables: Record<string, Table> = {}
216
208
  for (let sheet of sheets) {
217
209
  // must fetch rows to determine schema
@@ -294,7 +286,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
294
286
  async updateTable(table?: any) {
295
287
  try {
296
288
  await this.connect()
297
- const sheet = await this.client.sheetsByTitle[table.name]
289
+ const sheet = this.client.sheetsByTitle[table.name]
298
290
  await sheet.loadHeaderRow()
299
291
 
300
292
  if (table._rename) {
@@ -308,10 +300,17 @@ class GoogleSheetsIntegration implements DatasourcePlus {
308
300
  }
309
301
  await sheet.setHeaderRow(headers)
310
302
  } else {
311
- let newField = Object.keys(table.schema).find(
303
+ const updatedHeaderValues = [...sheet.headerValues]
304
+
305
+ const newField = Object.keys(table.schema).find(
312
306
  key => !sheet.headerValues.includes(key)
313
307
  )
314
- await sheet.setHeaderRow([...sheet.headerValues, newField])
308
+
309
+ if (newField) {
310
+ updatedHeaderValues.push(newField)
311
+ }
312
+
313
+ await sheet.setHeaderRow(updatedHeaderValues)
315
314
  }
316
315
  } catch (err) {
317
316
  console.error("Error updating table in google sheets", err)
@@ -322,7 +321,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
322
321
  async deleteTable(sheet: any) {
323
322
  try {
324
323
  await this.connect()
325
- const sheetToDelete = await this.client.sheetsByTitle[sheet]
324
+ const sheetToDelete = this.client.sheetsByTitle[sheet]
326
325
  return await sheetToDelete.delete()
327
326
  } catch (err) {
328
327
  console.error("Error deleting table in google sheets", err)
@@ -333,7 +332,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
333
332
  async create(query: { sheet: string; row: any }) {
334
333
  try {
335
334
  await this.connect()
336
- const sheet = await this.client.sheetsByTitle[query.sheet]
335
+ const sheet = this.client.sheetsByTitle[query.sheet]
337
336
  const rowToInsert =
338
337
  typeof query.row === "string" ? JSON.parse(query.row) : query.row
339
338
  const row = await sheet.addRow(rowToInsert)
@@ -349,7 +348,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
349
348
  async read(query: { sheet: string }) {
350
349
  try {
351
350
  await this.connect()
352
- const sheet = await this.client.sheetsByTitle[query.sheet]
351
+ const sheet = this.client.sheetsByTitle[query.sheet]
353
352
  const rows = await sheet.getRows()
354
353
  const headerValues = sheet.headerValues
355
354
  const response = []
@@ -368,7 +367,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
368
367
  async update(query: { sheet: string; rowIndex: number; row: any }) {
369
368
  try {
370
369
  await this.connect()
371
- const sheet = await this.client.sheetsByTitle[query.sheet]
370
+ const sheet = this.client.sheetsByTitle[query.sheet]
372
371
  const rows = await sheet.getRows()
373
372
  const row = rows[query.rowIndex]
374
373
  if (row) {
@@ -392,7 +391,7 @@ class GoogleSheetsIntegration implements DatasourcePlus {
392
391
 
393
392
  async delete(query: { sheet: string; rowIndex: number }) {
394
393
  await this.connect()
395
- const sheet = await this.client.sheetsByTitle[query.sheet]
394
+ const sheet = this.client.sheetsByTitle[query.sheet]
396
395
  const rows = await sheet.getRows()
397
396
  const row = rows[query.rowIndex]
398
397
  if (row) {
@@ -0,0 +1,122 @@
1
+ import type { GoogleSpreadsheetWorksheet } from "google-spreadsheet"
2
+
3
+ jest.mock("google-auth-library")
4
+ const { OAuth2Client } = require("google-auth-library")
5
+
6
+ const setCredentialsMock = jest.fn()
7
+ const getAccessTokenMock = jest.fn()
8
+
9
+ OAuth2Client.mockImplementation(() => {
10
+ return {
11
+ setCredentials: setCredentialsMock,
12
+ getAccessToken: getAccessTokenMock,
13
+ }
14
+ })
15
+
16
+ jest.mock("google-spreadsheet")
17
+ const { GoogleSpreadsheet } = require("google-spreadsheet")
18
+
19
+ const sheetsByTitle: { [title: string]: GoogleSpreadsheetWorksheet } = {}
20
+
21
+ GoogleSpreadsheet.mockImplementation(() => {
22
+ return {
23
+ useOAuth2Client: jest.fn(),
24
+ loadInfo: jest.fn(),
25
+ sheetsByTitle,
26
+ }
27
+ })
28
+
29
+ import { structures } from "@budibase/backend-core/tests"
30
+ import TestConfiguration from "../../tests/utilities/TestConfiguration"
31
+ import GoogleSheetsIntegration from "../googlesheets"
32
+ import { FieldType, Table, TableSchema } from "../../../../types/src/documents"
33
+
34
+ describe("Google Sheets Integration", () => {
35
+ let integration: any,
36
+ config = new TestConfiguration()
37
+
38
+ beforeEach(async () => {
39
+ integration = new GoogleSheetsIntegration.integration({
40
+ spreadsheetId: "randomId",
41
+ auth: {
42
+ appId: "appId",
43
+ accessToken: "accessToken",
44
+ refreshToken: "refreshToken",
45
+ },
46
+ })
47
+ await config.init()
48
+ })
49
+
50
+ function createBasicTable(name: string, columns: string[]): Table {
51
+ return {
52
+ name,
53
+ schema: {
54
+ ...columns.reduce((p, c) => {
55
+ p[c] = {
56
+ name: c,
57
+ type: FieldType.STRING,
58
+ constraints: {
59
+ type: "string",
60
+ },
61
+ }
62
+ return p
63
+ }, {} as TableSchema),
64
+ },
65
+ }
66
+ }
67
+
68
+ function createSheet({
69
+ headerValues,
70
+ }: {
71
+ headerValues: string[]
72
+ }): GoogleSpreadsheetWorksheet {
73
+ return {
74
+ // to ignore the unmapped fields
75
+ ...({} as any),
76
+ loadHeaderRow: jest.fn(),
77
+ headerValues,
78
+ setHeaderRow: jest.fn(),
79
+ }
80
+ }
81
+
82
+ describe("update table", () => {
83
+ test("adding a new field will be adding a new header row", async () => {
84
+ await config.doInContext(structures.uuid(), async () => {
85
+ const tableColumns = ["name", "description", "new field"]
86
+ const table = createBasicTable(structures.uuid(), tableColumns)
87
+
88
+ const sheet = createSheet({ headerValues: ["name", "description"] })
89
+ sheetsByTitle[table.name] = sheet
90
+ await integration.updateTable(table)
91
+
92
+ expect(sheet.loadHeaderRow).toBeCalledTimes(1)
93
+ expect(sheet.setHeaderRow).toBeCalledTimes(1)
94
+ expect(sheet.setHeaderRow).toBeCalledWith(tableColumns)
95
+ })
96
+ })
97
+
98
+ test("removing an existing field will not remove the data from the spreadsheet", async () => {
99
+ await config.doInContext(structures.uuid(), async () => {
100
+ const tableColumns = ["name"]
101
+ const table = createBasicTable(structures.uuid(), tableColumns)
102
+
103
+ const sheet = createSheet({
104
+ headerValues: ["name", "description", "location"],
105
+ })
106
+ sheetsByTitle[table.name] = sheet
107
+ await integration.updateTable(table)
108
+
109
+ expect(sheet.loadHeaderRow).toBeCalledTimes(1)
110
+ expect(sheet.setHeaderRow).toBeCalledTimes(1)
111
+ expect(sheet.setHeaderRow).toBeCalledWith([
112
+ "name",
113
+ "description",
114
+ "location",
115
+ ])
116
+
117
+ // No undefineds are sent
118
+ expect((sheet.setHeaderRow as any).mock.calls[0][0]).toHaveLength(3)
119
+ })
120
+ })
121
+ })
122
+ })
@@ -79,10 +79,6 @@ export default (
79
79
  return ctx.throw(403, "No user info found")
80
80
  }
81
81
 
82
- // check general builder stuff, this middleware is a good way
83
- // to find API endpoints which are builder focused
84
- await builderMiddleware(ctx, permType)
85
-
86
82
  // get the resource roles
87
83
  let resourceRoles: any = []
88
84
  let otherLevelRoles: any = []
@@ -112,6 +108,12 @@ export default (
112
108
  return ctx.throw(403, "Session not authenticated")
113
109
  }
114
110
 
111
+ // check general builder stuff, this middleware is a good way
112
+ // to find API endpoints which are builder focused
113
+ if (permType === permissions.PermissionType.BUILDER) {
114
+ await builderMiddleware(ctx)
115
+ }
116
+
115
117
  try {
116
118
  // check authorized
117
119
  await checkAuthorized(ctx, resourceRoles, permType, permLevel)
@@ -64,13 +64,18 @@ async function updateAppUpdatedAt(ctx: BBContext) {
64
64
  })
65
65
  }
66
66
 
67
- export default async function builder(ctx: BBContext, permType: string) {
67
+ export default async function builder(ctx: BBContext) {
68
68
  const appId = ctx.appId
69
69
  // this only functions within an app context
70
70
  if (!appId) {
71
71
  return
72
72
  }
73
- const isBuilderApi = permType === permissions.PermissionType.BUILDER
73
+
74
+ // check authenticated
75
+ if (!ctx.isAuthenticated) {
76
+ return ctx.throw(403, "Session not authenticated")
77
+ }
78
+
74
79
  const referer = ctx.headers["referer"]
75
80
 
76
81
  const overviewPath = "/builder/portal/overview/"
@@ -82,7 +87,7 @@ export default async function builder(ctx: BBContext, permType: string) {
82
87
  const hasAppId = !referer ? false : referer.includes(appId)
83
88
  const editingApp = referer ? hasAppId : false
84
89
  // check this is a builder call and editing
85
- if (!isBuilderApi || !editingApp) {
90
+ if (!editingApp) {
86
91
  return
87
92
  }
88
93
  // check locks
@@ -1,4 +1,9 @@
1
- import { events, db as dbUtils } from "@budibase/backend-core"
1
+ import {
2
+ events,
3
+ DocumentType,
4
+ SEPARATOR,
5
+ UNICODE_MAX,
6
+ } from "@budibase/backend-core"
2
7
  import {
3
8
  Config,
4
9
  isSMTPConfig,
@@ -9,15 +14,16 @@ import {
9
14
  } from "@budibase/types"
10
15
  import env from "./../../../../environment"
11
16
 
17
+ export const getConfigParams = () => {
18
+ return {
19
+ include_docs: true,
20
+ startkey: `${DocumentType.CONFIG}${SEPARATOR}`,
21
+ endkey: `${DocumentType.CONFIG}${SEPARATOR}${UNICODE_MAX}`,
22
+ }
23
+ }
24
+
12
25
  const getConfigs = async (globalDb: any): Promise<Config[]> => {
13
- const response = await globalDb.allDocs(
14
- dbUtils.getConfigParams(
15
- {},
16
- {
17
- include_docs: true,
18
- }
19
- )
20
- )
26
+ const response = await globalDb.allDocs(getConfigParams())
21
27
  return response.rows.map((row: any) => row.doc)
22
28
  }
23
29
 
@@ -8,9 +8,8 @@ jest.mock("@budibase/backend-core", () => {
8
8
  }
9
9
  }
10
10
  })
11
- const { tenancy, db: dbCore } = require("@budibase/backend-core")
11
+ const { context, db: dbCore } = require("@budibase/backend-core")
12
12
  const TestConfig = require("../../../tests/utilities/TestConfiguration")
13
- const { TENANT_ID } = require("../../../tests/utilities/structures")
14
13
 
15
14
  // mock email view creation
16
15
 
@@ -26,8 +25,8 @@ describe("run", () => {
26
25
  afterAll(config.end)
27
26
 
28
27
  it("runs successfully", async () => {
29
- await tenancy.doInTenant(TENANT_ID, async () => {
30
- const globalDb = tenancy.getGlobalDB()
28
+ await config.doInTenant(async () => {
29
+ const globalDb = context.getGlobalDB()
31
30
  await migration.run(globalDb)
32
31
  expect(dbCore.createNewUserEmailView).toHaveBeenCalledTimes(1)
33
32
  })
@@ -1,7 +1,7 @@
1
1
  // Mimic configs test configuration from worker, creation configs directly in database
2
2
 
3
3
  import * as structures from "./structures"
4
- import { db } from "@budibase/backend-core"
4
+ import { configs } from "@budibase/backend-core"
5
5
  import { Config } from "@budibase/types"
6
6
 
7
7
  export const saveSettingsConfig = async (globalDb: any) => {
@@ -25,7 +25,7 @@ export const saveSmtpConfig = async (globalDb: any) => {
25
25
  }
26
26
 
27
27
  const saveConfig = async (config: Config, globalDb: any) => {
28
- config._id = db.generateConfigID({ type: config.type })
28
+ config._id = configs.generateConfigID(config.type)
29
29
 
30
30
  let response
31
31
  try {
@@ -20,6 +20,7 @@ export const oidc = (conf?: OIDCConfig): OIDCConfig => {
20
20
  name: "Active Directory",
21
21
  uuid: utils.newid(),
22
22
  activated: true,
23
+ scopes: [],
23
24
  ...conf,
24
25
  },
25
26
  ],
@@ -1,2 +1,3 @@
1
1
  export const DB_EXPORT_FILE = "db.txt"
2
2
  export const GLOBAL_DB_EXPORT_FILE = "global.txt"
3
+ export const STATIC_APP_FILES = ["manifest.json", "budibase-client.js"]
@@ -7,10 +7,15 @@ import {
7
7
  TABLE_ROW_PREFIX,
8
8
  USER_METDATA_PREFIX,
9
9
  } from "../../../db/utils"
10
- import { DB_EXPORT_FILE, GLOBAL_DB_EXPORT_FILE } from "./constants"
10
+ import {
11
+ DB_EXPORT_FILE,
12
+ GLOBAL_DB_EXPORT_FILE,
13
+ STATIC_APP_FILES,
14
+ } from "./constants"
11
15
  import fs from "fs"
12
16
  import { join } from "path"
13
17
  import env from "../../../environment"
18
+
14
19
  const uuid = require("uuid/v4")
15
20
  const tar = require("tar")
16
21
  const MemoryStream = require("memorystream")
@@ -91,14 +96,25 @@ export async function exportApp(appId: string, config?: ExportOpts) {
91
96
  const prodAppId = dbCore.getProdAppID(appId)
92
97
  const appPath = `${prodAppId}/`
93
98
  // export bucket contents
94
- let tmpPath
99
+ let tmpPath = createTempFolder(uuid())
95
100
  if (!env.isTest()) {
96
- tmpPath = await objectStore.retrieveDirectory(
97
- ObjectStoreBuckets.APPS,
98
- appPath
99
- )
100
- } else {
101
- tmpPath = createTempFolder(uuid())
101
+ // write just the static files
102
+ if (config?.excludeRows) {
103
+ for (let path of STATIC_APP_FILES) {
104
+ const contents = await objectStore.retrieve(
105
+ ObjectStoreBuckets.APPS,
106
+ join(appPath, path)
107
+ )
108
+ fs.writeFileSync(join(tmpPath, path), contents)
109
+ }
110
+ }
111
+ // get all of the files
112
+ else {
113
+ tmpPath = await objectStore.retrieveDirectory(
114
+ ObjectStoreBuckets.APPS,
115
+ appPath
116
+ )
117
+ }
102
118
  }
103
119
  const downloadedPath = join(tmpPath, appPath)
104
120
  if (fs.existsSync(downloadedPath)) {
@@ -13,13 +13,13 @@ function generateAttachmentFindParams(
13
13
  ) {
14
14
  const params: CouchFindOptions = {
15
15
  selector: {
16
+ $or: attachmentCols.map(col => ({ [col]: { $exists: true } })),
16
17
  _id: {
17
18
  $regex: `^${DocumentType.ROW}${SEPARATOR}${tableId}`,
18
19
  },
19
20
  },
20
21
  limit: FIND_LIMIT,
21
22
  }
22
- attachmentCols.forEach(col => (params.selector[col] = { $exists: true }))
23
23
  if (bookmark) {
24
24
  params.bookmark = bookmark
25
25
  }
package/src/startup.ts CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  generateApiKey,
6
6
  getChecklist,
7
7
  } from "./utilities/workerRequests"
8
- import { installation, tenancy, logging } from "@budibase/backend-core"
8
+ import { installation, tenancy, logging, events } from "@budibase/backend-core"
9
9
  import fs from "fs"
10
10
  import { watch } from "./watch"
11
11
  import * as automations from "./automations"
@@ -124,6 +124,9 @@ export async function startup(app?: any, server?: any) {
124
124
  // get the references to the queue promises, don't await as
125
125
  // they will never end, unless the processing stops
126
126
  let queuePromises = []
127
+ // configure events to use the pro audit log write
128
+ // can't integrate directly into backend-core due to cyclic issues
129
+ queuePromises.push(events.processors.init(pro.sdk.auditLogs.write))
127
130
  queuePromises.push(automations.init())
128
131
  queuePromises.push(initPro())
129
132
  if (app) {
@@ -8,3 +8,4 @@ process.env.BUDIBASE_DIR = tmpdir("budibase-unittests")
8
8
  process.env.LOG_LEVEL = process.env.LOG_LEVEL || "error"
9
9
  process.env.ENABLE_4XX_HTTP_LOGGING = "0"
10
10
  process.env.MOCK_REDIS = "1"
11
+ process.env.PLATFORM_URL = "http://localhost:10000"
@@ -21,7 +21,6 @@ import {
21
21
  basicScreen,
22
22
  basicLayout,
23
23
  basicWebhook,
24
- TENANT_ID,
25
24
  } from "./structures"
26
25
  import {
27
26
  constants,
@@ -41,8 +40,8 @@ import { generateUserMetadataID } from "../../db/utils"
41
40
  import { startup } from "../../startup"
42
41
  import supertest from "supertest"
43
42
  import {
43
+ App,
44
44
  AuthToken,
45
- Database,
46
45
  Datasource,
47
46
  Row,
48
47
  SourceName,
@@ -63,7 +62,7 @@ class TestConfiguration {
63
62
  started: boolean
64
63
  appId: string | null
65
64
  allApps: any[]
66
- app: any
65
+ app?: App
67
66
  prodApp: any
68
67
  prodAppId: any
69
68
  user: any
@@ -73,7 +72,7 @@ class TestConfiguration {
73
72
  linkedTable: any
74
73
  automation: any
75
74
  datasource: any
76
- tenantId: string | null
75
+ tenantId?: string
77
76
  defaultUserValues: DefaultUserValues
78
77
 
79
78
  constructor(openServer = true) {
@@ -89,7 +88,6 @@ class TestConfiguration {
89
88
  }
90
89
  this.appId = null
91
90
  this.allApps = []
92
- this.tenantId = null
93
91
  this.defaultUserValues = this.populateDefaultUserValues()
94
92
  }
95
93
 
@@ -154,19 +152,10 @@ class TestConfiguration {
154
152
 
155
153
  // use a new id as the name to avoid name collisions
156
154
  async init(appName = newid()) {
157
- this.defaultUserValues = this.populateDefaultUserValues()
158
- if (context.isMultiTenant()) {
159
- this.tenantId = structures.tenant.id()
160
- }
161
-
162
155
  if (!this.started) {
163
156
  await startup()
164
157
  }
165
- this.user = await this.globalUser()
166
- this.globalUserId = this.user._id
167
- this.userMetadataId = generateUserMetadataID(this.globalUserId)
168
-
169
- return this.createApp(appName)
158
+ return this.newTenant(appName)
170
159
  }
171
160
 
172
161
  end() {
@@ -182,24 +171,22 @@ class TestConfiguration {
182
171
  }
183
172
 
184
173
  // MODES
185
- #setMultiTenancy = (value: boolean) => {
174
+ setMultiTenancy = (value: boolean) => {
186
175
  env._set("MULTI_TENANCY", value)
187
176
  coreEnv._set("MULTI_TENANCY", value)
188
177
  }
189
178
 
190
- #setSelfHosted = (value: boolean) => {
179
+ setSelfHosted = (value: boolean) => {
191
180
  env._set("SELF_HOSTED", value)
192
181
  coreEnv._set("SELF_HOSTED", value)
193
182
  }
194
183
 
195
184
  modeCloud = () => {
196
- this.#setSelfHosted(false)
197
- this.#setMultiTenancy(true)
185
+ this.setSelfHosted(false)
198
186
  }
199
187
 
200
188
  modeSelf = () => {
201
- this.#setSelfHosted(true)
202
- this.#setMultiTenancy(false)
189
+ this.setSelfHosted(true)
203
190
  }
204
191
 
205
192
  // UTILS
@@ -354,6 +341,8 @@ class TestConfiguration {
354
341
  })
355
342
  }
356
343
 
344
+ // HEADERS
345
+
357
346
  defaultHeaders(extras = {}) {
358
347
  const tenantId = this.getTenantId()
359
348
  const authObj: AuthToken = {
@@ -374,6 +363,7 @@ class TestConfiguration {
374
363
  `${constants.Cookie.CurrentApp}=${appToken}`,
375
364
  ],
376
365
  [constants.Header.CSRF_TOKEN]: this.defaultUserValues.csrfToken,
366
+ Host: this.tenantHost(),
377
367
  ...extras,
378
368
  }
379
369
 
@@ -383,10 +373,6 @@ class TestConfiguration {
383
373
  return headers
384
374
  }
385
375
 
386
- getTenantId() {
387
- return this.tenantId || TENANT_ID
388
- }
389
-
390
376
  publicHeaders({ prodApp = true } = {}) {
391
377
  const appId = prodApp ? this.prodAppId : this.appId
392
378
 
@@ -397,9 +383,7 @@ class TestConfiguration {
397
383
  headers[constants.Header.APP_ID] = appId
398
384
  }
399
385
 
400
- if (this.tenantId) {
401
- headers[constants.Header.TENANT_ID] = this.tenantId
402
- }
386
+ headers[constants.Header.TENANT_ID] = this.getTenantId()
403
387
 
404
388
  return headers
405
389
  }
@@ -413,6 +397,34 @@ class TestConfiguration {
413
397
  return this.login({ email, roleId, builder, prodApp })
414
398
  }
415
399
 
400
+ // TENANCY
401
+
402
+ tenantHost() {
403
+ const tenantId = this.getTenantId()
404
+ const platformHost = new URL(coreEnv.PLATFORM_URL).host.split(":")[0]
405
+ return `${tenantId}.${platformHost}`
406
+ }
407
+
408
+ getTenantId() {
409
+ if (!this.tenantId) {
410
+ throw new Error("no test tenant id - init has not been called")
411
+ }
412
+ return this.tenantId
413
+ }
414
+
415
+ async newTenant(appName = newid()): Promise<App> {
416
+ this.defaultUserValues = this.populateDefaultUserValues()
417
+ this.tenantId = structures.tenant.id()
418
+ this.user = await this.globalUser()
419
+ this.globalUserId = this.user._id
420
+ this.userMetadataId = generateUserMetadataID(this.globalUserId)
421
+ return this.createApp(appName)
422
+ }
423
+
424
+ doInTenant(task: any) {
425
+ return context.doInTenant(this.getTenantId(), task)
426
+ }
427
+
416
428
  // API
417
429
 
418
430
  async generateApiKey(userId = this.defaultUserValues.globalUserId) {
@@ -432,7 +444,7 @@ class TestConfiguration {
432
444
  }
433
445
 
434
446
  // APP
435
- async createApp(appName: string) {
447
+ async createApp(appName: string): Promise<App> {
436
448
  // create dev app
437
449
  // clear any old app
438
450
  this.appId = null
@@ -442,7 +454,7 @@ class TestConfiguration {
442
454
  null,
443
455
  controllers.app.create
444
456
  )
445
- this.appId = this.app.appId
457
+ this.appId = this.app?.appId!
446
458
  })
447
459
  return await context.doInAppContext(this.appId, async () => {
448
460
  // create production app
@@ -13,8 +13,6 @@ import {
13
13
 
14
14
  const { v4: uuidv4 } = require("uuid")
15
15
 
16
- export const TENANT_ID = "default"
17
-
18
16
  export function basicTable() {
19
17
  return {
20
18
  name: "TestTable",