Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 28 additions & 20 deletions backend/src/services/identity/identity-org-dal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,30 +414,32 @@ export const identityOrgDALFactory = (db: TDbClient) => {
tx?: Knex
) => {
try {
const searchQuery = (tx || db.replicaNode())(TableName.Membership)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
.where(`${TableName.Membership}.scopeOrgId`, orgId)
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Membership}.actorIdentityId`)
.whereNull(`${TableName.Identity}.projectId`)
.join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
const buildIdentitySearchBase = () =>
(tx || db.replicaNode())(TableName.Membership)
.where(`${TableName.Membership}.scope`, AccessScope.Organization)
.whereNotNull(`${TableName.Membership}.actorIdentityId`)
.where(`${TableName.Membership}.scopeOrgId`, orgId)
.join(TableName.Identity, `${TableName.Identity}.id`, `${TableName.Membership}.actorIdentityId`)
.whereNull(`${TableName.Identity}.projectId`)
.join(TableName.MembershipRole, `${TableName.MembershipRole}.membershipId`, `${TableName.Membership}.id`)
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`);

const searchQuery = buildIdentitySearchBase()
.orderBy(
orderBy === OrgIdentityOrderBy.Role
? `${TableName.MembershipRole}.${orderBy}`
: `${TableName.Identity}.${orderBy}`,
orderDirection
)
.select(`${TableName.Membership}.id`)
.select<{ id: string; total_count: string }>(
db.raw(
`count(${TableName.Membership}."actorIdentityId") OVER(PARTITION BY ${TableName.Membership}."scopeOrgId") as total_count`
)
)
.as("searchedIdentities");

// the membership -> membership_roles join is one-to-many, so a window count over the joined
// rows counts role assignments instead of identities. Count distinct memberships separately.
const countQuery = buildIdentitySearchBase();

