@budibase/server 2.5.9 → 2.5.10-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 (187) hide show
  1. package/builder/assets/index.24635afb.js +1794 -0
  2. package/builder/assets/index.4eae16b2.css +6 -0
  3. package/builder/index.html +2 -2
  4. package/dist/api/controllers/application.js +3 -4
  5. package/dist/api/controllers/automation.js +13 -7
  6. package/dist/api/controllers/datasource.js +1 -1
  7. package/dist/api/controllers/dev.js +1 -1
  8. package/dist/api/controllers/ops.js +40 -0
  9. package/dist/api/controllers/plugin/index.js +6 -37
  10. package/dist/api/controllers/query/index.js +2 -2
  11. package/dist/api/controllers/row/ExternalRequest.js +21 -14
  12. package/dist/api/controllers/table/utils.js +9 -3
  13. package/dist/api/controllers/user.js +1 -83
  14. package/dist/api/index.js +1 -2
  15. package/dist/api/routes/index.js +2 -2
  16. package/dist/api/routes/{cloud.js → ops.js} +19 -6
  17. package/dist/api/routes/user.js +0 -1
  18. package/dist/app.js +4 -13
  19. package/dist/automations/actions.js +32 -6
  20. package/dist/automations/index.js +3 -2
  21. package/dist/automations/steps/bash.js +6 -6
  22. package/dist/automations/steps/createRow.js +11 -11
  23. package/dist/automations/steps/delay.js +3 -3
  24. package/dist/automations/steps/deleteRow.js +8 -8
  25. package/dist/automations/steps/discord.js +8 -8
  26. package/dist/automations/steps/executeQuery.js +9 -9
  27. package/dist/automations/steps/executeScript.js +6 -6
  28. package/dist/automations/steps/filter.js +6 -6
  29. package/dist/automations/steps/integromat.js +10 -10
  30. package/dist/automations/steps/loop.js +9 -9
  31. package/dist/automations/steps/outgoingWebhook.js +10 -10
  32. package/dist/automations/steps/queryRows.js +14 -14
  33. package/dist/automations/steps/sendSmtpEmail.js +9 -9
  34. package/dist/automations/steps/serverLog.js +4 -4
  35. package/dist/automations/steps/slack.js +6 -6
  36. package/dist/automations/steps/updateRow.js +11 -11
  37. package/dist/automations/steps/zapier.js +9 -9
  38. package/dist/automations/triggerInfo/app.js +5 -5
  39. package/dist/automations/triggerInfo/cron.js +4 -4
  40. package/dist/automations/triggerInfo/rowDeleted.js +5 -5
  41. package/dist/automations/triggerInfo/rowSaved.js +7 -7
  42. package/dist/automations/triggerInfo/rowUpdated.js +7 -7
  43. package/dist/automations/triggerInfo/webhook.js +6 -6
  44. package/dist/db/utils.js +3 -2
  45. package/dist/environment.js +0 -1
  46. package/dist/events/docUpdates/index.js +17 -0
  47. package/dist/events/docUpdates/processors.js +18 -0
  48. package/dist/events/docUpdates/syncUsers.js +49 -0
  49. package/dist/events/index.js +3 -0
  50. package/dist/integrations/base/sqlTable.js +9 -2
  51. package/dist/integrations/index.js +3 -3
  52. package/dist/integrations/microsoftSqlServer.js +5 -2
  53. package/dist/integrations/mysql.js +5 -3
  54. package/dist/integrations/postgres.js +7 -5
  55. package/dist/integrations/redis.js +7 -0
  56. package/dist/integrations/rest.js +4 -0
  57. package/dist/migrations/functions/syncQuotas.js +2 -0
  58. package/dist/migrations/functions/usageQuotas/syncApps.js +1 -2
  59. package/dist/migrations/functions/usageQuotas/syncRows.js +1 -2
  60. package/dist/migrations/functions/usageQuotas/syncUsers.js +21 -0
  61. package/dist/sdk/app/applications/sync.js +117 -23
  62. package/dist/sdk/app/backups/exports.js +14 -38
  63. package/dist/sdk/index.js +2 -0
  64. package/dist/sdk/plugins/index.js +27 -0
  65. package/dist/sdk/plugins/plugins.js +53 -0
  66. package/dist/sdk/users/utils.js +21 -4
  67. package/dist/startup.js +31 -28
  68. package/dist/threads/automation.js +16 -5
  69. package/dist/tsconfig.build.tsbuildinfo +1 -1
  70. package/dist/utilities/fileSystem/plugin.js +33 -23
  71. package/dist/utilities/global.js +17 -12
  72. package/dist/utilities/rowProcessor/utils.js +4 -5
  73. package/dist/watch.js +2 -2
  74. package/dist/websockets/client.js +14 -0
  75. package/dist/websockets/grid.js +60 -0
  76. package/dist/websockets/index.js +17 -0
  77. package/dist/websockets/websocket.js +78 -0
  78. package/package.json +16 -16
  79. package/scripts/dev/manage.js +2 -0
  80. package/scripts/integrations/mssql/data/entrypoint.sh +1 -0
  81. package/scripts/integrations/mssql/data/setup.sql +17 -17
  82. package/scripts/integrations/mysql/init.sql +1 -1
  83. package/scripts/integrations/postgres/init.sql +1 -0
  84. package/src/api/controllers/application.ts +4 -4
  85. package/src/api/controllers/automation.ts +12 -6
  86. package/src/api/controllers/datasource.ts +15 -5
  87. package/src/api/controllers/dev.ts +2 -2
  88. package/src/api/controllers/ops.ts +32 -0
  89. package/src/api/controllers/plugin/index.ts +8 -45
  90. package/src/api/controllers/query/index.ts +2 -2
  91. package/src/api/controllers/row/ExternalRequest.ts +21 -12
  92. package/src/api/controllers/row/internal.ts +9 -10
  93. package/src/api/controllers/row/utils.ts +2 -2
  94. package/src/api/controllers/table/utils.ts +10 -3
  95. package/src/api/controllers/user.ts +10 -96
  96. package/src/api/index.ts +2 -4
  97. package/src/api/routes/index.ts +2 -2
  98. package/src/api/routes/ops.ts +30 -0
  99. package/src/api/routes/tests/automation.spec.js +7 -4
  100. package/src/api/routes/tests/user.spec.js +48 -37
  101. package/src/api/routes/user.ts +0 -5
  102. package/src/app.ts +4 -15
  103. package/src/automations/actions.ts +56 -24
  104. package/src/automations/index.ts +1 -1
  105. package/src/automations/steps/bash.ts +10 -7
  106. package/src/automations/steps/createRow.ts +15 -12
  107. package/src/automations/steps/delay.ts +6 -4
  108. package/src/automations/steps/deleteRow.ts +12 -9
  109. package/src/automations/steps/discord.ts +10 -8
  110. package/src/automations/steps/executeQuery.ts +13 -10
  111. package/src/automations/steps/executeScript.ts +10 -7
  112. package/src/automations/steps/filter.ts +8 -6
  113. package/src/automations/steps/integromat.ts +12 -10
  114. package/src/automations/steps/loop.ts +16 -10
  115. package/src/automations/steps/outgoingWebhook.ts +14 -11
  116. package/src/automations/steps/queryRows.ts +18 -15
  117. package/src/automations/steps/sendSmtpEmail.ts +11 -9
  118. package/src/automations/steps/serverLog.ts +6 -4
  119. package/src/automations/steps/slack.ts +8 -6
  120. package/src/automations/steps/updateRow.ts +15 -12
  121. package/src/automations/steps/zapier.ts +11 -9
  122. package/src/automations/tests/utilities/index.ts +2 -2
  123. package/src/automations/triggerInfo/app.ts +8 -5
  124. package/src/automations/triggerInfo/cron.ts +7 -4
  125. package/src/automations/triggerInfo/rowDeleted.ts +8 -5
  126. package/src/automations/triggerInfo/rowSaved.ts +10 -7
  127. package/src/automations/triggerInfo/rowUpdated.ts +10 -7
  128. package/src/automations/triggerInfo/webhook.ts +9 -6
  129. package/src/db/utils.ts +1 -0
  130. package/src/environment.ts +0 -1
  131. package/src/events/docUpdates/index.ts +1 -0
  132. package/src/events/docUpdates/processors.ts +14 -0
  133. package/src/events/docUpdates/syncUsers.ts +35 -0
  134. package/src/events/index.ts +1 -0
  135. package/src/integration-test/postgres.spec.ts +3 -1
  136. package/src/integrations/base/sqlTable.ts +9 -2
  137. package/src/integrations/index.ts +3 -3
  138. package/src/integrations/microsoftSqlServer.ts +5 -2
  139. package/src/integrations/mysql.ts +5 -3
  140. package/src/integrations/postgres.ts +7 -5
  141. package/src/integrations/redis.ts +8 -0
  142. package/src/integrations/rest.ts +3 -0
  143. package/src/migrations/functions/syncQuotas.ts +2 -0
  144. package/src/migrations/functions/usageQuotas/syncApps.ts +2 -3
  145. package/src/migrations/functions/usageQuotas/syncRows.ts +2 -3
  146. package/src/migrations/functions/usageQuotas/syncUsers.ts +9 -0
  147. package/src/migrations/functions/usageQuotas/tests/syncRows.spec.ts +2 -2
  148. package/src/migrations/functions/usageQuotas/tests/syncUsers.spec.ts +26 -0
  149. package/src/migrations/index.ts +1 -0
  150. package/src/sdk/app/applications/sync.ts +129 -22
  151. package/src/sdk/app/applications/tests/sync.spec.ts +137 -0
  152. package/src/sdk/app/backups/exports.ts +17 -41
  153. package/src/sdk/index.ts +2 -0
  154. package/src/sdk/plugins/index.ts +5 -0
  155. package/src/sdk/plugins/plugins.ts +41 -0
  156. package/src/sdk/users/tests/utils.spec.ts +1 -32
  157. package/src/sdk/users/utils.ts +23 -5
  158. package/src/startup.ts +36 -34
  159. package/src/tests/jestEnv.ts +0 -1
  160. package/src/tests/jestSetup.ts +0 -1
  161. package/src/tests/utilities/TestConfiguration.ts +28 -0
  162. package/src/tests/utilities/structures.ts +25 -17
  163. package/src/threads/automation.ts +18 -6
  164. package/src/utilities/fileSystem/plugin.ts +13 -4
  165. package/src/utilities/global.ts +21 -16
  166. package/src/utilities/rowProcessor/utils.ts +9 -10
  167. package/src/watch.ts +2 -2
  168. package/src/websockets/client.ts +11 -0
  169. package/src/websockets/grid.ts +55 -0
  170. package/src/websockets/index.ts +14 -0
  171. package/src/websockets/websocket.ts +83 -0
  172. package/tsconfig.build.json +3 -5
  173. package/tsconfig.json +2 -1
  174. package/builder/assets/index.0b358332.js +0 -1817
  175. package/builder/assets/index.7f9a008b.css +0 -6
  176. package/dist/api/controllers/cloud.js +0 -130
  177. package/dist/elasticApm.js +0 -14
  178. package/dist/package.json +0 -180
  179. package/dist/websocket.js +0 -22
  180. package/scripts/likeCypress.ts +0 -35
  181. package/src/api/controllers/cloud.ts +0 -119
  182. package/src/api/routes/cloud.ts +0 -18
  183. package/src/api/routes/tests/cloud.spec.ts +0 -54
  184. package/src/elasticApm.ts +0 -10
  185. package/src/migrations/functions/tests/syncQuotas.spec.js +0 -26
  186. package/src/tests/logging.ts +0 -34
  187. package/src/websocket.ts +0 -26
