Skip to content

Commit

Permalink
feat(core): add doc permission service
Browse files Browse the repository at this point in the history
  • Loading branch information
JimmFly committed Jan 23, 2025
1 parent 9beb4e9 commit 9f520c3
Show file tree
Hide file tree
Showing 27 changed files with 985 additions and 264 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const InviteModal = ({
}: InviteModalProps) => {
const t = useI18n();
const [inviteEmail, setInviteEmail] = useState('');
const [permission] = useState(Permission.Write);
const [permission] = useState(Permission.Collaborator);
const [isValidEmail, setIsValidEmail] = useState(true);

const handleConfirm = useCallback(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ const getMemberStatus = (member: Member): I18nString => {
return 'Workspace Owner';
case Permission.Admin:
return 'Admin';
case Permission.Write:
case Permission.Collaborator:
return 'Collaborator';
default:
return 'Member';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export const MemberOptions = ({
}, [member, membersService, t]);
const handleChangeToCollaborator = useCallback(() => {
membersService
.adjustMemberPermission(member.id, Permission.Write)
.adjustMemberPermission(member.id, Permission.Collaborator)
.then(result => {
if (result) {
notify.success({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IconButton, MobileMenu } from '@affine/component';
import { SharePage } from '@affine/core/components/affine/share-page-modal/share-menu/share-page';
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
import { DocService } from '@affine/core/modules/doc';
import { SharePage } from '@affine/core/modules/share-menu/view/share-menu/share-page';
import { WorkspaceService } from '@affine/core/modules/workspace';
import { ShareiOsIcon } from '@blocksuite/icons/rc';
import { useServices } from '@toeverything/infra';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import {
DocRole,
type GetCurrentUserDocPermissionQuery,
type GetPageGrantedUsersListQuery,
} from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
Entity,
fromPromise,
LiveData,
onComplete,
onStart,
} from '@toeverything/infra';
import { EMPTY, map, mergeMap, switchMap } from 'rxjs';

import { isBackendError, isNetworkError } from '../../cloud';
import type { DocService } from '../../doc';
import type { WorkspaceService } from '../../workspace';
import type { DocGrantedUsersStore } from '../stores/doc-granted-users';

export type GrantedUser =
GetPageGrantedUsersListQuery['workspace']['pageGrantedUsersList']['edges'][number]['user'];

export type UserDocPermission =
GetCurrentUserDocPermissionQuery['workspace']['currentUserPermission'];

Check failure on line 27 in packages/frontend/core/src/modules/permissions/entities/doc-granted-users.ts

View workflow job for this annotation

GitHub Actions / Lint

Property 'currentUserPermission' does not exist on type '{ __typename?: "WorkspaceType" | undefined; pagePermission: { __typename?: "DocType" | undefined; id: string; public: boolean; role: DocRole; permissions: { __typename?: "RolePermissions" | undefined; ... 12 more ...; Doc_TransferOwner: boolean; }; }; }'.

export class DocGrantedUsers extends Entity {
constructor(
private readonly store: DocGrantedUsersStore,
private readonly workspaceService: WorkspaceService,
private readonly docService: DocService
) {
super();
}

pageNum$ = new LiveData(0);
grantedUserCount$ = new LiveData<number | undefined>(undefined);
docGrantedUsers$ = new LiveData<GrantedUser[] | undefined>(undefined);
docOwner$ = this.docGrantedUsers$.map(users =>
users?.find(user => user.role === DocRole.Owner)
);

isLoading$ = new LiveData(false);
error$ = new LiveData<any>(null);

readonly PAGE_SIZE = 8;

readonly revalidate = effect(
map(() => this.pageNum$.value),
switchMap(pageNum => {
return fromPromise(async signal => {
return this.store.fetchDocGrantedUsersList(
this.workspaceService.workspace.id,
this.docService.doc.id,
{
first: pageNum * this.PAGE_SIZE,
offset: this.PAGE_SIZE,
},
signal
);
}).pipe(
mergeMap(data => {
const currentUsers = this.docGrantedUsers$.value || [];
const newUsers = data.edges.map(edge => edge.user);
const allUsers = [...currentUsers, ...newUsers];
this.grantedUserCount$.setValue(data.totalCount);
this.docGrantedUsers$.setValue(allUsers);
return EMPTY;
}),
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
catchErrorInto(this.error$),
onStart(() => {
this.isLoading$.setValue(true);
}),
onComplete(() => this.isLoading$.setValue(false))
);
})
);

setPageNum(pageNum: number) {
this.pageNum$.setValue(pageNum);
this.revalidate();
}

override dispose(): void {
this.revalidate.unsubscribe();
}
}
30 changes: 30 additions & 0 deletions packages/frontend/core/src/modules/permissions/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
export type { GrantedUser } from './entities/doc-granted-users';
export type { Member } from './entities/members';
export { DocGrantedUsersService } from './services/doc-granted-users';
export { DocPermissionService } from './services/doc-permission';
export { WorkspaceMembersService } from './services/members';
export { WorkspacePermissionService } from './services/permission';

import { type Framework } from '@toeverything/infra';

import { WorkspaceServerService } from '../cloud';
import { DocScope, DocService } from '../doc';
import {
WorkspaceScope,
WorkspaceService,
WorkspacesService,
} from '../workspace';
import { DocGrantedUsers } from './entities/doc-granted-users';
import { WorkspaceMembers } from './entities/members';
import { WorkspacePermission } from './entities/permission';
import { DocGrantedUsersService } from './services/doc-granted-users';
import { DocPermissionService } from './services/doc-permission';
import { WorkspaceMembersService } from './services/members';
import { WorkspacePermissionService } from './services/permission';
import { DocGrantedUsersStore } from './stores/doc-granted-users';
import { DocPermissionStore } from './stores/doc-permission';
import { WorkspaceMembersStore } from './stores/members';
import { WorkspacePermissionStore } from './stores/permission';

Expand All @@ -30,4 +39,25 @@ export function configurePermissionsModule(framework: Framework) {
.service(WorkspaceMembersService, [WorkspaceMembersStore, WorkspaceService])
.store(WorkspaceMembersStore, [WorkspaceServerService])
.entity(WorkspaceMembers, [WorkspaceMembersStore, WorkspaceService]);

framework
.scope(WorkspaceScope)
.scope(DocScope)
.service(DocGrantedUsersService, [
DocGrantedUsersStore,
WorkspaceService,
DocService,
])
.store(DocGrantedUsersStore, [WorkspaceServerService])
.entity(DocGrantedUsers, [
DocGrantedUsersStore,
WorkspaceService,
DocService,
])
.service(DocPermissionService, [
WorkspaceService,
DocService,
DocPermissionStore,
])
.store(DocPermissionStore, [WorkspaceServerService]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { DocRole } from '@affine/graphql';
import { Service } from '@toeverything/infra';

import type { DocService } from '../../doc';
import type { WorkspaceService } from '../../workspace';
import { DocGrantedUsers } from '../entities/doc-granted-users';
import type { DocGrantedUsersStore } from '../stores/doc-granted-users';

export class DocGrantedUsersService extends Service {
constructor(
private readonly store: DocGrantedUsersStore,
private readonly workspaceService: WorkspaceService,
private readonly docService: DocService
) {
super();
}

docGrantedUsers = this.framework.createEntity(DocGrantedUsers);

async grantUsersRole(userIds: string[], role: DocRole) {
return await this.store.grantDocUserRoles({
docId: this.docService.doc.id,
workspaceId: this.workspaceService.workspace.id,
userIds,
role,
});
}

async revokeUsersRole(userIds: string[]) {
return await this.store.revokeDocUserRoles(this.docService.doc.id, userIds);
}

async updateUserRole(userId: string, role: DocRole) {
return await this.store.updateDocUserRole(
this.docService.doc.id,
userId,
role
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { DebugLogger } from '@affine/debug';
import {
DocRole,
type GetCurrentUserDocPermissionQuery,
} from '@affine/graphql';
import {
backoffRetry,
catchErrorInto,
effect,
fromPromise,
LiveData,
onComplete,
onStart,
Service,
} from '@toeverything/infra';
import { EMPTY, exhaustMap, mergeMap } from 'rxjs';

import { isBackendError, isNetworkError } from '../../cloud';
import type { DocService } from '../../doc';
import type { WorkspaceService } from '../../workspace';
import type { DocPermissionStore } from '../stores/doc-permission';

const logger = new DebugLogger('affine:doc-permission');

export type DocRolePermissionActions =
GetCurrentUserDocPermissionQuery['workspace']['currentUserPermission']['permissions'];

Check failure on line 26 in packages/frontend/core/src/modules/permissions/services/doc-permission.ts

View workflow job for this annotation

GitHub Actions / Lint

Property 'currentUserPermission' does not exist on type '{ __typename?: "WorkspaceType" | undefined; pagePermission: { __typename?: "DocType" | undefined; id: string; public: boolean; role: DocRole; permissions: { __typename?: "RolePermissions" | undefined; ... 12 more ...; Doc_TransferOwner: boolean; }; }; }'.

export class DocPermissionService extends Service {
isOwner$ = new LiveData<boolean | null>(null);
canManage$ = new LiveData<boolean | null>(null);
role$ = new LiveData<DocRole | undefined>(undefined);
rolePermissions$ = new LiveData<DocRolePermissionActions | undefined>(
undefined
);

isLoading$ = new LiveData(false);
error$ = new LiveData<any>(null);

constructor(
private readonly workspaceService: WorkspaceService,
private readonly docService: DocService,
private readonly store: DocPermissionStore
) {
super();
}

revalidate = effect(
exhaustMap(() => {
return fromPromise(async signal => {
if (this.workspaceService.workspace.flavour !== 'local') {
return await this.store.fetchCurrentUserDocPermissions(
this.workspaceService.workspace.id,
this.docService.doc.id,
signal
);
} else {
return null;
}
}).pipe(
backoffRetry({
when: isNetworkError,
count: Infinity,
}),
backoffRetry({
when: isBackendError,
}),
mergeMap(permission => {
this.canManage$.next(
permission?.role === DocRole.Manager ||
permission?.role === DocRole.Owner
);
this.isOwner$.next(permission?.role === DocRole.Owner);
this.role$.next(permission?.role);
this.rolePermissions$.next(permission?.permissions);
return EMPTY;
}),
catchErrorInto(this.error$, error => {
logger.error('Failed to fetch doc permission', error);
}),
onStart(() => this.isLoading$.setValue(true)),
onComplete(() => this.isLoading$.setValue(false))
);
})
);

override dispose(): void {
this.revalidate.unsubscribe();
}
}
Loading

0 comments on commit 9f520c3

Please sign in to comment.