@budibase/server 2.4.42 → 2.4.44-alpha.0

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 (73) hide show
  1. package/__mocks__/node-fetch.ts +6 -1
  2. package/builder/assets/{index.3d64bc07.js → index.6846a382.js} +385 -384
  3. package/builder/assets/index.7f9a008b.css +6 -0
  4. package/builder/index.html +7 -7
  5. package/dist/api/controllers/application.js +28 -24
  6. package/dist/api/controllers/public/metrics.js +113 -0
  7. package/dist/api/controllers/row/external.js +15 -0
  8. package/dist/api/controllers/row/utils.js +4 -3
  9. package/dist/api/controllers/static/index.js +84 -24
  10. package/dist/api/controllers/static/templates/BudibaseApp.svelte +34 -11
  11. package/dist/api/controllers/table/utils.js +2 -4
  12. package/dist/api/routes/public/index.js +8 -0
  13. package/dist/api/routes/public/metrics.js +30 -0
  14. package/dist/app.js +1 -0
  15. package/dist/integrations/googlesheets.js +4 -0
  16. package/dist/integrations/redis.js +1 -1
  17. package/dist/middleware/currentapp.js +1 -27
  18. package/dist/package.json +12 -11
  19. package/dist/sdk/users/utils.js +11 -6
  20. package/dist/tsconfig.build.tsbuildinfo +1 -1
  21. package/dist/utilities/fileSystem/app.js +1 -10
  22. package/dist/utilities/fileSystem/filesystem.js +0 -4
  23. package/dist/utilities/global.js +17 -7
  24. package/jest.config.ts +2 -0
  25. package/package.json +13 -12
  26. package/scripts/test.sh +6 -4
  27. package/specs/openapi.json +39 -0
  28. package/specs/openapi.yaml +169 -0
  29. package/specs/resources/application.ts +11 -0
  30. package/specs/resources/index.ts +2 -0
  31. package/specs/resources/metrics.ts +81 -0
  32. package/src/api/controllers/application.ts +20 -21
  33. package/src/api/controllers/component.ts +2 -2
  34. package/src/api/controllers/public/metrics.ts +251 -0
  35. package/src/api/controllers/row/external.ts +14 -0
  36. package/src/api/controllers/row/utils.ts +4 -3
  37. package/src/api/controllers/static/index.ts +69 -26
  38. package/src/api/controllers/static/templates/BudibaseApp.svelte +34 -11
  39. package/src/api/controllers/table/tests/utils.spec.ts +97 -0
  40. package/src/api/controllers/table/utils.ts +20 -12
  41. package/src/api/controllers/view/tests/__snapshots__/viewBuilder.spec.js.snap +48 -48
  42. package/src/api/routes/public/index.ts +10 -1
  43. package/src/api/routes/public/metrics.ts +28 -0
  44. package/src/api/routes/public/tests/metrics.spec.js +34 -0
  45. package/src/api/routes/tests/__snapshots__/datasource.spec.ts.snap +22 -22
  46. package/src/api/routes/tests/__snapshots__/view.spec.js.snap +5 -5
  47. package/src/api/routes/tests/appSync.spec.ts +31 -0
  48. package/src/api/routes/tests/internalSearch.spec.js +8 -7
  49. package/src/app.ts +2 -1
  50. package/src/automations/automationUtils.ts +1 -1
  51. package/src/automations/tests/automation.spec.ts +99 -0
  52. package/src/db/defaultData/datasource_bb_default.ts +1 -1
  53. package/src/definitions/openapi.ts +15 -0
  54. package/src/integration-test/postgres.spec.ts +46 -52
  55. package/src/integrations/googlesheets.ts +4 -0
  56. package/src/integrations/redis.ts +1 -1
  57. package/src/integrations/tests/googlesheets.spec.ts +13 -13
  58. package/src/integrations/tests/redis.spec.ts +9 -5
  59. package/src/middleware/currentapp.ts +3 -32
  60. package/src/middleware/tests/currentapp.spec.js +6 -42
  61. package/src/sdk/users/tests/utils.spec.ts +159 -0
  62. package/src/sdk/users/utils.ts +18 -7
  63. package/src/tests/jestEnv.ts +1 -0
  64. package/src/tests/jestSetup.ts +5 -1
  65. package/src/tests/utilities/TestConfiguration.ts +18 -19
  66. package/src/tests/utilities/structures.ts +13 -1
  67. package/src/utilities/fileSystem/app.ts +2 -9
  68. package/src/utilities/fileSystem/filesystem.ts +0 -4
  69. package/src/utilities/global.ts +21 -9
  70. package/src/utilities/rowProcessor/index.ts +1 -1
  71. package/builder/assets/favicon.e7fc7733.png +0 -0
  72. package/builder/assets/index.b0e3aca6.css +0 -6
  73. package/src/automations/tests/automation.spec.js +0 -84