@@ -1,6 +1,117 @@
1
1
  import env from "../../../environment"
2
- import { db as dbCore, context } from "@budibase/backend-core"
2
+ import {
3
+ db as dbCore,
4
+ context,
5
+ docUpdates,
6
+ constants,
7
+ logging,
8
+ roles,
9
+ } from "@budibase/backend-core"
10
+ import { User, ContextUser, UserGroup } from "@budibase/types"
11
+ import { sdk as proSdk } from "@budibase/pro"
3
12
  import sdk from "../../"
13
+ import { getGlobalUsers, processUser } from "../../../utilities/global"
14
+ import { generateUserMetadataID, InternalTables } from "../../../db/utils"
15
+
16
+ type DeletedUser = { _id: string; deleted: boolean }
17
+
18
+ async function syncUsersToApp(
19
+ appId: string,
20
+ users: (User | DeletedUser)[],
21
+ groups: UserGroup[]
22
+ ) {
23
+ if (!(await dbCore.dbExists(appId))) {
24
+ return
25
+ }
26
+ await context.doInAppContext(appId, async () => {
27
+ const db = context.getAppDB()
28
+ for (let user of users) {
29
+ let ctxUser = user as ContextUser
30
+ let deletedUser = false
31
+ const metadataId = generateUserMetadataID(user._id!)
32
+ if ((user as DeletedUser).deleted) {
33
+ deletedUser = true
34
+ }
35
+
36
+ // make sure role is correct
37
+ if (!deletedUser) {
38
+ ctxUser = await processUser(ctxUser, { appId, groups })
39
+ }
40
+ let roleId = ctxUser.roleId
41
+ if (roleId === roles.BUILTIN_ROLE_IDS.PUBLIC) {
42
+ roleId = undefined
43
+ }
44
+
45
+ let metadata
46
+ try {
47
+ metadata = await db.get(metadataId)
48
+ } catch (err: any) {
49
+ if (err.status !== 404) {
50
+ throw err
51
+ }
52
+ // no metadata and user is to be deleted, can skip
53
+ // no role - user isn't in app anyway
54
+ if (!roleId) {
55
+ continue
56
+ } else if (!deletedUser) {
57
+ // doesn't exist yet, creating it
58
+ metadata = {
59
+ tableId: InternalTables.USER_METADATA,
60
+ }
61
+ }
62
+ }
63
+
64
+ // the user doesn't exist, or doesn't have a role anymore
65
+ // get rid of their metadata
66
+ if (deletedUser || !roleId) {
67
+ await db.remove(metadata)
68
+ continue
69
+ }
70
+
71
+ // assign the roleId for the metadata doc
72
+ if (roleId) {
73
+ metadata.roleId = roleId
74
+ }
75
+
76
+ let combined = sdk.users.combineMetadataAndUser(ctxUser, metadata)
77
+ // if no combined returned, there are no updates to make
78
+ if (combined) {
79
+ await db.put(combined)
80
+ }
81
+ }
82
+ })
83
+ }
84
+
85
+ export async function syncUsersToAllApps(userIds: string[]) {
86
+ // list of users, if one has been deleted it will be undefined in array
87
+ const users = (await getGlobalUsers(userIds, {
88
+ noProcessing: true,
89
+ })) as User[]
90
+ const groups = await proSdk.groups.fetch()
91
+ const finalUsers: (User | DeletedUser)[] = []
92
+ for (let userId of userIds) {
93
+ const user = users.find(user => user._id === userId)
94
+ if (!user) {
95
+ finalUsers.push({ _id: userId, deleted: true })
96
+ } else {
97
+ finalUsers.push(user)
98
+ }
99
+ }
100
+ const devAppIds = await dbCore.getDevAppIDs()
101
+ let promises = []
102
+ for (let devAppId of devAppIds) {
103
+ const prodAppId = dbCore.getProdAppID(devAppId)
104
+ for (let appId of [prodAppId, devAppId]) {
105
+ promises.push(syncUsersToApp(appId, finalUsers, groups))
106
+ }
107
+ }
108
+ const resp = await Promise.allSettled(promises)
109
+ const failed = resp.filter(promise => promise.status === "rejected")
110
+ if (failed.length > 0) {
111
+ const reasons = failed.map(fail => (fail as PromiseRejectedResult).reason)
112
+ logging.logAlert("Failed to sync users to apps", reasons)
113
+ }
114
+ }
4
115
 
