@budibase/server 2.5.6-alpha.1 → 2.5.6-alpha.2

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.
@@ -12,7 +12,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
12
12
  return (mod && mod.__esModule) ? mod : { "default": mod };
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.getGlobalUsersFromMetadata = exports.getGlobalUsers = exports.getGlobalUser = exports.getRawGlobalUser = exports.getCachedSelf = exports.updateAppRole = void 0;
15
+ exports.getGlobalUsersFromMetadata = exports.getGlobalUsers = exports.getGlobalUser = exports.getRawGlobalUser = exports.getCachedSelf = exports.processUser = exports.updateAppRole = void 0;
16
16
  const utils_1 = require("../db/utils");
17
17
  const backend_core_1 = require("@budibase/backend-core");
18
18
  const environment_1 = __importDefault(require("../environment"));
@@ -20,7 +20,7 @@ const pro_1 = require("@budibase/pro");
20
20
  function updateAppRole(user, { appId } = {}) {
21
21
  var _a;
22
22
  appId = appId || backend_core_1.context.getAppId();
23
- if (!user || !user.roles) {
23
+ if (!user || (!user.roles && !user.userGroups)) {
24
24
  return user;
25
25
  }
26
26
  // if in an multi-tenancy environment make sure roles are never updated
@@ -31,7 +31,7 @@ function updateAppRole(user, { appId } = {}) {
31
31
  return user;
32
32
  }
33
33
  // always use the deployed app
34
- if (appId) {
34
+ if (appId && user.roles) {
35
35
  user.roleId = user.roles[backend_core_1.db.getProdAppID(appId)];
36
36
  }
37
37
  // if a role wasn't found then either set as admin (builder) or public (everyone else)
@@ -76,6 +76,7 @@ function processUser(user, opts = {}) {
76
76
  return user;
77
77
  });
78
78
  }
79
+ exports.processUser = processUser;
79
80
  function getCachedSelf(ctx, appId) {
80
81
  var _a;
81
82
  return __awaiter(this, void 0, void 0, function* () {
@@ -101,15 +102,13 @@ function getGlobalUser(userId) {
101
102
  });
102
103
  }
103
104
  exports.getGlobalUser = getGlobalUser;
104
- function getGlobalUsers(users) {
105
+ function getGlobalUsers(userIds, opts) {
105
106
  return __awaiter(this, void 0, void 0, function* () {
106
107
  const appId = backend_core_1.context.getAppId();
107
108
  const db = backend_core_1.tenancy.getGlobalDB();
108
- const allGroups = yield pro_1.groups.fetch();
109
109
  let globalUsers;
110
- if (users) {
111
- const globalIds = users.map(user => (0, utils_1.getGlobalIDFromUserMetadataID)(user._id));
112
- globalUsers = (yield db.allDocs((0, utils_1.getMultiIDParams)(globalIds))).rows.map(row => row.doc);
110
+ if (userIds) {
111
+ globalUsers = (yield db.allDocs((0, utils_1.getMultiIDParams)(userIds))).rows.map(row => row.doc);
113
112
  }
114
113
  else {
115
114
  globalUsers = (yield db.allDocs(backend_core_1.db.getGlobalUserParams(null, {
@@ -126,15 +125,21 @@ function getGlobalUsers(users) {
126
125
  if (!appId) {
127
126
  return globalUsers;
128
127
  }
129
- // pass in the groups, meaning we don't actually need to retrieve them for
130
- // each user individually
131
- return Promise.all(globalUsers.map(user => processUser(user, { groups: allGroups })));
128
+ if (opts === null || opts === void 0 ? void 0 : opts.noProcessing) {
129
+ return globalUsers;
130
+ }
131
+ else {
132
+ // pass in the groups, meaning we don't actually need to retrieve them for
133
+ // each user individually
134
+ const allGroups = yield pro_1.groups.fetch();
135
+ return Promise.all(globalUsers.map(user => processUser(user, { groups: allGroups })));
136
+ }
132
137
  });
133
138
  }
134
139
  exports.getGlobalUsers = getGlobalUsers;
135
140
  function getGlobalUsersFromMetadata(users) {
136
141
  return __awaiter(this, void 0, void 0, function* () {
137
- const globalUsers = yield getGlobalUsers(users);
142
+ const globalUsers = yield getGlobalUsers(users.map(user => user._id));
138
143
  return users.map(user => {
139
144
  const globalUser = globalUsers.find(globalUser => { var _a; return globalUser && ((_a = user._id) === null || _a === void 0 ? void 0 : _a.includes(globalUser._id)); });
140
145
  return Object.assign(Object.assign({}, globalUser), user);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/server",
3
3
  "email": "hi@budibase.com",
4
- "version": "2.5.6-alpha.1",
4
+ "version": "2.5.6-alpha.2",
5
5
  "description": "Budibase Web Server",
6
6
  "main": "src/index.ts",
7
7
  "repository": {
@@ -45,12 +45,12 @@
45
45
  "license": "GPL-3.0",
46
46
  "dependencies": {
47
47
  "@apidevtools/swagger-parser": "10.0.3",
48
- "@budibase/backend-core": "2.5.6-alpha.1",
49
- "@budibase/client": "2.5.6-alpha.1",
50
- "@budibase/pro": "2.5.5-alpha.4",
51
- "@budibase/shared-core": "2.5.6-alpha.1",
52
- "@budibase/string-templates": "2.5.6-alpha.1",
53
- "@budibase/types": "2.5.6-alpha.1",
48
+ "@budibase/backend-core": "2.5.6-alpha.2",
49
+ "@budibase/client": "2.5.6-alpha.2",
50
+ "@budibase/pro": "2.5.6-alpha.1",
51
+ "@budibase/shared-core": "2.5.6-alpha.2",
52
+ "@budibase/string-templates": "2.5.6-alpha.2",
53
+ "@budibase/types": "2.5.6-alpha.2",
54
54
  "@bull-board/api": "3.7.0",
55
55
  "@bull-board/koa": "3.9.4",
56
56
  "@elastic/elasticsearch": "7.10.0",
@@ -175,5 +175,5 @@
175
175
  "optionalDependencies": {
176
176
  "oracledb": "5.3.0"
177
177
  },
178
- "gitHead": "fdc38d656252b7b81508245e20ca65ef2739a363"
178
+ "gitHead": "af0bc0890fbe45ca786366c72a754c2202277a54"
179
179
  }
@@ -30,7 +30,6 @@ import { finaliseRow, updateRelatedFormula } from "./staticFormula"
30
30
  import { csv, json, jsonWithSchema, Format } from "../view/exporters"
31
31
  import { apiFileReturn } from "../../../utilities/fileSystem"
32
32
  import {
33
- Ctx,
34
33
  UserCtx,
35
34
  Database,
36
35
  LinkDocumentValue,
@@ -72,7 +71,7 @@ async function getView(db: Database, viewName: string) {
72
71
  return viewInfo
73
72
  }
74
73
 
75
- async function getRawTableData(ctx: Ctx, db: Database, tableId: string) {
74
+ async function getRawTableData(ctx: UserCtx, db: Database, tableId: string) {
76
75
  let rows
77
76
  if (tableId === InternalTables.USER_METADATA) {
78
77
  await userController.fetchMetadata(ctx)
@@ -188,7 +187,7 @@ export async function save(ctx: UserCtx) {
188
187
  })
189
188
  }
190
189
 
191
- export async function fetchView(ctx: Ctx) {
190
+ export async function fetchView(ctx: UserCtx) {
192
191
  const viewName = decodeURIComponent(ctx.params.viewName)
193
192
 
194
193
  // if this is a table view being looked for just transfer to that
@@ -255,7 +254,7 @@ export async function fetchView(ctx: Ctx) {
255
254
  return rows
256
255
  }
257
256
 
258
- export async function fetch(ctx: Ctx) {
257
+ export async function fetch(ctx: UserCtx) {
259
258
  const db = context.getAppDB()
260
259
 
261
260
  const tableId = ctx.params.tableId
@@ -264,7 +263,7 @@ export async function fetch(ctx: Ctx) {
264
263
  return outputProcessing(table, rows)
265
264
  }
266
265
 
267
- export async function find(ctx: Ctx) {
266
+ export async function find(ctx: UserCtx) {
268
267
  const db = dbCore.getDB(ctx.appId)
269
268
  const table = await db.get(ctx.params.tableId)
270
269
  let row = await utils.findRow(ctx, ctx.params.tableId, ctx.params.rowId)
@@ -272,7 +271,7 @@ export async function find(ctx: Ctx) {
272
271
  return row
273
272
  }
274
273
 
275
- export async function destroy(ctx: Ctx) {
274
+ export async function destroy(ctx: UserCtx) {
276
275
  const db = context.getAppDB()
277
276
  const { _id } = ctx.request.body
278
277
  let row = await db.get(_id)
@@ -308,7 +307,7 @@ export async function destroy(ctx: Ctx) {
308
307
  return { response, row }
309
308
  }
310
309
 
311
- export async function bulkDestroy(ctx: Ctx) {
310
+ export async function bulkDestroy(ctx: UserCtx) {
312
311
  const db = context.getAppDB()
313
312
  const tableId = ctx.params.tableId
314
313
  const table = await db.get(tableId)
@@ -347,7 +346,7 @@ export async function bulkDestroy(ctx: Ctx) {
347
346
  return { response: { ok: true }, rows: processedRows }
348
347
  }
349
348
 
350
- export async function search(ctx: Ctx) {
349
+ export async function search(ctx: UserCtx) {
351
350
  // Fetch the whole table when running in cypress, as search doesn't work
352
351
  if (!env.COUCH_DB_URL && env.isCypress()) {
353
352
  return { rows: await fetch(ctx) }
@@ -387,7 +386,7 @@ export async function search(ctx: Ctx) {
387
386
  return response
388
387
  }
389
388
 
390
- export async function exportRows(ctx: Ctx) {
389
+ export async function exportRows(ctx: UserCtx) {
391
390
  const db = context.getAppDB()
392
391
  const table = await db.get(ctx.params.tableId)
393
392
  const rowIds = ctx.request.body.rows
@@ -439,7 +438,7 @@ export async function exportRows(ctx: Ctx) {
439
438
  }
440
439
  }
441
440
 
442
- export async function fetchEnrichedRow(ctx: Ctx) {
441
+ export async function fetchEnrichedRow(ctx: UserCtx) {
443
442
  const db = context.getAppDB()
444
443
  const tableId = ctx.params.tableId
445
444
  const rowId = ctx.params.rowId
@@ -5,7 +5,7 @@ import { context } from "@budibase/backend-core"
5
5
  import { makeExternalQuery } from "../../../integrations/base/query"
6
6
  import { Row, Table } from "@budibase/types"
7
7
  import { Format } from "../view/exporters"
8
- import { Ctx } from "@budibase/types"
8
+ import { UserCtx } from "@budibase/types"
9
9
  import sdk from "../../../sdk"
10
10
  const validateJs = require("validate.js")
11
11
  const { cloneDeep } = require("lodash/fp")
@@ -26,7 +26,7 @@ export async function getDatasourceAndQuery(json: any) {
26
26
  return makeExternalQuery(datasource, json)
27
27
  }
28
28
 
29
- export async function findRow(ctx: Ctx, tableId: string, rowId: string) {
29
+ export async function findRow(ctx: UserCtx, tableId: string, rowId: string) {
30
30
  const db = context.getAppDB()
31
31
  let row
32
32
  // TODO remove special user case in future
@@ -1,98 +1,12 @@
1
1
  import { generateUserMetadataID, generateUserFlagID } from "../../db/utils"
2
2
  import { InternalTables } from "../../db/utils"
3
- import { getGlobalUsers, getRawGlobalUser } from "../../utilities/global"
3
+ import { getGlobalUsers } from "../../utilities/global"
4
4
  import { getFullUser } from "../../utilities/users"
5
- import {
6
- context,
7
- roles as rolesCore,
8
- db as dbCore,
9
- } from "@budibase/backend-core"
10
- import { BBContext, Ctx, SyncUserRequest, User } from "@budibase/types"
5
+ import { context } from "@budibase/backend-core"
6
+ import { UserCtx } from "@budibase/types"
11
7
  import sdk from "../../sdk"
12
8
 
13
- export async function syncUser(ctx: Ctx<SyncUserRequest>) {
14
- let deleting = false,
15
- user: User | any
16
- const userId = ctx.params.id
17
-
18
- const previousUser = ctx.request.body?.previousUser
19
-
20
- try {
21
- user = (await getRawGlobalUser(userId)) as User
22
- } catch (err: any) {
23
- if (err && err.status === 404) {
24
- user = {}
25
- deleting = true
26
- } else {
27
- throw err
28
- }
29
- }
30
-
31
- let previousApps = previousUser
32
- ? Object.keys(previousUser.roles).map(appId => appId)
33
- : []
34
-
35
- const roles = deleting ? {} : user.roles
36
- // remove props which aren't useful to metadata
37
- delete user.password
38
- delete user.forceResetPassword
39
- delete user.roles
40
- // run through all production appIDs in the users roles
41
- let prodAppIds
42
- // if they are a builder then get all production app IDs
43
- if ((user.builder && user.builder.global) || deleting) {
44
- prodAppIds = await dbCore.getProdAppIDs()
45
- } else {
46
- prodAppIds = Object.entries(roles)
47
- .filter(entry => entry[1] !== rolesCore.BUILTIN_ROLE_IDS.PUBLIC)
48
- .map(([appId]) => appId)
49
- }
50
- for (let prodAppId of new Set([...prodAppIds, ...previousApps])) {
51
- const roleId = roles[prodAppId]
52
- const deleteFromApp = !roleId
53
- const devAppId = dbCore.getDevelopmentAppID(prodAppId)
54
- for (let appId of [prodAppId, devAppId]) {
55
- if (!(await dbCore.dbExists(appId))) {
56
- continue
57
- }
58
- await context.doInAppContext(appId, async () => {
59
- const db = context.getAppDB()
60
- const metadataId = generateUserMetadataID(userId)
61
- let metadata
62
- try {
63
- metadata = await db.get(metadataId)
64
- } catch (err) {
65
- if (deleteFromApp) {
66
- return
67
- }
68
- metadata = {
69
- tableId: InternalTables.USER_METADATA,
70
- }
71
- }
72
-
73
- if (deleteFromApp) {
74
- await db.remove(metadata)
75
- return
76
- }
77
-
78
- // assign the roleId for the metadata doc
79
- if (roleId) {
80
- metadata.roleId = roleId
81
- }
82
- let combined = sdk.users.combineMetadataAndUser(user, metadata)
83
- // if its null then there was no updates required
84
- if (combined) {
85
- await db.put(combined)
86
- }
87
- })
88
- }
89
- }
90
- ctx.body = {
91
- message: "User synced.",
92
- }
93
- }
94
-
95
- export async function fetchMetadata(ctx: BBContext) {
9
+ export async function fetchMetadata(ctx: UserCtx) {
96
10
  const global = await getGlobalUsers()
97
11
  const metadata = await sdk.users.rawUserMetadata()
98
12
  const users = []
@@ -111,7 +25,7 @@ export async function fetchMetadata(ctx: BBContext) {
111
25
  ctx.body = users
112
26
  }
113
27
 
114
- export async function updateSelfMetadata(ctx: BBContext) {
28
+ export async function updateSelfMetadata(ctx: UserCtx) {
115
29
  // overwrite the ID with current users
116
30
  ctx.request.body._id = ctx.user?._id
117
31
  // make sure no stale rev
@@ -121,7 +35,7 @@ export async function updateSelfMetadata(ctx: BBContext) {
121
35
  await updateMetadata(ctx)
122
36
  }
123
37
 
124
- export async function updateMetadata(ctx: BBContext) {
38
+ export async function updateMetadata(ctx: UserCtx) {
125
39
  const db = context.getAppDB()
126
40
  const user = ctx.request.body
127
41
  // this isn't applicable to the user
@@ -133,7 +47,7 @@ export async function updateMetadata(ctx: BBContext) {
133
47
  ctx.body = await db.put(metadata)
134
48
  }
135
49
 
136
- export async function destroyMetadata(ctx: BBContext) {
50
+ export async function destroyMetadata(ctx: UserCtx) {
137
51
  const db = context.getAppDB()
138
52
  try {
139
53
  const dbUser = await db.get(ctx.params.id)
@@ -146,11 +60,11 @@ export async function destroyMetadata(ctx: BBContext) {
146
60
  }
147
61
  }
148
62
 
149
- export async function findMetadata(ctx: BBContext) {
63
+ export async function findMetadata(ctx: UserCtx) {
150
64
  ctx.body = await getFullUser(ctx, ctx.params.id)
151
65
  }
152
66
 
153
- export async function setFlag(ctx: BBContext) {
67
+ export async function setFlag(ctx: UserCtx) {
154
68
  const userId = ctx.user?._id
155
69
  const { flag, value } = ctx.request.body
156
70
  if (!flag) {
@@ -169,7 +83,7 @@ export async function setFlag(ctx: BBContext) {
169
83
  ctx.body = { message: "Flag set successfully" }
170
84
  }
171
85
 
172
- export async function getFlags(ctx: BBContext) {
86
+ export async function getFlags(ctx: UserCtx) {
173
87
  const userId = ctx.user?._id
174
88
  const docId = generateUserFlagID(userId!)
175
89
  const db = context.getAppDB()
@@ -205,41 +205,4 @@ describe("/users", () => {
205
205
  expect(res.body.message).toEqual("Flag set successfully")
206
206
  })
207
207
  })
208
-
209
- describe("syncUser", () => {
210
- it("should sync the user", async () => {
211
- let user = await config.createUser()
212
- await config.createApp("New App")
213
- let res = await request
214
- .post(`/api/users/metadata/sync/${user._id}`)
215
- .set(config.defaultHeaders())
216
- .expect(200)
217
- .expect("Content-Type", /json/)
218
- expect(res.body.message).toEqual("User synced.")
219
- })
220
-
221
- it("should sync the user when a previous user is specified", async () => {
222
- const app1 = await config.createApp("App 1")
223
- const app2 = await config.createApp("App 2")
224
-
225
- let user = await config.createUser({
226
- builder: false,
227
- admin: true,
228
- roles: { [app1.appId]: "ADMIN" },
229
- })
230
- let res = await request
231
- .post(`/api/users/metadata/sync/${user._id}`)
232
- .set(config.defaultHeaders())
233
- .send({
234
- previousUser: {
235
- ...user,
236
- roles: { ...user.roles, [app2.appId]: "BASIC" },
237
- },
238
- })
239
- .expect(200)
240
- .expect("Content-Type", /json/)
241
-
242
- expect(res.body.message).toEqual("User synced.")
243
- })
244
- })
245
208
  })
@@ -32,11 +32,6 @@ router
32
32
  authorized(PermissionType.USER, PermissionLevel.WRITE),
33
33
  controller.destroyMetadata
34
34
  )
35
- .post(
36
- "/api/users/metadata/sync/:id",
37
- authorized(PermissionType.USER, PermissionLevel.WRITE),
38
- controller.syncUser
39
- )
40
35
  .post(
41
36
  "/api/users/flags",
42
37
  authorized(PermissionType.USER, PermissionLevel.WRITE),
@@ -0,0 +1 @@
1
+ export * from "./processors"
@@ -0,0 +1,14 @@
1
+ import userGroupProcessor from "./syncUsers"
2
+ import { docUpdates } from "@budibase/backend-core"
3
+
4
+ export type UpdateCallback = (docId: string) => void
5
+ let started = false
6
+
7
+ export function init(updateCb?: UpdateCallback) {
8
+ if (started) {
9
+ return
10
+ }
11
+ const processors = [userGroupProcessor(updateCb)]
12
+ docUpdates.init(processors)
13
+ started = true
14
+ }
@@ -0,0 +1,35 @@
1
+ import { constants, logging } from "@budibase/backend-core"
2
+ import { sdk as proSdk } from "@budibase/pro"
3
+ import { DocUpdateEvent, UserGroupSyncEvents } from "@budibase/types"
4
+ import { syncUsersToAllApps } from "../../sdk/app/applications/sync"
5
+ import { UpdateCallback } from "./processors"
6
+
7
+ export default function process(updateCb?: UpdateCallback) {
8
+ const processor = async (update: DocUpdateEvent) => {
9
+ try {
10
+ const docId = update.id
11
+ const isGroup = docId.startsWith(constants.DocumentType.GROUP)
12
+ let userIds: string[]
13
+ if (isGroup) {
14
+ const group = await proSdk.groups.get(docId)
15
+ userIds = group.users?.map(user => user._id) || []
16
+ } else {
17
+ userIds = [docId]
18
+ }
19
+ if (userIds.length > 0) {
20
+ await syncUsersToAllApps(userIds)
21
+ }
22
+ if (updateCb) {
23
+ updateCb(docId)
24
+ }
25
+ } catch (err: any) {
26
+ // if something not found - no changes to perform
27
+ if (err?.status === 404) {
28
+ return
29
+ } else {
30
+ logging.logAlert("Failed to perform user/group app sync", err)
31
+ }
32
+ }
33
+ }
34
+ return { events: UserGroupSyncEvents, processor }
35
+ }
@@ -2,4 +2,5 @@ import BudibaseEmitter from "./BudibaseEmitter"
2
2
 
3
3
  const emitter = new BudibaseEmitter()
4
4
 
5
+ export { init } from "./docUpdates"
5
6
  export default emitter
@@ -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) {