@@ -0,0 +1,159 @@
1
+ import { db, roles } from "@budibase/backend-core"
2
+ import { structures } from "@budibase/backend-core/tests"
3
+ import { sdk as proSdk } from "@budibase/pro"
4
+
5
+ import TestConfiguration from "../../../tests/utilities/TestConfiguration"
6
+ import { rawUserMetadata, syncGlobalUsers } from "../utils"
7
+
8
+ describe("syncGlobalUsers", () => {
9
+ const config = new TestConfiguration()
10
+
11
+ beforeEach(async () => {
12
+ await config.init()
13
+ })
14
+
15
+ afterAll(config.end)
16
+
17
+ it("the default user is synced", async () => {
18
+ await config.doInContext(config.appId, async () => {
19
+ await syncGlobalUsers()
20
+
21
+ const metadata = await rawUserMetadata()
22
+ expect(metadata).toHaveLength(1)
23
+ expect(metadata).toEqual([
24
+ expect.objectContaining({
25
+ _id: db.generateUserMetadataID(config.user._id),
26
+ }),
27
+ ])
28
+ })
29
+ })
30
+
31
+ it("admin and builders users are synced", async () => {
32
+ const user1 = await config.createUser({ admin: true })
33
+ const user2 = await config.createUser({ admin: false, builder: true })
34
+ await config.doInContext(config.appId, async () => {
35
+ expect(await rawUserMetadata()).toHaveLength(1)
36
+ await syncGlobalUsers()
37
+
38
+ const metadata = await rawUserMetadata()
39
+ expect(metadata).toHaveLength(3)
40
+ expect(metadata).toContainEqual(
41
+ expect.objectContaining({
42
+ _id: db.generateUserMetadataID(user1._id),
43
+ })
44
+ )
45
+ expect(metadata).toContainEqual(
46
+ expect.objectContaining({
47
+ _id: db.generateUserMetadataID(user2._id),
48
+ })
49
+ )
50
+ })
51
+ })
52
+
53
+ it("app users are not synced if not specified", async () => {
54
+ const user = await config.createUser({ admin: false, builder: false })
55
+ await config.doInContext(config.appId, async () => {
56
+ await syncGlobalUsers()
57
+
58
+ const metadata = await rawUserMetadata()
59
+ expect(metadata).toHaveLength(1)
60
+ expect(metadata).not.toContainEqual(
61
+ expect.objectContaining({
62
+ _id: db.generateUserMetadataID(user._id),
63
+ })
64
+ )
65
+ })
66
+ })
67
+
68
+ it("app users are added when group is assigned to app", async () => {
69
+ await config.doInTenant(async () => {
70
+ const group = await proSdk.groups.save(structures.userGroups.userGroup())
71
+ const user1 = await config.createUser({ admin: false, builder: false })
72
+ const user2 = await config.createUser({ admin: false, builder: false })
73
+ await proSdk.groups.addUsers(group.id, [user1._id, user2._id])
74
+
75
+ await config.doInContext(config.appId, async () => {
76
+ await syncGlobalUsers()
77
+ expect(await rawUserMetadata()).toHaveLength(1)
78
+
79
+ await proSdk.groups.updateGroupApps(group.id, {
80
+ appsToAdd: [
81
+ { appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC },
82
+ ],
83
+ })
84
+ await syncGlobalUsers()
85
+
86
+ const metadata = await rawUserMetadata()
87
+ expect(metadata).toHaveLength(3)
88
+ expect(metadata).toContainEqual(
89
+ expect.objectContaining({
90
+ _id: db.generateUserMetadataID(user1._id),
91
+ })
92
+ )
93
+ expect(metadata).toContainEqual(
94
+ expect.objectContaining({
95
+ _id: db.generateUserMetadataID(user2._id),
96
+ })
97
+ )
98
+ })
99
+ })
100
+ })
101
+
102
+ it("app users are removed when app is removed from user group", async () => {
103
+ await config.doInTenant(async () => {
104
+ const group = await proSdk.groups.save(structures.userGroups.userGroup())
105
+ const user1 = await config.createUser({ admin: false, builder: false })
106
+ const user2 = await config.createUser({ admin: false, builder: false })
107
+ await proSdk.groups.updateGroupApps(group.id, {
108
+ appsToAdd: [
109
+ { appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC },
110
+ ],
111
+ })
112
+ await proSdk.groups.addUsers(group.id, [user1._id, user2._id])
113
+
114
+ await config.doInContext(config.appId, async () => {
115
+ await syncGlobalUsers()
116
+ expect(await rawUserMetadata()).toHaveLength(3)
117
+
118
+ await proSdk.groups.updateGroupApps(group.id, {
119
+ appsToRemove: [{ appId: config.prodAppId! }],
120
+ })
121
+ await syncGlobalUsers()
122
+
123
+ const metadata = await rawUserMetadata()
124
+ expect(metadata).toHaveLength(1)
125
+ })
126
+ })
127
+ })
128
+
129
+ it("app users are removed when app is removed from user group", async () => {
130
+ await config.doInTenant(async () => {
131
+ const group = await proSdk.groups.save(structures.userGroups.userGroup())
132
+ const user1 = await config.createUser({ admin: false, builder: false })
133
+ const user2 = await config.createUser({ admin: false, builder: false })
134
+ await proSdk.groups.updateGroupApps(group.id, {
135
+ appsToAdd: [
136
+ { appId: config.prodAppId!, roleId: roles.BUILTIN_ROLE_IDS.BASIC },
137
+ ],
138
+ })
139
+ await proSdk.groups.addUsers(group.id, [user1._id, user2._id])
140
+
141
+ await config.doInContext(config.appId, async () => {
142
+ await syncGlobalUsers()
143
+ expect(await rawUserMetadata()).toHaveLength(3)
144
+
145
+ await proSdk.groups.removeUsers(group.id, [user1._id])
146
+ await syncGlobalUsers()
147
+
148
+ const metadata = await rawUserMetadata()
149
+ expect(metadata).toHaveLength(2)
150
+
151
+ expect(metadata).not.toContainEqual(
152
+ expect.objectContaining({
153
+ _id: db.generateUserMetadataID(user1._id),
154
+ })
155
+ )
156
+ })
157
+ })
158
+ })
159
+ })
@@ -6,22 +6,33 @@ import {
6
6
  InternalTables,
7
7
  } from "../../db/utils"
