fix(server): batch grant page roles (#10007)

This commit is contained in:
forehalo
2025-02-07 05:55:07 +00:00
parent b9ad53ae68
commit 12cc94f32a
6 changed files with 70 additions and 98 deletions

View File

@@ -1,15 +1,15 @@
import { Injectable, Logger } from '@nestjs/common';
import type { Prisma } from '@prisma/client';
import type { Prisma, WorkspacePageUserPermission } from '@prisma/client';
import { PrismaClient, WorkspaceMemberStatus } from '@prisma/client';
import { groupBy } from 'lodash-es';
import {
CanNotBatchGrantPageOwnerPermissions,
DocAccessDenied,
EventBus,
OnEvent,
SpaceAccessDenied,
SpaceOwnerNotFound,
SpaceShouldHaveOnlyOneOwner,
WorkspacePermissionNotFound,
} from '../../base';
import {
@@ -737,17 +737,15 @@ export class PermissionService {
].filter(Boolean) as Prisma.PrismaPromise<any>[]
);
return p;
return p as WorkspacePageUserPermission;
}
async revokePage(ws: string, page: string, users: string[]) {
async revokePage(ws: string, page: string, user: string) {
const result = await this.prisma.workspacePageUserPermission.deleteMany({
where: {
workspaceId: ws,
pageId: page,
userId: {
in: users,
},
userId: user,
type: {
// We shouldn't revoke owner permission, should auto deleted by workspace/user delete cascading
not: DocRole.Owner,
@@ -758,74 +756,30 @@ export class PermissionService {
return result.count > 0;
}
async grantPagePermission(
async batchGrantPage(
workspaceId: string,
pageId: string,
userIds: string[],
role: DocRole
) {
if (userIds.length === 0) {
return [];
}
if (role === DocRole.Owner && userIds.length > 1) {
throw new SpaceShouldHaveOnlyOneOwner({ spaceId: workspaceId });
return 0;
}
return await this.prisma.$transaction(async tx =>
Promise.all(
userIds.map(id =>
tx.workspacePageUserPermission.upsert({
where: {
workspaceId_pageId_userId: {
workspaceId,
pageId,
userId: id,
},
},
create: {
workspaceId,
pageId,
userId: id,
type: role,
},
update: {
type: role,
},
})
)
)
);
}
if (role === DocRole.Owner) {
throw new CanNotBatchGrantPageOwnerPermissions();
}
async updatePagePermission(
workspaceId: string,
pageId: string,
userId: string,
role: DocRole
) {
const permission = await this.prisma.workspacePageUserPermission.findFirst({
where: {
const result = await this.prisma.workspacePageUserPermission.createMany({
skipDuplicates: true,
data: userIds.map(id => ({
workspaceId,
pageId,
userId,
},
});
if (!permission) {
return this.grantPage(workspaceId, pageId, userId, role);
}
return await this.prisma.workspacePageUserPermission.update({
where: {
workspaceId_pageId_userId: {
workspaceId,
pageId,
userId,
},
},
data: {
userId: id,
type: role,
},
})),
});
return result.count;
}
}

View File

@@ -37,7 +37,6 @@ import {
mapDocRoleToPermissions,
PermissionService,
PublicPageMode,
WorkspaceRole,
} from '../../permission';
import { PublicUserType } from '../../user';
import { DocID } from '../../utils/doc';
@@ -94,15 +93,15 @@ class UpdateDocUserRoleInput {
}
@InputType()
class RevokeDocUserRolesInput {
class RevokeDocUserRoleInput {
@Field(() => String)
docId!: string;
@Field(() => String)
workspaceId!: string;
@Field(() => [String])
userIds!: string[];
@Field(() => String)
userId!: string;
}
@InputType()
@@ -263,10 +262,18 @@ export class PagePermissionResolver {
complexity: 4,
})
async pageGrantedUsersList(
@CurrentUser() user: CurrentUser,
@Parent() workspace: WorkspaceType,
@Args('pageId') pageId: string,
@Args('pagination') pagination: PaginationInput
): Promise<PaginatedGrantedDocUserType> {
await this.permission.checkPagePermission(
workspace.id,
pageId,
'Doc.Users.Read',
user.id
);
const docId = new DocID(pageId, workspace.id);
const [permissions, totalCount] = await this.prisma.$transaction(tx => {
return Promise.all([
@@ -454,7 +461,7 @@ export class PagePermissionResolver {
'Doc.Users.Manage',
user.id
);
await this.permission.grantPagePermission(
await this.permission.batchGrantPage(
doc.workspace,
doc.guid,
input.userIds,
@@ -471,7 +478,7 @@ export class PagePermissionResolver {
@Mutation(() => Boolean)
async revokeDocUserRoles(
@CurrentUser() user: CurrentUser,
@Args('input') input: RevokeDocUserRolesInput
@Args('input') input: RevokeDocUserRoleInput
): Promise<boolean> {
const doc = new DocID(input.docId, input.workspaceId);
const pairs = {
@@ -488,15 +495,16 @@ export class PagePermissionResolver {
'Expect doc not to be workspace'
);
}
await this.permission.checkWorkspace(
await this.permission.checkPagePermission(
doc.workspace,
user.id,
WorkspaceRole.Collaborator
doc.guid,
'Doc.Users.Manage',
user.id
);
await this.permission.revokePage(doc.workspace, doc.guid, input.userIds);
await this.permission.revokePage(doc.workspace, doc.guid, input.userId);
this.logger.log('Revoke doc user roles', {
...pairs,
userIds: input.userIds,
userId: input.userId,
});
return true;
}
@@ -521,38 +529,36 @@ export class PagePermissionResolver {
'Expect doc not to be workspace'
);
}
await this.permission.checkWorkspace(
await this.permission.checkPagePermission(
doc.workspace,
user.id,
WorkspaceRole.Collaborator
doc.guid,
input.role === DocRole.Owner ? 'Doc.TransferOwner' : 'Doc.Users.Manage',
user.id
);
await this.permission.grantPage(
doc.workspace,
doc.guid,
input.userId,
input.role
);
if (input.role === DocRole.Owner) {
const ret = await this.permission.grantPagePermission(
doc.workspace,
doc.guid,
[input.userId],
input.role
);
this.logger.log('Transfer doc owner', {
...pairs,
userId: input.userId,
role: input.role,
});
return ret.length > 0;
} else {
await this.permission.updatePagePermission(
doc.workspace,
doc.guid,
input.userId,
input.role
);
this.logger.log('Update doc user role', {
...pairs,
userId: input.userId,
role: input.role,
});
return true;
}
return true;
}
@Mutation(() => Boolean)
@@ -580,7 +586,7 @@ export class PagePermissionResolver {
);
}
try {
await this.permission.checkCloudPagePermission(
await this.permission.checkPagePermission(
doc.workspace,
doc.guid,
'Doc.Users.Manage',