From f75cfc1db6832a250eaf7e340654ffb7c8b2ad62 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Fri, 28 Feb 2025 17:11:42 +0000 Subject: [PATCH 01/11] Wide variety of improvements to performance, making some group operations synchronous to avoid re-fetching groups repeatedly. --- packages/backend-core/src/users/db.ts | 25 +++++--- .../backend-core/src/users/test/utils.spec.ts | 28 ++++----- packages/backend-core/src/users/users.ts | 6 +- packages/backend-core/src/users/utils.ts | 59 +++++++++++++------ packages/pro | 2 +- packages/server/src/middleware/authorized.ts | 2 +- 6 files changed, 77 insertions(+), 45 deletions(-) diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts index 2b15338925..677feed678 100644 --- a/packages/backend-core/src/users/db.ts +++ b/packages/backend-core/src/users/db.ts @@ -26,8 +26,9 @@ import { import { getAccountHolderFromUsers, isAdmin, - isCreator, + creatorsInList, validateUniqueUser, + isCreatorAsync, } from "./utils" import { getFirstPlatformUser, @@ -261,8 +262,16 @@ export class UserDB { } const change = dbUser ? 0 : 1 // no change if there is existing user - const creatorsChange = - (await isCreator(dbUser)) !== (await isCreator(user)) ? 1 : 0 + + let creatorsChange = 0 + if (dbUser) { + const [isDbUserCreator, isUserCreator] = await creatorsInList([ + dbUser, + user, + ]) + creatorsChange = isDbUserCreator !== isUserCreator ? 1 : 0 + } + return UserDB.quotas.addUsers(change, creatorsChange, async () => { if (!opts.isAccountHolder) { await validateUniqueUser(email, tenantId) @@ -353,7 +362,7 @@ export class UserDB { } newUser.userGroups = groups || [] newUsers.push(newUser) - if (await isCreator(newUser)) { + if (await isCreatorAsync(newUser)) { newCreators.push(newUser) } } @@ -453,10 +462,8 @@ export class UserDB { })) const dbResponse = await usersCore.bulkUpdateGlobalUsers(toDelete) - const creatorsEval = await Promise.all(usersToDelete.map(isCreator)) - const creatorsToDeleteCount = creatorsEval.filter( - creator => !!creator - ).length + const creatorsEval = await creatorsInList(usersToDelete) + const creatorsToDeleteCount = creatorsEval.filter(creator => creator).length const ssoUsersToDelete: AnyDocument[] = [] for (let user of usersToDelete) { @@ -533,7 +540,7 @@ export class UserDB { await db.remove(userId, dbUser._rev!) - const creatorsToDelete = (await isCreator(dbUser)) ? 1 : 0 + const creatorsToDelete = (await isCreatorAsync(dbUser)) ? 1 : 0 await UserDB.quotas.removeUsers(1, creatorsToDelete) await eventHelpers.handleDeleteEvents(dbUser) await cache.user.invalidateUser(userId) diff --git a/packages/backend-core/src/users/test/utils.spec.ts b/packages/backend-core/src/users/test/utils.spec.ts index cb98b8972b..b52397c979 100644 --- a/packages/backend-core/src/users/test/utils.spec.ts +++ b/packages/backend-core/src/users/test/utils.spec.ts @@ -2,39 +2,39 @@ import { User, UserGroup } from "@budibase/types" import { generator, structures } from "../../../tests" import { DBTestConfiguration } from "../../../tests/extra" import { getGlobalDB } from "../../context" -import { isCreator } from "../utils" +import { isCreatorSync, creatorsInList } from "../utils" const config = new DBTestConfiguration() describe("Users", () => { - it("User is a creator if it is configured as a global builder", async () => { + it("User is a creator if it is configured as a global builder", () => { const user: User = structures.users.user({ builder: { global: true } }) - expect(await isCreator(user)).toBe(true) + expect(isCreatorSync(user, [])).toBe(true) }) - it("User is a creator if it is configured as a global admin", async () => { + it("User is a creator if it is configured as a global admin", () => { const user: User = structures.users.user({ admin: { global: true } }) - expect(await isCreator(user)).toBe(true) + expect(isCreatorSync(user, [])).toBe(true) }) - it("User is a creator if it is configured with creator permission", async () => { + it("User is a creator if it is configured with creator permission", () => { const user: User = structures.users.user({ builder: { creator: true } }) - expect(await isCreator(user)).toBe(true) + expect(isCreatorSync(user, [])).toBe(true) }) - it("User is a creator if it is a builder in some application", async () => { + it("User is a creator if it is a builder in some application", () => { const user: User = structures.users.user({ builder: { apps: ["app1"] } }) - expect(await isCreator(user)).toBe(true) + expect(isCreatorSync(user, [])).toBe(true) }) - it("User is a creator if it has CREATOR permission in some application", async () => { + it("User is a creator if it has CREATOR permission in some application", () => { const user: User = structures.users.user({ roles: { app1: "CREATOR" } }) - expect(await isCreator(user)).toBe(true) + expect(isCreatorSync(user, [])).toBe(true) }) - it("User is a creator if it has ADMIN permission in some application", async () => { + it("User is a creator if it has ADMIN permission in some application", () => { const user: User = structures.users.user({ roles: { app1: "ADMIN" } }) - expect(await isCreator(user)).toBe(true) + expect(isCreatorSync(user, [])).toBe(true) }) it("User is a creator if it remains to a group with ADMIN permissions", async () => { @@ -59,7 +59,7 @@ describe("Users", () => { await db.put(group) for (let user of users) { await db.put(user) - const creator = await isCreator(user) + const creator = (await creatorsInList([user]))[0] expect(creator).toBe(true) } }) diff --git a/packages/backend-core/src/users/users.ts b/packages/backend-core/src/users/users.ts index 0bff428fa9..36abfcfb2d 100644 --- a/packages/backend-core/src/users/users.ts +++ b/packages/backend-core/src/users/users.ts @@ -22,7 +22,7 @@ import { } from "@budibase/types" import * as context from "../context" import { getGlobalDB } from "../context" -import { isCreator } from "./utils" +import { creatorsInList } from "./utils" import { UserDB } from "./db" import { dataFilters } from "@budibase/shared-core" @@ -305,8 +305,8 @@ export async function getCreatorCount() { let creators = 0 async function iterate(startPage?: string) { const page = await paginatedUsers({ bookmark: startPage }) - const creatorsEval = await Promise.all(page.data.map(isCreator)) - creators += creatorsEval.filter(creator => !!creator).length + const creatorsEval = await creatorsInList(page.data) + creators += creatorsEval.filter(creator => creator).length if (page.hasNextPage) { await iterate(page.nextPage) } diff --git a/packages/backend-core/src/users/utils.ts b/packages/backend-core/src/users/utils.ts index 91b667ce17..b9d8dc5823 100644 --- a/packages/backend-core/src/users/utils.ts +++ b/packages/backend-core/src/users/utils.ts @@ -16,30 +16,55 @@ export const hasAdminPermissions = sdk.users.hasAdminPermissions export const hasBuilderPermissions = sdk.users.hasBuilderPermissions export const hasAppBuilderPermissions = sdk.users.hasAppBuilderPermissions -export async function isCreator(user?: User | ContextUser) { +async function getGroups(groupIds: string[]) { + if (groupIds.length) { + const db = context.getGlobalDB() + return await db.getMultiple(groupIds) + } + return [] +} + +export async function creatorsInList( + users: (User | ContextUser)[], + groups?: UserGroup[] +) { + if (!groups) { + const groupIds = users.flatMap(user => user.userGroups || []) + if (groupIds.length) { + groups = await getGroups(groupIds) + } + } + return users.map(user => isCreatorSync(user, groups || [])) +} + +// fetches groups if no provided, but is async and shouldn't be looped with +export async function isCreatorAsync( + user: User | ContextUser, + groups?: UserGroup[] +) { + if (!groups) { + groups = await getGroups(user.userGroups || []) + } + return isCreatorSync(user, groups) +} + +export function isCreatorSync(user: User | ContextUser, groups: UserGroup[]) { const isCreatorByUserDefinition = sdk.users.isCreator(user) if (!isCreatorByUserDefinition && user) { - return await isCreatorByGroupMembership(user) + return isCreatorByGroupMembership(user, groups) } return isCreatorByUserDefinition } -async function isCreatorByGroupMembership(user?: User | ContextUser) { - const userGroups = user?.userGroups || [] +function isCreatorByGroupMembership( + user: User | ContextUser, + groups: UserGroup[] +) { + const userGroups = groups.filter( + group => user.userGroups?.indexOf(group._id!) !== -1 + ) if (userGroups.length > 0) { - const db = context.getGlobalDB() - const groups: UserGroup[] = [] - for (let groupId of userGroups) { - try { - const group = await db.get(groupId) - groups.push(group) - } catch (e: any) { - if (e.error !== "not_found") { - throw e - } - } - } - return groups.some(group => + return userGroups.some(group => Object.values(group.roles || {}).includes(BUILTIN_ROLE_IDS.ADMIN) ) } diff --git a/packages/pro b/packages/pro index e3843dd4ea..a1903c9b8c 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit e3843dd4eaced68ae063355b77df200dbc789c98 +Subproject commit a1903c9b8c5985f72ddb33b40b4a37521aaf8fa0 diff --git a/packages/server/src/middleware/authorized.ts b/packages/server/src/middleware/authorized.ts index 3bead7f80d..0c4943cf5e 100644 --- a/packages/server/src/middleware/authorized.ts +++ b/packages/server/src/middleware/authorized.ts @@ -34,7 +34,7 @@ const checkAuthorized = async ( const isCreatorApi = permType === PermissionType.CREATOR const isBuilderApi = permType === PermissionType.BUILDER const isGlobalBuilder = users.isGlobalBuilder(ctx.user) - const isCreator = await users.isCreator(ctx.user) + const isCreator = await users.isCreatorAsync(ctx.user) const isBuilder = appId ? users.isBuilder(ctx.user, appId) : users.hasBuilderPermissions(ctx.user) From 89d320578d1d18c7be994688c5d7a38e9e430bca Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 3 Mar 2025 13:33:01 +0000 Subject: [PATCH 02/11] Attempting to add delete files in during deletion. --- packages/server/src/api/controllers/application.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index a3b08b5ff9..3a370564e7 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -654,9 +654,7 @@ async function destroyApp(ctx: UserCtx) { await quotas.removeApp() await events.app.deleted(app) - if (!env.isTest()) { - await deleteAppFiles(appId) - } + await deleteAppFiles(appId) await removeAppFromUserRoles(ctx, appId) await cache.app.invalidateAppMetadata(devAppId) From a6d2cfc4d90f364e0df4e4b23fad4f66cf007190 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 3 Mar 2025 13:34:00 +0000 Subject: [PATCH 03/11] Undo --- packages/server/src/api/controllers/application.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 3a370564e7..a3b08b5ff9 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -654,7 +654,9 @@ async function destroyApp(ctx: UserCtx) { await quotas.removeApp() await events.app.deleted(app) - await deleteAppFiles(appId) + if (!env.isTest()) { + await deleteAppFiles(appId) + } await removeAppFromUserRoles(ctx, appId) await cache.app.invalidateAppMetadata(devAppId) From 0252dd318821b41b680ded0f4751587f18ac30d9 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 3 Mar 2025 18:48:14 +0000 Subject: [PATCH 04/11] User groups can be missing, this was previously allowed. --- packages/backend-core/src/db/couch/DatabaseImpl.ts | 2 +- packages/backend-core/src/users/utils.ts | 2 +- packages/pro | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts index 98e24e0996..502cd5594b 100644 --- a/packages/backend-core/src/db/couch/DatabaseImpl.ts +++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts @@ -249,7 +249,7 @@ export class DatabaseImpl implements Database { if (!opts?.allowMissing && someMissing) { const missing = response.rows.filter(row => rowUnavailable(row)) const missingIds = missing.map(row => row.key).join(", ") - throw new Error(`Unable to get documents: ${missingIds}`) + throw new Error(`Unable to get bulk documents: ${missingIds}`) } return rows.map(row => (includeDocs ? row.doc! : row.value)) } diff --git a/packages/backend-core/src/users/utils.ts b/packages/backend-core/src/users/utils.ts index b9d8dc5823..83c581dd6c 100644 --- a/packages/backend-core/src/users/utils.ts +++ b/packages/backend-core/src/users/utils.ts @@ -19,7 +19,7 @@ export const hasAppBuilderPermissions = sdk.users.hasAppBuilderPermissions async function getGroups(groupIds: string[]) { if (groupIds.length) { const db = context.getGlobalDB() - return await db.getMultiple(groupIds) + return await db.getMultiple(groupIds, { allowMissing: true }) } return [] } diff --git a/packages/pro b/packages/pro index a1903c9b8c..6dc5a3071b 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit a1903c9b8c5985f72ddb33b40b4a37521aaf8fa0 +Subproject commit 6dc5a3071bd37c54a0977cde0d54569f3c972ad3 From 45843e328dd0ccb1022554c2d149bbaef10fb48b Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 4 Mar 2025 17:23:08 +0000 Subject: [PATCH 05/11] Small improvements. --- packages/backend-core/src/users/utils.ts | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/packages/backend-core/src/users/utils.ts b/packages/backend-core/src/users/utils.ts index 83c581dd6c..7c8cbb083a 100644 --- a/packages/backend-core/src/users/utils.ts +++ b/packages/backend-core/src/users/utils.ts @@ -16,8 +16,8 @@ export const hasAdminPermissions = sdk.users.hasAdminPermissions export const hasBuilderPermissions = sdk.users.hasBuilderPermissions export const hasAppBuilderPermissions = sdk.users.hasAppBuilderPermissions -async function getGroups(groupIds: string[]) { - if (groupIds.length) { +async function getGroups(groupIds?: string[]) { + if (groupIds?.length) { const db = context.getGlobalDB() return await db.getMultiple(groupIds, { allowMissing: true }) } @@ -34,21 +34,16 @@ export async function creatorsInList( groups = await getGroups(groupIds) } } - return users.map(user => isCreatorSync(user, groups || [])) + return users.map(user => isCreatorSync(user, groups)) } // fetches groups if no provided, but is async and shouldn't be looped with -export async function isCreatorAsync( - user: User | ContextUser, - groups?: UserGroup[] -) { - if (!groups) { - groups = await getGroups(user.userGroups || []) - } +export async function isCreatorAsync(user: User | ContextUser) { + const groups = await getGroups(user.userGroups) return isCreatorSync(user, groups) } -export function isCreatorSync(user: User | ContextUser, groups: UserGroup[]) { +export function isCreatorSync(user: User | ContextUser, groups?: UserGroup[]) { const isCreatorByUserDefinition = sdk.users.isCreator(user) if (!isCreatorByUserDefinition && user) { return isCreatorByGroupMembership(user, groups) @@ -58,12 +53,12 @@ export function isCreatorSync(user: User | ContextUser, groups: UserGroup[]) { function isCreatorByGroupMembership( user: User | ContextUser, - groups: UserGroup[] + groups?: UserGroup[] ) { - const userGroups = groups.filter( + const userGroups = groups?.filter( group => user.userGroups?.indexOf(group._id!) !== -1 ) - if (userGroups.length > 0) { + if (userGroups && userGroups.length > 0) { return userGroups.some(group => Object.values(group.roles || {}).includes(BUILTIN_ROLE_IDS.ADMIN) ) From a7c7a947604d4cd86f2940bae6d2c57203299b13 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 4 Mar 2025 17:56:46 +0000 Subject: [PATCH 06/11] PR comments. --- .../backend-core/src/db/couch/DatabaseImpl.ts | 5 +++- packages/backend-core/src/users/utils.ts | 24 ++++++++----------- packages/types/src/sdk/db.ts | 2 +- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts index 502cd5594b..69f0fd64ea 100644 --- a/packages/backend-core/src/db/couch/DatabaseImpl.ts +++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts @@ -222,9 +222,12 @@ export class DatabaseImpl implements Database { } async getMultiple( - ids: string[], + ids?: string[], opts?: { allowMissing?: boolean; excludeDocs?: boolean } ): Promise { + if (!ids || ids.length === 0) { + return [] + } // get unique ids = [...new Set(ids)] const includeDocs = !opts?.excludeDocs diff --git a/packages/backend-core/src/users/utils.ts b/packages/backend-core/src/users/utils.ts index 7c8cbb083a..01d8a0bdca 100644 --- a/packages/backend-core/src/users/utils.ts +++ b/packages/backend-core/src/users/utils.ts @@ -16,30 +16,26 @@ export const hasAdminPermissions = sdk.users.hasAdminPermissions export const hasBuilderPermissions = sdk.users.hasBuilderPermissions export const hasAppBuilderPermissions = sdk.users.hasAppBuilderPermissions -async function getGroups(groupIds?: string[]) { - if (groupIds?.length) { - const db = context.getGlobalDB() - return await db.getMultiple(groupIds, { allowMissing: true }) - } - return [] -} - export async function creatorsInList( users: (User | ContextUser)[], groups?: UserGroup[] ) { - if (!groups) { - const groupIds = users.flatMap(user => user.userGroups || []) - if (groupIds.length) { - groups = await getGroups(groupIds) - } + const groupIds = [ + ...new Set( + users.filter(user => user.userGroups).flatMap(user => user.userGroups!) + ), + ] + if (groupIds.length) { + const db = context.getGlobalDB() + groups = await db.getMultiple(groupIds) } return users.map(user => isCreatorSync(user, groups)) } // fetches groups if no provided, but is async and shouldn't be looped with export async function isCreatorAsync(user: User | ContextUser) { - const groups = await getGroups(user.userGroups) + const db = context.getGlobalDB() + const groups = await db.getMultiple(user.userGroups) return isCreatorSync(user, groups) } diff --git a/packages/types/src/sdk/db.ts b/packages/types/src/sdk/db.ts index eb922bd81e..9f99266644 100644 --- a/packages/types/src/sdk/db.ts +++ b/packages/types/src/sdk/db.ts @@ -136,7 +136,7 @@ export interface Database { get(id?: string): Promise tryGet(id?: string): Promise getMultiple( - ids: string[], + ids?: string[], opts?: { allowMissing?: boolean; excludeDocs?: boolean } ): Promise remove(idOrDoc: Document): Promise From 4ff338f6557ff292c3ebba11640acce0ba462ca0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 4 Mar 2025 18:06:20 +0000 Subject: [PATCH 07/11] Get user groups, allow missing. --- packages/backend-core/src/users/utils.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/backend-core/src/users/utils.ts b/packages/backend-core/src/users/utils.ts index 01d8a0bdca..c9a31da4df 100644 --- a/packages/backend-core/src/users/utils.ts +++ b/packages/backend-core/src/users/utils.ts @@ -25,10 +25,8 @@ export async function creatorsInList( users.filter(user => user.userGroups).flatMap(user => user.userGroups!) ), ] - if (groupIds.length) { - const db = context.getGlobalDB() - groups = await db.getMultiple(groupIds) - } + const db = context.getGlobalDB() + groups = await db.getMultiple(groupIds, { allowMissing: true }) return users.map(user => isCreatorSync(user, groups)) } From 34338244393f2598d370eaf1c85b05c23ae4d2e1 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 10 Mar 2025 15:44:02 +0000 Subject: [PATCH 08/11] Removing a completely mocked test, tested as part of group tests already. --- .../tests/core/users/users.spec.ts | 52 ------------------- 1 file changed, 52 deletions(-) delete mode 100644 packages/backend-core/tests/core/users/users.spec.ts diff --git a/packages/backend-core/tests/core/users/users.spec.ts b/packages/backend-core/tests/core/users/users.spec.ts deleted file mode 100644 index b14f553266..0000000000 --- a/packages/backend-core/tests/core/users/users.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { range } from "lodash/fp" -import { structures } from "../.." - -jest.mock("../../../src/context") -jest.mock("../../../src/db") - -import * as context from "../../../src/context" -import * as db from "../../../src/db" - -import { getCreatorCount } from "../../../src/users/users" - -describe("Users", () => { - let getGlobalDBMock: jest.SpyInstance - let paginationMock: jest.SpyInstance - - beforeEach(() => { - jest.resetAllMocks() - - getGlobalDBMock = jest.spyOn(context, "getGlobalDB") - paginationMock = jest.spyOn(db, "pagination") - - jest.spyOn(db, "getGlobalUserParams") - }) - - it("retrieves the number of creators", async () => { - const getUsers = (offset: number, limit: number, creators = false) => { - const opts = creators ? { builder: { global: true } } : undefined - return range(offset, limit).map(() => structures.users.user(opts)) - } - const page1Data = getUsers(0, 8) - const page2Data = getUsers(8, 12, true) - getGlobalDBMock.mockImplementation(() => ({ - name: "fake-db", - allDocs: () => ({ - rows: [...page1Data, ...page2Data], - }), - })) - paginationMock.mockImplementationOnce(() => ({ - data: page1Data, - hasNextPage: true, - nextPage: "1", - })) - paginationMock.mockImplementation(() => ({ - data: page2Data, - hasNextPage: false, - nextPage: undefined, - })) - const creatorsCount = await getCreatorCount() - expect(creatorsCount).toBe(4) - expect(paginationMock).toHaveBeenCalledTimes(2) - }) -}) From d9cd64831ef4063276819eea27d8cf61de192c71 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 10 Mar 2025 15:50:08 +0000 Subject: [PATCH 09/11] Fixing instrumentation layer. --- packages/backend-core/src/db/instrumentation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-core/src/db/instrumentation.ts b/packages/backend-core/src/db/instrumentation.ts index 0c0056d6ed..68c3694672 100644 --- a/packages/backend-core/src/db/instrumentation.ts +++ b/packages/backend-core/src/db/instrumentation.ts @@ -52,13 +52,13 @@ export class DDInstrumentedDatabase implements Database { } getMultiple( - ids: string[], + ids?: string[], opts?: { allowMissing?: boolean | undefined } | undefined ): Promise { return tracer.trace("db.getMultiple", async span => { span.addTags({ db_name: this.name, - num_docs: ids.length, + num_docs: ids?.length || 0, allow_missing: opts?.allowMissing, }) const docs = await this.db.getMultiple(ids, opts) From dd5c2c5c7663ad1331ad99b2303f19f33edf85d0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Mon, 10 Mar 2025 16:05:38 +0000 Subject: [PATCH 10/11] Only call to DB if needed. --- packages/backend-core/src/users/utils.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/backend-core/src/users/utils.ts b/packages/backend-core/src/users/utils.ts index c9a31da4df..039f9228f9 100644 --- a/packages/backend-core/src/users/utils.ts +++ b/packages/backend-core/src/users/utils.ts @@ -32,8 +32,11 @@ export async function creatorsInList( // fetches groups if no provided, but is async and shouldn't be looped with export async function isCreatorAsync(user: User | ContextUser) { - const db = context.getGlobalDB() - const groups = await db.getMultiple(user.userGroups) + let groups: UserGroup[] = [] + if (user.userGroups) { + const db = context.getGlobalDB() + groups = await db.getMultiple(user.userGroups) + } return isCreatorSync(user, groups) } From 2243d48da07aac2753975a0bfe1db41d6d53a82a Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Tue, 11 Mar 2025 13:36:32 +0000 Subject: [PATCH 11/11] Update pro to master. --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index 6dc5a3071b..2dd06c2fcb 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit 6dc5a3071bd37c54a0977cde0d54569f3c972ad3 +Subproject commit 2dd06c2fcb3cf10d5f16f5d8fe6cd344c8e905a5