5
116
  export async function syncApp(
6
117
  appId: string,
@@ -23,32 +134,28 @@ export async function syncApp(
23
134
  // specific case, want to make sure setup is skipped
24
135
  const prodDb = context.getProdAppDB({ skip_setup: true })
25
136
  const exists = await prodDb.exists()
26
- if (!exists) {
27
- // the database doesn't exist. Don't replicate
28
- return {
29
- message: "App sync not required, app not deployed.",
30
- }
31
- }
32
137
 
33
- const replication = new dbCore.Replication({
34
- source: prodAppId,
35
- target: appId,
36
- })
37
138
  let error
38
- try {
39
- const replOpts = replication.appReplicateOpts()
40
- if (opts?.automationOnly) {
41
- replOpts.filter = (doc: any) =>
42
- doc._id.startsWith(dbCore.DocumentType.AUTOMATION)
139
+ if (exists) {
140
+ const replication = new dbCore.Replication({
141
+ source: prodAppId,
142
+ target: appId,
143
+ })
144
+ try {
145
+ const replOpts = replication.appReplicateOpts()
146
+ if (opts?.automationOnly) {
147
+ replOpts.filter = (doc: any) =>
148
+ doc._id.startsWith(dbCore.DocumentType.AUTOMATION)
149
+ }
150
+ await replication.replicate(replOpts)
151
+ } catch (err) {
152
+ error = err
153
+ } finally {
154
+ await replication.close()
43
155
  }
44
- await replication.replicate(replOpts)
45
- } catch (err) {
46
- error = err
47
- } finally {
48
- await replication.close()
49
156
  }
50
157
 
51
- // sync the users
158
+ // sync the users - kept for safe keeping
52
159
  await sdk.users.syncGlobalUsers()
53
160
 
54
161
  if (error) {
@@ -0,0 +1,137 @@
1
+ import TestConfiguration from "../../../../tests/utilities/TestConfiguration"
2
+ import { events, context, roles, constants } from "@budibase/backend-core"
3
+ import { init } from "../../../../events"
4
+ import { rawUserMetadata } from "../../../users/utils"
5
+ import EventEmitter from "events"
6
+ import { UserGroup, UserMetadata, UserRoles, User } from "@budibase/types"
7
+
8
+ const config = new TestConfiguration()
9
+ let app, group: UserGroup, groupUser: User
10
+ const ROLE_ID = roles.BUILTIN_ROLE_IDS.BASIC
11
+
12
+ const emitter = new EventEmitter()
13
+
14
+ function updateCb(docId: string) {
15
+ const isGroup = docId.startsWith(constants.DocumentType.GROUP)
16
+ if (isGroup) {
17
+ emitter.emit("update-group")
18
+ } else {
19
+ emitter.emit("update-user")
20
+ }
21
+ }
22
+
23
+ init(updateCb)
24
+
25
+ function waitForUpdate(opts: { group?: boolean }) {
26
+ return new Promise<void>((resolve, reject) => {
27
+ const timeout = setTimeout(() => {
28
+ reject()
29
+ }, 5000)
30
+ const event = opts?.group ? "update-group" : "update-user"
31
+ emitter.on(event, () => {
32
+ clearTimeout(timeout)
33
+ resolve()
34
+ })
35
+ })
36
+ }
37
+
38
+ beforeAll(async () => {
39
+ app = await config.init("syncApp")
40
+ })
41
+
42
+ async function createUser(email: string, roles: UserRoles, builder?: boolean) {
43
+ const user = await config.createUser({
44
+ email,
45
+ roles,
46
+ builder: builder || false,
47
+ admin: false,
48
+ })
49
+ await context.doInContext(config.appId!, async () => {
50
+ await events.user.created(user)
51
+ })
52
+ return user
53
+ }
54
+
55
+ async function removeUserRole(user: User) {
56
+ const final = await config.globalUser({
57
+ ...user,
58
+ id: user._id,
59
+ roles: {},
60
+ builder: false,
61
+ admin: false,
62
+ })
63
+ await context.doInContext(config.appId!, async () => {
64
+ await events.user.updated(final)
65
+ })
66
+ }
67
+
68
+ async function createGroupAndUser(email: string) {
69
+ groupUser = await config.createUser({
70
+ email,
71
+ roles: {},
72
+ builder: false,
73
+ admin: false,
74
+ })
75
+ group = await config.createGroup()
76
+ await config.addUserToGroup(group._id!, groupUser._id!)
77
+ }
78
+
79
+ async function removeUserFromGroup() {
80
+ await config.removeUserFromGroup(group._id!, groupUser._id!)
81
+ return context.doInContext(config.appId!, async () => {
82
+ await events.user.updated(groupUser)
83
+ })
84
+ }
85
+
86
+ async function getUserMetadata(): Promise<UserMetadata[]> {
87
+ return context.doInContext(config.appId!, async () => {
88
+ return await rawUserMetadata()
89
+ })
90
+ }
91
+
92
+ function buildRoles() {
93
+ return { [config.prodAppId!]: ROLE_ID }
94
+ }
95
+
96
+ describe("app user/group sync", () => {
97
+ const groupEmail = "test2@test.com",
98
+ normalEmail = "test@test.com"
99
+ async function checkEmail(
100
+ email: string,
101
+ opts?: { group?: boolean; notFound?: boolean }
102
+ ) {
103
+ await waitForUpdate(opts || {})
104
+ const metadata = await getUserMetadata()
105
+ const found = metadata.find(data => data.email === email)
106
+ if (opts?.notFound) {
107
+ expect(found).toBeUndefined()
108
+ } else {
109
+ expect(found).toBeDefined()
110
+ }
111
+ }
112
+
113
+ it("should be able to sync a new user, add then remove", async () => {
114
+ const user = await createUser(normalEmail, buildRoles())
115
+ await checkEmail(normalEmail)
116
+ await removeUserRole(user)
117
+ await checkEmail(normalEmail, { notFound: true })
118
+ })
119
+
120
+ it("should be able to sync a group", async () => {
121
+ await createGroupAndUser(groupEmail)
122
+ await checkEmail(groupEmail, { group: true })
123
+ })
124
+
125
+ it("should be able to remove user from group", async () => {
126
+ if (!group) {
127
+ await createGroupAndUser(groupEmail)
128
+ }
129
+ await removeUserFromGroup()
130
+ await checkEmail(groupEmail, { notFound: true })
131
+ })
132
+
133
+ it("should be able to handle builder users", async () => {
134
+ await createUser("test3@test.com", {}, true)
135
+ await checkEmail("test3@test.com")
136
+ })
137
+ })
@@ -3,6 +3,7 @@ import { budibaseTempDir } from "../../../utilities/budibaseDir"
3
3
  import { streamFile, createTempFolder } from "../../../utilities/fileSystem"
4
4
  import { ObjectStoreBuckets } from "../../../constants"
5
5
  import {
6
+ AUTOMATION_LOG_PREFIX,
6
7
  LINK_USER_METADATA_PREFIX,
7
8
  TABLE_ROW_PREFIX,
8
9
  USER_METDATA_PREFIX,
@@ -20,11 +21,15 @@ const uuid = require("uuid/v4")
20
21
  const tar = require("tar")
21
22
  const MemoryStream = require("memorystream")
22
23
 
23
- type ExportOpts = {
24
+ interface DBDumpOpts {
24
25
  filter?: any
25
26
  exportPath?: string
27
+ }
28
+
29
+ interface ExportOpts extends DBDumpOpts {
26
30
  tar?: boolean
27
31
  excludeRows?: boolean
32
+ excludeLogs?: boolean
28
33
  }
29
34
 
30
35
  function tarFilesToTmp(tmpDir: string, files: string[]) {
@@ -49,7 +54,7 @@ function tarFilesToTmp(tmpDir: string, files: string[]) {
49
54
  * a filter function or the name of the export.
50
55
  * @return {*} either a readable stream or a string
51
56
  */
52
- export async function exportDB(dbName: string, opts: ExportOpts = {}) {
57
+ export async function exportDB(dbName: string, opts: DBDumpOpts = {}) {
53
58
  const exportOpts = {
54
59
  filter: opts?.filter,
55
60
  batch_size: 1000,
@@ -76,11 +81,14 @@ export async function exportDB(dbName: string, opts: ExportOpts = {}) {
76
81
  })
77
82
  }
78
83
 
79
- function defineFilter(excludeRows?: boolean) {
84
+ function defineFilter(excludeRows?: boolean, excludeLogs?: boolean) {
80
85
  const ids = [USER_METDATA_PREFIX, LINK_USER_METADATA_PREFIX]
81
86
  if (excludeRows) {
82
87
  ids.push(TABLE_ROW_PREFIX)
83
88
  }
89
+ if (excludeLogs) {
90
+ ids.push(AUTOMATION_LOG_PREFIX)
91
+ }
84
92
  return (doc: any) =>
85
93
  !ids.map(key => doc._id.includes(key)).reduce((prev, curr) => prev || curr)
86
94
  }
@@ -130,8 +138,7 @@ export async function exportApp(appId: string, config?: ExportOpts) {
130
138
  // enforce an export of app DB to the tmp path
131
139
  const dbPath = join(tmpPath, DB_EXPORT_FILE)
132
140
  await exportDB(appId, {
133
- ...config,
134
- filter: defineFilter(config?.excludeRows),
141
+ filter: defineFilter(config?.excludeRows, config?.excludeLogs),
135
142
  exportPath: dbPath,
136
143
  })
137
144
  // if tar requested, return where the tarball is
@@ -148,41 +155,6 @@ export async function exportApp(appId: string, config?: ExportOpts) {
148
155
  }
149
156
  }
150
157
 
151
- /**
152
- * Export all apps + global DB (if supplied) to a single tarball, this includes
153
- * the attachments for each app as well.
154
- * @param {object[]} appMetadata The IDs and names of apps to export.
155
- * @param {string} globalDbContents The contents of the global DB to export as well.
156
- * @return {string} The path to the tarball.
157
- */
158
- export async function exportMultipleApps(
159
- appMetadata: { appId: string; name: string }[],
160
- globalDbContents?: string
161
- ) {
162
- const tmpPath = join(budibaseTempDir(), uuid())
163
- fs.mkdirSync(tmpPath)
164
- let exportPromises: Promise<void>[] = []
165
- // export each app to a directory, then move it into the complete export
166
- const exportAndMove = async (appId: string, appName: string) => {
167
- const path = await exportApp(appId)
168
- await fs.promises.rename(path, join(tmpPath, appName))
169
- }
170
- for (let metadata of appMetadata) {
171
- exportPromises.push(exportAndMove(metadata.appId, metadata.name))
172
- }
173
- // wait for all exports to finish
174
- await Promise.all(exportPromises)
175
- // add the global DB contents
176
- if (globalDbContents) {
177
- fs.writeFileSync(join(tmpPath, GLOBAL_DB_EXPORT_FILE), globalDbContents)
178
- }
179
- const appNames = appMetadata.map(metadata => metadata.name)
180
- const tarPath = tarFilesToTmp(tmpPath, [...appNames, GLOBAL_DB_EXPORT_FILE])
181
- // clear up the tmp path now tarball generated
182
- fs.rmSync(tmpPath, { recursive: true, force: true })
183
- return tarPath
184
- }
185
-
186
158
  /**
187
159
  * Streams a backup of the database state for an app
188
160
  * @param {string} appId The ID of the app which is to be backed up.
@@ -190,6 +162,10 @@ export async function exportMultipleApps(
190
162
  * @returns {*} a readable stream of the backup which is written in real time
191
163
  */
192
164
  export async function streamExportApp(appId: string, excludeRows: boolean) {
193
- const tmpPath = await exportApp(appId, { excludeRows, tar: true })
165
+ const tmpPath = await exportApp(appId, {
166
+ excludeRows,
167
+ excludeLogs: true,
168
+ tar: true,
169
+ })
194
170
  return streamFile(tmpPath)
195
171
  }
package/src/sdk/index.ts CHANGED
@@ -6,6 +6,7 @@ import { default as datasources } from "./app/datasources"
6
6
  import { default as queries } from "./app/queries"
7
7
  import { default as rows } from "./app/rows"
8
8
  import { default as users } from "./users"
9
+ import { default as plugins } from "./plugins"
9
10
 
10
11
  const sdk = {
11
12
  backups,
@@ -16,6 +17,7 @@ const sdk = {
16
17
  users,
17
18
  datasources,
18
19
  queries,
20
+ plugins,
19
21
  }
20
22
 
21
23
  // default export for TS
@@ -0,0 +1,5 @@
1
+ import * as plugins from "./plugins"
2
+
3
+ export default {
4
+ ...plugins,
5
+ }
@@ -0,0 +1,41 @@
1
+ import { FileType, Plugin, PluginSource, PluginType } from "@budibase/types"
2
+ import {
3
+ db as dbCore,
4
+ objectStore,
5
+ plugins as pluginCore,
6
+ tenancy,
7
+ } from "@budibase/backend-core"
8
+ import { fileUpload } from "../../api/controllers/plugin/file"
9
+ import env from "../../environment"
10
+ import { clientAppSocket } from "../../websockets"
11
+ import { sdk as pro } from "@budibase/pro"
12
+
13
+ export async function fetch(type?: PluginType) {
14
+ const db = tenancy.getGlobalDB()
15
+ const response = await db.allDocs(
16
+ dbCore.getPluginParams(null, {
17
+ include_docs: true,
18
+ })
19
+ )
20
+ let plugins = response.rows.map((row: any) => row.doc) as Plugin[]
21
+ plugins = objectStore.enrichPluginURLs(plugins)
22
+ if (type) {
23
+ return plugins.filter((plugin: Plugin) => plugin.schema?.type === type)
24
+ } else {
25
+ return plugins
26
+ }
27
+ }
28
+
29
+ export async function processUploaded(plugin: FileType, source?: PluginSource) {
30
+ const { metadata, directory } = await fileUpload(plugin)
31
+ pluginCore.validate(metadata?.schema)
32
+
33
+ // Only allow components in cloud
34
+ if (!env.SELF_HOSTED && metadata?.schema?.type !== PluginType.COMPONENT) {
35
+ throw new Error("Only component plugins are supported outside of self-host")
36
+ }
37
+
38
+ const doc = await pro.plugins.storePlugin(metadata, directory, source)
39
+ clientAppSocket.emit("plugin-update", { name: doc.name, hash: doc.hash })
40
+ return doc
41
+ }
@@ -121,38 +121,7 @@ describe("syncGlobalUsers", () => {
121
121
  await syncGlobalUsers()
122
122
 
123
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
- )
124
+ expect(metadata).toHaveLength(0)
156
125
  })
157
126
  })
158
127
  })
@@ -1,12 +1,13 @@
1
1
  import { getGlobalUsers } from "../../utilities/global"
2
2
  import { context, roles as rolesCore } from "@budibase/backend-core"
3
3
  import {
4
+ getGlobalIDFromUserMetadataID,
4
5
  generateUserMetadataID,
5
6
  getUserMetadataParams,
6
7
  InternalTables,
7
8
  } from "../../db/utils"
8
9
  import { isEqual } from "lodash"
9
- import { ContextUser, UserMetadata } from "@budibase/types"
10
+ import { ContextUser, UserMetadata, User } from "@budibase/types"
10
11
 
11
12
  export function combineMetadataAndUser(
12
13
  user: ContextUser,
@@ -37,6 +38,10 @@ export function combineMetadataAndUser(
37
38
  if (found) {
38
39
  newDoc._rev = found._rev
39
40
  }
41
+ // clear fields that shouldn't be in metadata
42
+ delete newDoc.password
43
+ delete newDoc.forceResetPassword
44
+ delete newDoc.roles
40
45
  if (found == null || !isEqual(newDoc, found)) {
41
46
  return {
42
47
  ...found,
@@ -60,10 +65,9 @@ export async function rawUserMetadata() {
60
65
  export async function syncGlobalUsers() {
61
66
  // sync user metadata
62
67
  const db = context.getAppDB()
63
- const [users, metadata] = await Promise.all([
64
- getGlobalUsers(),
65
- rawUserMetadata(),
66
- ])
68
+ const resp = await Promise.all([getGlobalUsers(), rawUserMetadata()])
69
+ const users = resp[0] as User[]
70
+ const metadata = resp[1] as UserMetadata[]
67
71
  const toWrite = []
68
72
  for (let user of users) {
69
73
  const combined = combineMetadataAndUser(user, metadata)
@@ -71,5 +75,19 @@ export async function syncGlobalUsers() {
71
75
  toWrite.push(combined)
72
76
  }
73
77
  }
78
+ let foundEmails: string[] = []
79
+ for (let data of metadata) {
80
+ if (!data._id) {
81
+ continue
82
+ }
83
+ const alreadyExisting = data.email && foundEmails.indexOf(data.email) !== -1
84
+ const globalId = getGlobalIDFromUserMetadataID(data._id)
85
+ if (!users.find(user => user._id === globalId) || alreadyExisting) {
86
+ toWrite.push({ ...data, _deleted: true })
87
+ }
88
+ if (data.email) {
89
+ foundEmails.push(data.email)
90
+ }
91
+ }
74
92
  await db.bulkDocs(toWrite)
75
93
  }
package/src/startup.ts CHANGED
@@ -10,19 +10,16 @@ import fs from "fs"
10
10
  import { watch } from "./watch"
11
11
  import * as automations from "./automations"
12
12
  import * as fileSystem from "./utilities/fileSystem"
13
- import eventEmitter from "./events"
13
+ import { default as eventEmitter, init as eventInit } from "./events"
14
14
  import * as migrations from "./migrations"
15
15
  import * as bullboard from "./automations/bullboard"
16
16
  import * as pro from "@budibase/pro"
17
17
  import * as api from "./api"
18
18
  import sdk from "./sdk"
19
- const pino = require("koa-pino-logger")
20
19
 
21
20
  let STARTUP_RAN = false
22
21
 
23
22
  async function initRoutes(app: any) {
24
- app.use(pino(logging.pinoSettings()))
25
-
26
23
  if (!env.isTest()) {
27
24
  const plugin = await bullboard.init()
28
25
  app.use(plugin)
@@ -48,8 +45,10 @@ async function initPro() {
48
45
  }
49
46
 
50
47
  function shutdown(server?: any) {
51
- server.close()
52
- server.destroy()
48
+ if (server) {
49
+ server.close()
50
+ server.destroy()
51
+ }
53
52
  }
54
53
 
55
54
  export async function startup(app?: any, server?: any) {
@@ -64,6 +63,7 @@ export async function startup(app?: any, server?: any) {
64
63
  eventEmitter.emitPort(env.PORT)
65
64
  fileSystem.init()
66
65
  await redis.init()
66
+ eventInit()
67
67
 
68
68
  // run migrations on startup if not done via http
69
69
  // not recommended in a clustered environment
@@ -72,11 +72,39 @@ export async function startup(app?: any, server?: any) {
72
72
  await migrations.migrate()
73
73
  } catch (e) {
74
74
  logging.logAlert("Error performing migrations. Exiting.", e)
75
- shutdown()
75
+ shutdown(server)
76
76
  }
77
77
  }
78
78
 
79
+ // monitor plugin directory if required
80
+ if (
81
+ env.SELF_HOSTED &&
82
+ !env.MULTI_TENANCY &&
83
+ env.PLUGINS_DIR &&
84
+ fs.existsSync(env.PLUGINS_DIR)
85
+ ) {
86
+ watch()
87
+ }
88
+
89
+ // check for version updates
90
+ await installation.checkInstallVersion()
91
+
92
+ // get the references to the queue promises, don't await as
93
+ // they will never end, unless the processing stops
94
+ let queuePromises = []
95
+ // configure events to use the pro audit log write
96
+ // can't integrate directly into backend-core due to cyclic issues
97
+ queuePromises.push(events.processors.init(pro.sdk.auditLogs.write))
98
+ queuePromises.push(automations.init())
99
+ queuePromises.push(initPro())
100
+ if (app) {
101
+ // bring routes online as final step once everything ready
102
+ await initRoutes(app)
103
+ }
104
+
79
105
  // check and create admin user if required
106
+ // this must be run after the api has been initialised due to
107
+ // the app user sync
80
108
  if (
81
109
  env.SELF_HOSTED &&
82
110
  !env.MULTI_TENANCY &&
@@ -103,34 +131,8 @@ export async function startup(app?: any, server?: any) {
103
131
  )
104
132
  } catch (e) {
105
133
  logging.logAlert("Error creating initial admin user. Exiting.", e)
106
- shutdown()
134
+ shutdown(server)
107
135
  }
108
136
  }
109
137
  }
110
-
111
- // monitor plugin directory if required
112
- if (
113
- env.SELF_HOSTED &&
114
- !env.MULTI_TENANCY &&
115
- env.PLUGINS_DIR &&
116
- fs.existsSync(env.PLUGINS_DIR)
117
- ) {
118
- watch()
119
- }
120
-
121
- // check for version updates
122
- await installation.checkInstallVersion()
123
-
124
- // get the references to the queue promises, don't await as
125
- // they will never end, unless the processing stops
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))
130
- queuePromises.push(automations.init())
131
- queuePromises.push(initPro())
132
- if (app) {
133
- // bring routes online as final step once everything ready
134
- await initRoutes(app)
135
- }
136
138
  }