if (searchFilter) {
buildKnexFilterForSearchResource(searchQuery, searchFilter, (attr) => {
const getSearchFilterField = (attr: "name" | "role") => {
switch (attr) {
case "role":
return [`${TableName.Role}.slug`, `${TableName.MembershipRole}.role`];
Expand All @@ -446,7 +448,9 @@ export const identityOrgDALFactory = (db: TDbClient) => {
default:
throw new BadRequestError({ message: `Invalid ${String(attr)} provided` });
}
});
};
buildKnexFilterForSearchResource(searchQuery, searchFilter, getSearchFilterField);
buildKnexFilterForSearchResource(countQuery, searchFilter, getSearchFilterField);
}

if (limit) {
Expand Down Expand Up @@ -527,7 +531,6 @@ export const identityOrgDALFactory = (db: TDbClient) => {
)
.select(
db.ref("id").withSchema(TableName.Membership),
db.ref("total_count").withSchema("searchedIdentities"),
db.ref("role").withSchema(TableName.MembershipRole),
db.ref("customRoleId").withSchema(TableName.MembershipRole).as("roleId"),
db.ref("scopeOrgId").withSchema(TableName.Membership).as("orgId"),
Expand Down Expand Up @@ -580,7 +583,14 @@ export const identityOrgDALFactory = (db: TDbClient) => {
);
}

const docs = await query;
// the count and the paginated fetch are independent (they share only orgId), so run them
// concurrently instead of awaiting the count before building and executing the main query.
const [docs, countRows] = await Promise.all([
query,
countQuery.countDistinct(`${TableName.Membership}.id as count`)
]);
const totalCount = Number((countRows as unknown as [{ count: string | number }?])[0]?.count ?? 0);

const formattedDocs = sqlNestRelationships({
data: docs,
key: "id",
Expand All @@ -596,7 +606,6 @@ export const identityOrgDALFactory = (db: TDbClient) => {
hasDeleteProtection,
role,
roleId,
total_count,
id,
uaId,
alicloudId,
Expand All @@ -619,7 +628,6 @@ export const identityOrgDALFactory = (db: TDbClient) => {
roleId,
identityId: identityId as string,
id,
total_count: total_count as string,
orgId,
createdAt,
updatedAt,
Expand Down Expand Up @@ -668,7 +676,7 @@ export const identityOrgDALFactory = (db: TDbClient) => {
]
});

return { docs: formattedDocs, totalCount: Number(formattedDocs?.[0]?.total_count ?? 0) };
return { docs: formattedDocs, totalCount };
} catch (error) {
throw new DatabaseError({ error, name: "FindByOrgId" });
}
Expand Down
27 changes: 15 additions & 12 deletions backend/src/services/membership-user/membership-user-dal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,12 +149,11 @@ export const membershipUserDALFactory = (db: TDbClient) => {

const findUsers = async ({ scopeData, tx, filter }: TFindUserArg) => {
try {
const paginatedUsers = (tx || db.replicaNode())(TableName.Membership)
const baseUserQuery = (tx || db.replicaNode())(TableName.Membership)
.whereNotNull(`${TableName.Membership}.actorUserId`)
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.Membership}.actorUserId`)
.join(TableName.MembershipRole, `${TableName.Membership}.id`, `${TableName.MembershipRole}.membershipId`)
.leftJoin(TableName.Role, `${TableName.MembershipRole}.customRoleId`, `${TableName.Role}.id`)
.distinct(`${TableName.Membership}.id`)
.where(`${TableName.Membership}.scopeOrgId`, scopeData.orgId)
.where(`${TableName.Users}.isGhost`, false)
.where((qb) => {
Expand All @@ -167,12 +166,9 @@ export const membershipUserDALFactory = (db: TDbClient) => {
}
});

if (filter.limit) void paginatedUsers.limit(filter.limit);
if (filter.offset) void paginatedUsers.offset(filter.offset);

if (filter.username || filter.role) {
buildKnexFilterForSearchResource(
paginatedUsers,
baseUserQuery,
{
username: filter.username!,
role: filter.role!
Expand All @@ -190,6 +186,18 @@ export const membershipUserDALFactory = (db: TDbClient) => {
);
}

// the membership -> membership_roles join is one-to-many, so counting joined rows (as the
// previous window function did) counts role assignments instead of users. Count distinct
// memberships before applying pagination to get the true number of matching users.
const [countResult] = (await baseUserQuery.clone().countDistinct(`${TableName.Membership}.id as count`)) as [
{ count: string | number }?
];
const totalCount = Number(countResult?.count ?? 0);

const paginatedUsers = baseUserQuery.clone().distinct(`${TableName.Membership}.id`);
if (filter.limit) void paginatedUsers.limit(filter.limit);
if (filter.offset) void paginatedUsers.offset(filter.offset);

const docs = await (tx || db.replicaNode())(TableName.Membership)
.whereNotNull(`${TableName.Membership}.actorUserId`)
.join(TableName.Users, `${TableName.Users}.id`, `${TableName.Membership}.actorUserId`)
Expand Down Expand Up @@ -223,11 +231,6 @@ export const membershipUserDALFactory = (db: TDbClient) => {
db.ref("firstName").withSchema(TableName.Users).as("userFirstName"),
db.ref("lastName").withSchema(TableName.Users).as("userLastName"),
db.ref("id").withSchema(TableName.Users).as("userId")
)
.select(
db.raw(
`count(${TableName.Membership}."actorUserId") OVER(PARTITION BY ${TableName.Membership}."scopeOrgId") as total`
)
);

const data = sqlNestRelationships({
Expand Down Expand Up @@ -277,7 +280,7 @@ export const membershipUserDALFactory = (db: TDbClient) => {
}
]
});
return { data, totalCount: Number((data?.[0] as unknown as { total: number })?.total ?? 0) };
return { data, totalCount };
} catch (error) {
throw new DatabaseError({ error, name: "MembershipfindUser" });
}
Expand Down