feat(core): guard service (#9816)

This commit is contained in:
EYHN
2025-02-10 07:26:38 +08:00
committed by GitHub
parent 879157b938
commit 92f4f0c2d9
89 changed files with 1520 additions and 522 deletions

View File

@@ -22,9 +22,11 @@ const getRoleName = (role: DocRole, t: ReturnType<typeof useI18n>) => {
export const MembersPermission = ({
openPaywallModal,
hittingPaywall,
disabled,
}: {
hittingPaywall: boolean;
openPaywallModal?: () => void;
disabled?: boolean;
}) => {
const t = useI18n();
const [docRole, setDocRole] = useState<DocRole>(DocRole.Manager);
@@ -63,38 +65,40 @@ export const MembersPermission = ({
align: 'end',
}}
items={
<>
<MenuItem
onSelect={selectManage}
selected={docRole === DocRole.Manager}
>
<div className={styles.publicItemRowStyle}>
{t['com.affine.share-menu.option.permission.can-manage']()}
</div>
</MenuItem>
<MenuItem
onSelect={selectEdit}
selected={docRole === DocRole.Editor}
>
<div className={styles.publicItemRowStyle}>
<div className={styles.tagContainerStyle}>
{t['com.affine.share-menu.option.permission.can-edit']()}
<PlanTag />
disabled ? null : (
<>
<MenuItem
onSelect={selectManage}
selected={docRole === DocRole.Manager}
>
<div className={styles.publicItemRowStyle}>
{t['com.affine.share-menu.option.permission.can-manage']()}
</div>
</div>
</MenuItem>
<MenuItem
onSelect={selectRead}
selected={docRole === DocRole.Reader}
>
<div className={styles.publicItemRowStyle}>
<div className={styles.tagContainerStyle}>
{t['com.affine.share-menu.option.permission.can-read']()}
<PlanTag />
</MenuItem>
<MenuItem
onSelect={selectEdit}
selected={docRole === DocRole.Editor}
>
<div className={styles.publicItemRowStyle}>
<div className={styles.tagContainerStyle}>
{t['com.affine.share-menu.option.permission.can-edit']()}
<PlanTag />
</div>
</div>
</div>
</MenuItem>
</>
</MenuItem>
<MenuItem
onSelect={selectRead}
selected={docRole === DocRole.Reader}
>
<div className={styles.publicItemRowStyle}>
<div className={styles.tagContainerStyle}>
{t['com.affine.share-menu.option.permission.can-read']()}
<PlanTag />
</div>
</div>
</MenuItem>
</>
)
}
>
<MenuTrigger
@@ -103,6 +107,7 @@ export const MembersPermission = ({
contentStyle={{
width: '100%',
}}
disabled={disabled}
>
{currentRoleName}
</MenuTrigger>

View File

@@ -15,7 +15,7 @@ import { useEffect } from 'react';
import * as styles from './styles.css';
export const PublicDoc = () => {
export const PublicDoc = ({ disabled }: { disabled?: boolean }) => {
const t = useI18n();
const shareInfoService = useService(ShareInfoService);
const isSharedPage = useLiveData(shareInfoService.shareInfo.isShared$);
@@ -98,27 +98,31 @@ export const PublicDoc = () => {
align: 'end',
}}
items={
<>
<MenuItem
prefixIcon={<LockIcon />}
onSelect={onDisablePublic}
selected={!isSharedPage}
>
<div className={styles.publicItemRowStyle}>
<div>{t['com.affine.share-menu.option.link.no-access']()}</div>
</div>
</MenuItem>
<MenuItem
prefixIcon={<ViewIcon />}
onSelect={onClickAnyoneReadOnlyShare}
data-testid="share-link-menu-enable-share"
selected={!!isSharedPage}
>
<div className={styles.publicItemRowStyle}>
<div>{t['com.affine.share-menu.option.link.readonly']()}</div>
</div>
</MenuItem>
</>
disabled ? null : (
<>
<MenuItem
prefixIcon={<LockIcon />}
onSelect={onDisablePublic}
selected={!isSharedPage}
>
<div className={styles.publicItemRowStyle}>
<div>
{t['com.affine.share-menu.option.link.no-access']()}
</div>
</div>
</MenuItem>
<MenuItem
prefixIcon={<ViewIcon />}
onSelect={onClickAnyoneReadOnlyShare}
data-testid="share-link-menu-enable-share"
selected={!!isSharedPage}
>
<div className={styles.publicItemRowStyle}>
<div>{t['com.affine.share-menu.option.link.readonly']()}</div>
</div>
</MenuItem>
</>
)
}
>
<MenuTrigger
@@ -128,6 +132,7 @@ export const PublicDoc = () => {
contentStyle={{
width: '100%',
}}
disabled={disabled}
>
{isSharedPage
? t['com.affine.share-menu.option.link.readonly']()

View File

@@ -8,13 +8,15 @@ import {
Tooltip,
} from '@affine/component';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { DocService } from '@affine/core/modules/doc';
import {
DocGrantedUsersService,
type GrantedUser,
GuardService,
} from '@affine/core/modules/permissions';
import { DocRole, UserFriendlyError } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { useService } from '@toeverything/infra';
import { useLiveData, useService } from '@toeverything/infra';
import { useMemo } from 'react';
import { PlanTag } from '../plan-tag';
@@ -77,7 +79,6 @@ export const MemberItem = ({
</div>
</div>
{/* TODO(@eyhn): add guard here */}
<Menu
items={
<Options
@@ -118,6 +119,15 @@ const Options = ({
}) => {
const t = useI18n();
const docGrantedUsersService = useService(DocGrantedUsersService);
const docService = useService(DocService);
const guardService = useService(GuardService);
const canTransferOwner = useLiveData(
guardService.can$('Doc_TransferOwner', docService.doc.id)
);
const canManageUsers = useLiveData(
guardService.can$('Doc_Users_Manage', docService.doc.id)
);
const changeToManager = useAsyncCallback(async () => {
try {
@@ -188,20 +198,17 @@ const Options = ({
label: t['com.affine.share-menu.option.permission.can-manage'](),
onClick: changeToManager,
role: DocRole.Manager,
show: true, // TODO(@eyhn): add guard here
},
{
label: t['com.affine.share-menu.option.permission.can-edit'](),
onClick: changeToEditor,
role: DocRole.Editor,
show: true, // TODO(@eyhn): add guard here
showPlanTag: true,
},
{
label: t['com.affine.share-menu.option.permission.can-read'](),
onClick: changeToReader,
role: DocRole.Reader,
show: true, // TODO(@eyhn): add guard here
showPlanTag: true,
},
];
@@ -209,26 +216,28 @@ const Options = ({
return (
<>
{operationButtonInfo.map(item =>
item.show ? (
<MenuItem
key={item.label}
onSelect={item.onClick}
selected={memberRole === item.role}
>
<div className={styles.planTagContainer}>
{item.label} {item.showPlanTag ? <PlanTag /> : null}
</div>
</MenuItem>
) : null
)}
{/* TODO(@eyhn): add guard here */}
<MenuItem onSelect={changeToOwner}>
{operationButtonInfo.map(item => (
<MenuItem
key={item.label}
onSelect={item.onClick}
selected={memberRole === item.role}
disabled={!canManageUsers}
>
<div className={styles.planTagContainer}>
{item.label} {item.showPlanTag ? <PlanTag /> : null}
</div>
</MenuItem>
))}
<MenuItem onSelect={changeToOwner} disabled={!canTransferOwner}>
{t['com.affine.share-menu.member-management.set-as-owner']()}
</MenuItem>
{/* TODO(@eyhn): add guard here */}
<MenuSeparator />
<MenuItem onSelect={removeMember} type="danger" className={styles.remove}>
<MenuItem
onSelect={removeMember}
type="danger"
className={styles.remove}
disabled={!canManageUsers}
>
{t['com.affine.share-menu.member-management.remove']()}
</MenuItem>
</>

View File

@@ -1,7 +1,9 @@
import { Skeleton } from '@affine/component';
import { DocService } from '@affine/core/modules/doc';
import {
DocGrantedUsersService,
type GrantedUser,
GuardService,
} from '@affine/core/modules/permissions';
import { useI18n } from '@affine/i18n';
import { ArrowLeftBigIcon } from '@blocksuite/icons/rc';
@@ -30,6 +32,12 @@ export const MemberManagement = ({
const grantedUserCount = useLiveData(
docGrantedUsersService.grantedUserCount$
);
const docService = useService(DocService);
const guardService = useService(GuardService);
const canManageUsers = useLiveData(
guardService.can$('Doc_Users_Manage', docService.doc.id)
);
const t = useI18n();
@@ -62,11 +70,15 @@ export const MemberManagement = ({
) : (
<Skeleton className={styles.scrollableRootStyle} />
)}
{/* TODO(@eyhn): add guard here */}
<div className={styles.footerStyle}>
<span className={styles.addCollaboratorsStyle} onClick={onClickInvite}>
{t['com.affine.share-menu.member-management.add-collaborators']()}
</span>
{canManageUsers ? (
<span
className={styles.addCollaboratorsStyle}
onClick={onClickInvite}
>
{t['com.affine.share-menu.member-management.add-collaborators']()}
</span>
) : null}
</div>
</div>
);

View File

@@ -1,6 +1,8 @@
import { Skeleton } from '@affine/component';
import { Button } from '@affine/component/ui/button';
import { ServerService } from '@affine/core/modules/cloud';
import { DocService } from '@affine/core/modules/doc';
import { GuardService } from '@affine/core/modules/permissions';
import { ShareInfoService } from '@affine/core/modules/share-doc';
import { useI18n } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra';
@@ -61,6 +63,16 @@ export const AFFiNESharePage = (
} = props;
const shareInfoService = useService(ShareInfoService);
const serverService = useService(ServerService);
const docService = useService(DocService);
const guardService = useService(GuardService);
const canManageUsers = useLiveData(
guardService.can$('Doc_Users_Manage', docService.doc.id)
);
const canPublish = useLiveData(
guardService.can$('Doc_Publish', docService.doc.id)
);
useEffect(() => {
shareInfoService.shareInfo.revalidate();
@@ -85,7 +97,7 @@ export const AFFiNESharePage = (
return (
<div className={styles.content}>
<div className={styles.columnContainerStyle}>
<InviteInput onFocus={props.onClickInvite} />
{canManageUsers && <InviteInput onFocus={props.onClickInvite} />}
<MembersRow onClick={props.onClickMembers} />
<div className={styles.generalAccessStyle}>
{t['com.affine.share-menu.generalAccess']()}
@@ -93,8 +105,9 @@ export const AFFiNESharePage = (
<MembersPermission
openPaywallModal={props.openPaywallModal}
hittingPaywall={!!props.hittingPaywall}
disabled={!canManageUsers}
/>
<PublicDoc />
<PublicDoc disabled={!canPublish} />
</div>
<CopyLinkButton workspaceId={workspaceId} />
</div>