8
8
  import { isEqual } from "lodash"
9
+ import { ContextUser, UserMetadata } from "@budibase/types"
9
10
 
10
- export function combineMetadataAndUser(user: any, metadata: any) {
11
+ export function combineMetadataAndUser(
12
+ user: ContextUser,
13
+ metadata: UserMetadata | UserMetadata[]
14
+ ) {
15
+ const metadataId = generateUserMetadataID(user._id!)
16
+ const found = Array.isArray(metadata)
17
+ ? metadata.find(doc => doc._id === metadataId)
18
+ : metadata
11
19
  // skip users with no access
12
- if (user.roleId === rolesCore.BUILTIN_ROLE_IDS.PUBLIC) {
20
+ if (
21
+ user.roleId == null ||
22
+ user.roleId === rolesCore.BUILTIN_ROLE_IDS.PUBLIC
23
+ ) {
24
+ // If it exists and it should not, we must remove it
25
+ if (found?._id) {
26
+ return { ...found, _deleted: true }
27
+ }
13
28
  return null
14
29
  }
15
30
  delete user._rev
16
- const metadataId = generateUserMetadataID(user._id)
17
31
  const newDoc = {
18
32
  ...user,
19
33
  _id: metadataId,
20
34
  tableId: InternalTables.USER_METADATA,
21
35
  }
22
- const found = Array.isArray(metadata)
23
- ? metadata.find(doc => doc._id === metadataId)
24
- : metadata
25
36
  // copy rev over for the purposes of equality check
26
37
  if (found) {
27
38
  newDoc._rev = found._rev
@@ -55,7 +66,7 @@ export async function syncGlobalUsers() {
55
66
  ])
56
67
  const toWrite = []
57
68
  for (let user of users) {
58
- const combined = await combineMetadataAndUser(user, metadata)
69
+ const combined = combineMetadataAndUser(user, metadata)
59
70
  if (combined) {
60
71
  toWrite.push(combined)
61
72
  }
@@ -9,3 +9,4 @@ 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
11
  process.env.PLATFORM_URL = "http://localhost:10000"
12
+ process.env.REDIS_PASSWORD = "budibase"
@@ -1,6 +1,6 @@
1
1
  import "./logging"
2
2
  import env from "../environment"
3
- import { env as coreEnv } from "@budibase/backend-core"
3
+ import { env as coreEnv, timers } from "@budibase/backend-core"
4
4
  import { testContainerUtils } from "@budibase/backend-core/tests"
5
5
 
6
6
  if (!process.env.DEBUG) {
@@ -17,3 +17,7 @@ if (!process.env.CI) {
17
17
  }
18
18
 
19
19
  testContainerUtils.setupEnv(env, coreEnv)
20
+
21
+ afterAll(() => {
22
+ timers.cleanup()
23
+ })
@@ -46,6 +46,8 @@ import {
46
46
  Row,
47
47
  SourceName,
48
48
  Table,
49
+ SearchFilters,
50
+ UserRoles,
49
51
  } from "@budibase/types"
50
52
 
51
53
  type DefaultUserValues = {
@@ -164,6 +166,8 @@ class TestConfiguration {
164
166
  }
165
167
  if (this.server) {
166
168
  this.server.close()
169
+ } else {
170
+ require("../../app").default.close()
167
171
  }
168
172
  if (this.allApps) {
169
173
  cleanup(this.allApps.map(app => app.appId))
@@ -274,7 +278,7 @@ class TestConfiguration {
274
278
  email?: string
275
279
  builder?: boolean
276
280
  admin?: boolean
277
- roles?: any
281
+ roles?: UserRoles
278
282
  } = {}
279
283
  ) {
280
284
  let { id, firstName, lastName, email, builder, admin, roles } = user
@@ -327,21 +331,13 @@ class TestConfiguration {
327
331
  sessionId: "sessionid",
328
332
  tenantId: this.getTenantId(),
329
333
  }
330
- const app = {
331
- roleId: roleId,
332
- appId,
333
- }
334
334
  const authToken = auth.jwt.sign(authObj, coreEnv.JWT_SECRET)
335
- const appToken = auth.jwt.sign(app, coreEnv.JWT_SECRET)
336
335
 
337
336
  // returning necessary request headers
338
337
  await cache.user.invalidateUser(userId)
339
338
  return {
340
339
  Accept: "application/json",
341
- Cookie: [
342
- `${constants.Cookie.Auth}=${authToken}`,
343
- `${constants.Cookie.CurrentApp}=${appToken}`,
344
- ],
340
+ Cookie: [`${constants.Cookie.Auth}=${authToken}`],
345
341
  [constants.Header.APP_ID]: appId,
346
342
  }
347
343
  })
@@ -356,18 +352,11 @@ class TestConfiguration {
356
352
  sessionId: "sessionid",
357
353
  tenantId,
358
354
  }
359
- const app = {
360
- roleId: roles.BUILTIN_ROLE_IDS.ADMIN,
361
- appId: this.appId,
362
- }
363
355
  const authToken = auth.jwt.sign(authObj, coreEnv.JWT_SECRET)
364
- const appToken = auth.jwt.sign(app, coreEnv.JWT_SECRET)
356
+
365
357
  const headers: any = {
366
358
  Accept: "application/json",
367
- Cookie: [
368
- `${constants.Cookie.Auth}=${authToken}`,
369
- `${constants.Cookie.CurrentApp}=${appToken}`,
370
- ],
359
+ Cookie: [`${constants.Cookie.Auth}=${authToken}`],
371
360
  [constants.Header.CSRF_TOKEN]: this.defaultUserValues.csrfToken,
372
361
  Host: this.tenantHost(),
373
362
  ...extras,
@@ -568,6 +557,16 @@ class TestConfiguration {
568
557
  return this._req(null, { tableId }, controllers.row.fetch)
569
558
  }
570
559
 
560
+ async searchRows(tableId: string, searchParams: SearchFilters = {}) {
561
+ if (!tableId && this.table) {
562
+ tableId = this.table._id
563
+ }
564
+ const body = {
565
+ query: searchParams,
566
+ }
567
+ return this._req(body, { tableId }, controllers.row.search)
568
+ }
569
+
571
570
  // ROLE
572
571
 
573
572
  async createRole(config?: any) {
@@ -106,7 +106,7 @@ export function newAutomation({ steps, trigger }: any = {}) {
106
106
  return automation
107
107
  }
108
108
 
109
- export function basicAutomation() {
109
+ export function basicAutomation(appId?: string) {
110
110
  return {
111
111
  name: "My Automation",
112
112
  screenId: "kasdkfldsafkl",
@@ -114,11 +114,23 @@ export function basicAutomation() {
114
114
  uiTree: {},
115
115
  definition: {
116
116
  trigger: {
117
+ stepId: AutomationTriggerStepId.APP,
118
+ name: "test",
119
+ tagline: "test",
120
+ icon: "test",
121
+ description: "test",
122
+ type: "trigger",
123
+ id: "test",
117
124
  inputs: {},
125
+ schema: {
126
+ inputs: {},
127
+ outputs: {},
128
+ },
118
129
  },
119
130
  steps: [],
120
131
  },
121
132
  type: "automation",
133
+ appId,
122
134
  }
123
135
  }
124
136
 
@@ -33,15 +33,8 @@ export const deleteApp = async (appId: string) => {
33
33
  export const getComponentLibraryManifest = async (library: string) => {
34
34
  const appId = context.getAppId()
35
35
  const filename = "manifest.json"
36
- /* istanbul ignore next */
37
- // when testing in cypress and so on we need to get the package
38
- // as the environment may not be fully fleshed out for dev or prod
39
- if (env.isTest()) {
40
- library = library.replace("standard-components", "client")
41
- const lib = library.split("/")[1]
42
- const path = require.resolve(library).split(lib)[0]
43
- return require(join(path, lib, filename))
44
- } else if (env.isDev()) {
36
+
37
+ if (env.isDev() || env.isTest()) {
45
38
  const path = join(NODE_MODULES_PATH, "@budibase", "client", filename)
46
39
  // always load from new so that updates are refreshed
47
40
  delete require.cache[require.resolve(path)]
@@ -24,10 +24,6 @@ export const init = () => {
24
24
  }
25
25
  }
26
26
  }
27
- const clientLibPath = join(budibaseTempDir(), "budibase-client.js")
28
- if (env.isTest() && !fs.existsSync(clientLibPath)) {
29
- fs.copyFileSync(require.resolve("@budibase/client"), clientLibPath)
30
- }
31
27
  }
32
28
 
33
29
  /**
@@ -8,7 +8,7 @@ import {
8
8
  } from "@budibase/backend-core"
9
9
  import env from "../environment"
10
10
  import { groups } from "@budibase/pro"
11
- import { BBContext, ContextUser, User } from "@budibase/types"
11
+ import { UserCtx, ContextUser, User, UserGroup } from "@budibase/types"
12
12
 
13
13
  export function updateAppRole(
14
14
  user: ContextUser,
@@ -43,33 +43,40 @@ export function updateAppRole(
43
43
 
44
44
  async function checkGroupRoles(
45
45
  user: ContextUser,
46
- { appId }: { appId?: string } = {}
46
+ opts: { appId?: string; groups?: UserGroup[] } = {}
47
47
  ) {
48
48
  if (user.roleId && user.roleId !== roles.BUILTIN_ROLE_IDS.PUBLIC) {
49
49
  return user
50
50
  }
51
- if (appId) {
52
- user.roleId = await groups.getGroupRoleId(user as User, appId)
51
+ if (opts.appId) {
52
+ user.roleId = await groups.getGroupRoleId(user as User, opts.appId, {
53
+ groups: opts.groups,
54
+ })
55
+ }
56
+ // final fallback, simply couldn't find a role - user must be public
57
+ if (!user.roleId) {
58
+ user.roleId = roles.BUILTIN_ROLE_IDS.PUBLIC
53
59
  }
54
60
  return user
55
61
  }
56
62
 
57
63
  async function processUser(
58
64
  user: ContextUser,
59
- { appId }: { appId?: string } = {}
65
+ opts: { appId?: string; groups?: UserGroup[] } = {}
60
66
  ) {
61
67
  if (user) {
62
68
  delete user.password
63
69
  }
64
- user = await updateAppRole(user, { appId })
70
+ const appId = opts.appId || context.getAppId()
71
+ user = updateAppRole(user, { appId })
65
72
  if (!user.roleId && user?.userGroups?.length) {
66
- user = await checkGroupRoles(user, { appId })
73
+ user = await checkGroupRoles(user, { appId, groups: opts?.groups })
67
74
  }
68
75
 
69
76
  return user
70
77
  }
71
78
 
72
- export async function getCachedSelf(ctx: BBContext, appId: string) {
79
+ export async function getCachedSelf(ctx: UserCtx, appId: string) {
73
80
  // this has to be tenant aware, can't depend on the context to find it out
74
81
  // running some middlewares before the tenancy causes context to break
75
82
  const user = await cache.user.getUser(ctx.user?._id!)
@@ -90,6 +97,7 @@ export async function getGlobalUser(userId: string) {
90
97
  export async function getGlobalUsers(users?: ContextUser[]) {
91
98
  const appId = context.getAppId()
92
99
  const db = tenancy.getGlobalDB()
100
+ const allGroups = await groups.fetch()
93
101
  let globalUsers
94
102
  if (users) {
95
103
  const globalIds = users.map(user =>
@@ -118,7 +126,11 @@ export async function getGlobalUsers(users?: ContextUser[]) {
118
126
  return globalUsers
119
127
  }
120
128
 
121
- return globalUsers.map(user => updateAppRole(user))
129
+ // pass in the groups, meaning we don't actually need to retrieve them for
130
+ // each user individually
131
+ return Promise.all(
132
+ globalUsers.map(user => processUser(user, { groups: allGroups }))
133
+ )
122
134
  }
123
135
 
124
136
  export async function getGlobalUsersFromMetadata(users: ContextUser[]) {
@@ -131,7 +131,7 @@ export function coerce(row: any, type: string) {
131
131
  * @returns {object} the row which has been prepared to be written to the DB.
132
132
  */
133
133
  export function inputProcessing(
134
- user: ContextUser,
134
+ user: ContextUser | null,
135
135
  table: Table,
136
136
  row: Row,
137
137
  opts?: AutoColumnProcessingOpts
Binary file