feat(core): add use guard hook (#11180)

Previously, we used `useLiveData(guardService.can$())` to get the guard result, but `guardService.can$()` will request the server to revalidate the permission when calling it, will cause additional network requests when re-render.

This pr make a new hook `useGuard` to fix this problem.

And the side effect in `can$` is moved to `revalidateCan()` to make that the subscribe method is pure
This commit is contained in:
EYHN
2025-03-25 14:15:29 +00:00
parent a10acf304b
commit 3df51a217d
28 changed files with 154 additions and 163 deletions

View File

@@ -1,4 +1,5 @@
import { Button, Divider, useLitPortalFactory } from '@affine/component'; import { Button, Divider, useLitPortalFactory } from '@affine/component';
import { useGuard } from '@affine/core/components/guard';
import { useEnableAI } from '@affine/core/components/hooks/affine/use-enable-ai'; import { useEnableAI } from '@affine/core/components/hooks/affine/use-enable-ai';
import { DocService } from '@affine/core/modules/doc'; import { DocService } from '@affine/core/modules/doc';
import { import {
@@ -7,7 +8,6 @@ import {
type Link, type Link,
} from '@affine/core/modules/doc-link'; } from '@affine/core/modules/doc-link';
import { toURLSearchParams } from '@affine/core/modules/navigation'; import { toURLSearchParams } from '@affine/core/modules/navigation';
import { GuardService } from '@affine/core/modules/permissions';
import { GlobalSessionStateService } from '@affine/core/modules/storage'; import { GlobalSessionStateService } from '@affine/core/modules/storage';
import { WorkbenchLink } from '@affine/core/modules/workbench'; import { WorkbenchLink } from '@affine/core/modules/workbench';
import { import {
@@ -26,7 +26,6 @@ import {
LiveData, LiveData,
useFramework, useFramework,
useLiveData, useLiveData,
useService,
useServices, useServices,
} from '@toeverything/infra'; } from '@toeverything/infra';
import { import {
@@ -265,8 +264,7 @@ export const LinkPreview = ({
linkGroup: BacklinkGroups; linkGroup: BacklinkGroups;
textRendererOptions: TextRendererOptions; textRendererOptions: TextRendererOptions;
}) => { }) => {
const guardService = useService(GuardService); const canAccess = useGuard('Doc_Read', linkGroup.docId);
const canAccess = useLiveData(guardService.can$('Doc_Read', linkGroup.docId));
const t = useI18n(); const t = useI18n();
if (!canAccess) { if (!canAccess) {

View File

@@ -6,6 +6,7 @@ import {
MenuSub, MenuSub,
} from '@affine/component/ui/menu'; } from '@affine/component/ui/menu';
import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal'; import { PageHistoryModal } from '@affine/core/components/affine/page-history-modal';
import { useGuard } from '@affine/core/components/guard';
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud'; import { useEnableCloud } from '@affine/core/components/hooks/affine/use-enable-cloud';
import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page'; import { useExportPage } from '@affine/core/components/hooks/affine/use-export-page';
@@ -333,8 +334,8 @@ const PageHeaderMenuItem = ({
openInAppService?.showOpenInAppPage(); openInAppService?.showOpenInAppPage();
}, [openInAppService]); }, [openInAppService]);
const canEdit = useLiveData(guardService.can$('Doc_Update', pageId)); const canEdit = useGuard('Doc_Update', pageId);
const canMoveToTrash = useLiveData(guardService.can$('Doc_Trash', pageId)); const canMoveToTrash = useGuard('Doc_Trash', pageId);
return ( return (
<> <>

View File

@@ -1,8 +1,8 @@
import type { InlineEditProps } from '@affine/component'; import type { InlineEditProps } from '@affine/component';
import { InlineEdit } from '@affine/component'; import { InlineEdit } from '@affine/component';
import { useGuard } from '@affine/core/components/guard';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { DocService, DocsService } from '@affine/core/modules/doc'; import { DocService, DocsService } from '@affine/core/modules/doc';
import { GuardService } from '@affine/core/modules/permissions';
import { WorkspaceService } from '@affine/core/modules/workspace'; import { WorkspaceService } from '@affine/core/modules/workspace';
import { track } from '@affine/track'; import { track } from '@affine/track';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
@@ -25,7 +25,6 @@ export const BlocksuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => {
const workspaceService = useService(WorkspaceService); const workspaceService = useService(WorkspaceService);
const isSharedMode = workspaceService.workspace.openOptions.isSharedMode; const isSharedMode = workspaceService.workspace.openOptions.isSharedMode;
const docsService = useService(DocsService); const docsService = useService(DocsService);
const guardService = useService(GuardService);
const docService = useService(DocService); const docService = useService(DocService);
const docTitle = useLiveData(docService.doc.record.title$); const docTitle = useLiveData(docService.doc.record.title$);
@@ -37,9 +36,7 @@ export const BlocksuiteHeaderTitle = (props: BlockSuiteHeaderTitleProps) => {
[docService.doc.id, docsService] [docService.doc.id, docsService]
); );
const canEdit = useLiveData( const canEdit = useGuard('Doc_Update', docService.doc.id);
guardService.can$('Doc_Update', docService.doc.id)
);
return ( return (
<InlineEdit <InlineEdit

View File

@@ -5,10 +5,7 @@ import { Modal, useConfirmModal } from '@affine/component/ui/modal';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
import { EditorService } from '@affine/core/modules/editor'; import { EditorService } from '@affine/core/modules/editor';
import { import { WorkspacePermissionService } from '@affine/core/modules/permissions';
GuardService,
WorkspacePermissionService,
} from '@affine/core/modules/permissions';
import { WorkspaceQuotaService } from '@affine/core/modules/quota'; import { WorkspaceQuotaService } from '@affine/core/modules/quota';
import { WorkspaceService } from '@affine/core/modules/workspace'; import { WorkspaceService } from '@affine/core/modules/workspace';
import { i18nTime, Trans, useI18n } from '@affine/i18n'; import { i18nTime, Trans, useI18n } from '@affine/i18n';
@@ -35,6 +32,7 @@ import { encodeStateAsUpdate } from 'yjs';
import { BlockSuiteEditor } from '../../../blocksuite/block-suite-editor'; import { BlockSuiteEditor } from '../../../blocksuite/block-suite-editor';
import { PureEditorModeSwitch } from '../../../blocksuite/block-suite-mode-switch'; import { PureEditorModeSwitch } from '../../../blocksuite/block-suite-mode-switch';
import { pageHistoryModalAtom } from '../../atoms/page-history'; import { pageHistoryModalAtom } from '../../atoms/page-history';
import { useGuard } from '../../guard';
import { AffineErrorBoundary } from '../affine-error-boundary'; import { AffineErrorBoundary } from '../affine-error-boundary';
import { import {
historyListGroupByDay, historyListGroupByDay,
@@ -408,8 +406,6 @@ const PageHistoryManager = ({
const workspaceId = docCollection.id; const workspaceId = docCollection.id;
const [activeVersion, setActiveVersion] = useState<string>(); const [activeVersion, setActiveVersion] = useState<string>();
const guardService = useService(GuardService);
const pageDocId = useMemo(() => { const pageDocId = useMemo(() => {
return docCollection.getDoc(pageId)?.spaceDoc.guid ?? pageId; return docCollection.getDoc(pageId)?.spaceDoc.guid ?? pageId;
}, [pageId, docCollection]); }, [pageId, docCollection]);
@@ -441,7 +437,7 @@ const PageHistoryManager = ({
const i18n = useI18n(); const i18n = useI18n();
const title = useLiveData(docDisplayMetaService.title$(pageId)); const title = useLiveData(docDisplayMetaService.title$(pageId));
const canEdit = useLiveData(guardService.can$('Doc_Update', pageDocId)); const canEdit = useGuard('Doc_Update', pageDocId);
const onConfirmRestore = useCallback(() => { const onConfirmRestore = useCallback(() => {
openConfirmModal({ openConfirmModal({

View File

@@ -8,7 +8,6 @@ import {
} from '@affine/component'; } from '@affine/component';
import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import { GuardService } from '@affine/core/modules/permissions';
import { WorkspaceService } from '@affine/core/modules/workspace'; import { WorkspaceService } from '@affine/core/modules/workspace';
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -17,6 +16,7 @@ import { useLiveData, useService } from '@toeverything/infra';
import clsx from 'clsx'; import clsx from 'clsx';
import { type HTMLProps, useCallback, useState } from 'react'; import { type HTMLProps, useCallback, useState } from 'react';
import { useGuard } from '../../guard';
import { DocPropertyIcon } from '../icons/doc-property-icon'; import { DocPropertyIcon } from '../icons/doc-property-icon';
import { EditDocPropertyMenuItems } from '../menu/edit-doc-property'; import { EditDocPropertyMenuItems } from '../menu/edit-doc-property';
import { import {
@@ -38,13 +38,10 @@ const PropertyItem = ({
) => void; ) => void;
}) => { }) => {
const t = useI18n(); const t = useI18n();
const guardService = useService(GuardService);
const workspaceService = useService(WorkspaceService); const workspaceService = useService(WorkspaceService);
const docsService = useService(DocsService); const docsService = useService(DocsService);
const [moreMenuOpen, setMoreMenuOpen] = useState(defaultOpenEditMenu); const [moreMenuOpen, setMoreMenuOpen] = useState(defaultOpenEditMenu);
const canEditPropertyInfo = useLiveData( const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
guardService.can$('Workspace_Properties_Update')
);
const typeInfo = isSupportedDocPropertyType(propertyInfo.type) const typeInfo = isSupportedDocPropertyType(propertyInfo.type)
? DocPropertyTypes[propertyInfo.type] ? DocPropertyTypes[propertyInfo.type]

View File

@@ -1,7 +1,6 @@
import { Divider, IconButton, Tooltip } from '@affine/component'; import { Divider, IconButton, Tooltip } from '@affine/component';
import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import { GuardService } from '@affine/core/modules/permissions';
import { generateUniqueNameInSequence } from '@affine/core/utils/unique-name'; import { generateUniqueNameInSequence } from '@affine/core/utils/unique-name';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import track from '@affine/track'; import track from '@affine/track';
@@ -13,6 +12,7 @@ import {
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { useGuard } from '../../guard';
import { DocPropertyManager } from '../manager'; import { DocPropertyManager } from '../manager';
import { import {
DocPropertyTypes, DocPropertyTypes,
@@ -29,12 +29,9 @@ export const DocPropertySidebar = () => {
const [newPropertyId, setNewPropertyId] = useState<string>(); const [newPropertyId, setNewPropertyId] = useState<string>();
const docsService = useService(DocsService); const docsService = useService(DocsService);
const guardService = useService(GuardService);
const propertyList = docsService.propertyList; const propertyList = docsService.propertyList;
const properties = useLiveData(propertyList.properties$); const properties = useLiveData(propertyList.properties$);
const canEditPropertyInfo = useLiveData( const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
guardService.can$('Workspace_Properties_Update')
);
const onAddProperty = useCallback( const onAddProperty = useCallback(
(option: { type: string; name: string }) => { (option: { type: string; name: string }) => {
if (!isSupportedDocPropertyType(option.type)) { if (!isSupportedDocPropertyType(option.type)) {

View File

@@ -16,7 +16,6 @@ import type {
DatabaseValueCell, DatabaseValueCell,
} from '@affine/core/modules/doc-info/types'; } from '@affine/core/modules/doc-info/types';
import { DocIntegrationPropertiesTable } from '@affine/core/modules/integration'; import { DocIntegrationPropertiesTable } from '@affine/core/modules/integration';
import { GuardService } from '@affine/core/modules/permissions';
import { ViewService, WorkbenchService } from '@affine/core/modules/workbench'; import { ViewService, WorkbenchService } from '@affine/core/modules/workbench';
import type { AffineDNDData } from '@affine/core/types/dnd'; import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -32,6 +31,7 @@ import clsx from 'clsx';
import type React from 'react'; import type React from 'react';
import { forwardRef, useCallback, useMemo, useState } from 'react'; import { forwardRef, useCallback, useMemo, useState } from 'react';
import { useGuard } from '../guard';
import { DocPropertyIcon } from './icons/doc-property-icon'; import { DocPropertyIcon } from './icons/doc-property-icon';
import { CreatePropertyMenuItems } from './menu/create-doc-property'; import { CreatePropertyMenuItems } from './menu/create-doc-property';
import { EditDocPropertyMenuItems } from './menu/edit-doc-property'; import { EditDocPropertyMenuItems } from './menu/edit-doc-property';
@@ -290,18 +290,13 @@ const DocWorkspacePropertiesTableBody = forwardRef<
const workbenchService = useService(WorkbenchService); const workbenchService = useService(WorkbenchService);
const viewService = useServiceOptional(ViewService); const viewService = useServiceOptional(ViewService);
const docService = useService(DocService); const docService = useService(DocService);
const guardService = useService(GuardService);
const properties = useLiveData(docsService.propertyList.sortedProperties$); const properties = useLiveData(docsService.propertyList.sortedProperties$);
const [addMoreCollapsed, setAddMoreCollapsed] = useState(true); const [addMoreCollapsed, setAddMoreCollapsed] = useState(true);
const [newPropertyId, setNewPropertyId] = useState<string | null>(null); const [newPropertyId, setNewPropertyId] = useState<string | null>(null);
const canEditProperty = useLiveData( const canEditProperty = useGuard('Doc_Update', docService.doc.id);
guardService.can$('Doc_Update', docService.doc.id) const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
);
const canEditPropertyInfo = useLiveData(
guardService.can$('Workspace_Properties_Update')
);
const handlePropertyAdded = useCallback( const handlePropertyAdded = useCallback(
(property: DocCustomPropertyInfo) => { (property: DocCustomPropertyInfo) => {

View File

@@ -1,24 +0,0 @@
import {
type DocPermissionActions,
GuardService,
} from '@affine/core/modules/permissions';
import { useLiveData, useService } from '@toeverything/infra';
import type React from 'react';
export const DocPermissionGuard = ({
docId,
children,
permission,
}: {
docId: string;
permission: DocPermissionActions;
children: (can: boolean | undefined) => React.ReactNode;
}) => {
const guardService = useService(GuardService);
const can = useLiveData(guardService.can$(permission, docId));
if (typeof children === 'function') {
return children(can);
}
throw new Error('children must be a function');
};

View File

@@ -0,0 +1,25 @@
import {
type DocPermissionActions,
type WorkspacePermissionActions,
} from '@affine/core/modules/permissions';
import type React from 'react';
import { useGuard } from './use-guard';
export const Guard = <
T extends WorkspacePermissionActions | DocPermissionActions,
>(
props: {
permission: T;
children: (can: boolean | undefined) => React.ReactNode;
} & (T extends DocPermissionActions ? { docId: string } : {})
) => {
const { permission, children, ...rest } = props;
const docId = 'docId' in rest ? [rest.docId] : [];
const can = useGuard(permission, ...(docId as any));
if (typeof children === 'function') {
return children(can);
}
throw new Error('children must be a function');
};

View File

@@ -0,0 +1,2 @@
export * from './guard';
export * from './use-guard';

View File

@@ -0,0 +1,29 @@
import {
type DocPermissionActions,
GuardService,
type WorkspacePermissionActions,
} from '@affine/core/modules/permissions';
import { useLiveData, useService } from '@toeverything/infra';
import { useEffect, useMemo } from 'react';
export const useGuard = <
T extends WorkspacePermissionActions | DocPermissionActions,
>(
action: T,
...args: T extends DocPermissionActions ? [string] : []
) => {
const guardService = useService(GuardService);
useEffect(() => {
guardService.revalidateCan(action, ...args);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [action, guardService, ...args]);
const livedata$ = useMemo(
() => guardService.can$(action, ...args),
// eslint-disable-next-line react-hooks/exhaustive-deps
[action, guardService, ...args]
);
const can = useLiveData(livedata$);
return can;
};

View File

@@ -14,7 +14,6 @@ import {
CompatibleFavoriteItemsAdapter, CompatibleFavoriteItemsAdapter,
FavoriteService, FavoriteService,
} from '@affine/core/modules/favorite'; } from '@affine/core/modules/favorite';
import { GuardService } from '@affine/core/modules/permissions';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace'; import { WorkspaceService } from '@affine/core/modules/workspace';
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter'; import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
@@ -41,6 +40,7 @@ import { useCallback, useState } from 'react';
import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils'; import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
import type { CollectionService } from '../../modules/collection'; import type { CollectionService } from '../../modules/collection';
import { useGuard } from '../guard';
import { IsFavoriteIcon } from '../pure/icons'; import { IsFavoriteIcon } from '../pure/icons';
import { FavoriteTag } from './components/favorite-tag'; import { FavoriteTag } from './components/favorite-tag';
import * as styles from './list.css'; import * as styles from './list.css';
@@ -68,15 +68,13 @@ const PageOperationCellMenuItem = ({
workspaceService, workspaceService,
compatibleFavoriteItemsAdapter: favAdapter, compatibleFavoriteItemsAdapter: favAdapter,
workbenchService, workbenchService,
guardService,
} = useServices({ } = useServices({
WorkspaceService, WorkspaceService,
CompatibleFavoriteItemsAdapter, CompatibleFavoriteItemsAdapter,
WorkbenchService, WorkbenchService,
GuardService,
}); });
const canMoveToTrash = useLiveData(guardService.can$('Doc_Trash', page.id)); const canMoveToTrash = useGuard('Doc_Trash', page.id);
const currentWorkspace = workspaceService.workspace; const currentWorkspace = workspaceService.workspace;
const favourite = useLiveData(favAdapter.isFavorite$(page.id, 'doc')); const favourite = useLiveData(favAdapter.isFavorite$(page.id, 'doc'));
const workbench = workbenchService.workbench; const workbench = workbenchService.workbench;

View File

@@ -6,13 +6,13 @@ import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-
import { EditorOutlineViewer } from '@affine/core/blocksuite/outline-viewer'; import { EditorOutlineViewer } from '@affine/core/blocksuite/outline-viewer';
import { PageAIOnboarding } from '@affine/core/components/affine/ai-onboarding'; import { PageAIOnboarding } from '@affine/core/components/affine/ai-onboarding';
import { DocPropertySidebar } from '@affine/core/components/doc-properties/sidebar'; import { DocPropertySidebar } from '@affine/core/components/doc-properties/sidebar';
import { useGuard } from '@affine/core/components/guard';
import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper'; import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper';
import { useEnableAI } from '@affine/core/components/hooks/affine/use-enable-ai'; import { useEnableAI } from '@affine/core/components/hooks/affine/use-enable-ai';
import { DocService } from '@affine/core/modules/doc'; import { DocService } from '@affine/core/modules/doc';
import { EditorService } from '@affine/core/modules/editor'; import { EditorService } from '@affine/core/modules/editor';
import { GlobalContextService } from '@affine/core/modules/global-context'; import { GlobalContextService } from '@affine/core/modules/global-context';
import { PeekViewService } from '@affine/core/modules/peek-view'; import { PeekViewService } from '@affine/core/modules/peek-view';
import { GuardService } from '@affine/core/modules/permissions';
import { RecentDocsService } from '@affine/core/modules/quicksearch'; import { RecentDocsService } from '@affine/core/modules/quicksearch';
import { ViewService } from '@affine/core/modules/workbench'; import { ViewService } from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace'; import { WorkspaceService } from '@affine/core/modules/workspace';
@@ -69,7 +69,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
docService, docService,
workspaceService, workspaceService,
globalContextService, globalContextService,
guardService,
} = useServices({ } = useServices({
WorkbenchService, WorkbenchService,
ViewService, ViewService,
@@ -77,7 +76,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
DocService, DocService,
WorkspaceService, WorkspaceService,
GlobalContextService, GlobalContextService,
GuardService,
}); });
const workbench = workbenchService.workbench; const workbench = workbenchService.workbench;
const editor = editorService.editor; const editor = editorService.editor;
@@ -262,7 +260,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
const [dragging, setDragging] = useState(false); const [dragging, setDragging] = useState(false);
const canEdit = useLiveData(guardService.can$('Doc_Update', doc.id)); const canEdit = useGuard('Doc_Update', doc.id);
const readonly = !canEdit || isInTrash; const readonly = !canEdit || isInTrash;
@@ -368,7 +366,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
export const Component = () => { export const Component = () => {
const params = useParams(); const params = useParams();
const recentPages = useService(RecentDocsService); const recentPages = useService(RecentDocsService);
const guardService = useService(GuardService);
useEffect(() => { useEffect(() => {
if (params.pageId) { if (params.pageId) {
@@ -380,9 +377,7 @@ export const Component = () => {
}, [params, recentPages]); }, [params, recentPages]);
const pageId = params.pageId; const pageId = params.pageId;
const canAccess = useLiveData( const canAccess = useGuard('Doc_Read', pageId ?? '');
pageId ? guardService.can$('Doc_Read', pageId) : undefined
);
return pageId ? ( return pageId ? (
<DetailPageWrapper <DetailPageWrapper

View File

@@ -8,7 +8,7 @@ import {
Scrollable, Scrollable,
useConfirmModal, useConfirmModal,
} from '@affine/component'; } from '@affine/component';
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard'; import { Guard } from '@affine/core/components/guard';
import { useJournalRouteHelper } from '@affine/core/components/hooks/use-journal'; import { useJournalRouteHelper } from '@affine/core/components/hooks/use-journal';
import { MoveToTrash } from '@affine/core/components/page-list'; import { MoveToTrash } from '@affine/core/components/page-list';
import { import {
@@ -367,10 +367,7 @@ const ConflictList = ({
}} }}
items={ items={
<> <>
<DocPermissionGuard <Guard docId={docRecord.id} permission="Doc_Update">
docId={docRecord.id}
permission="Doc_Update"
>
{canEdit => ( {canEdit => (
<MenuItem <MenuItem
prefixIcon={<CalendarXmarkIcon />} prefixIcon={<CalendarXmarkIcon />}
@@ -386,19 +383,16 @@ const ConflictList = ({
]()} ]()}
</MenuItem> </MenuItem>
)} )}
</DocPermissionGuard> </Guard>
<MenuSeparator /> <MenuSeparator />
<DocPermissionGuard <Guard docId={docRecord.id} permission="Doc_Trash">
docId={docRecord.id}
permission="Doc_Trash"
>
{canTrash => ( {canTrash => (
<MoveToTrash <MoveToTrash
onSelect={() => handleOpenTrashModal(docRecord)} onSelect={() => handleOpenTrashModal(docRecord)}
disabled={!canTrash} disabled={!canTrash}
/> />
)} )}
</DocPermissionGuard> </Guard>
</> </>
} }
> >

View File

@@ -11,13 +11,13 @@ import {
DocPropertyRow, DocPropertyRow,
} from '@affine/core/components/doc-properties'; } from '@affine/core/components/doc-properties';
import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property'; import { CreatePropertyMenuItems } from '@affine/core/components/doc-properties/menu/create-doc-property';
import { useGuard } from '@affine/core/components/guard';
import { LinksRow } from '@affine/core/desktop/dialogs/doc-info/links-row'; import { LinksRow } from '@affine/core/desktop/dialogs/doc-info/links-row';
import { TimeRow } from '@affine/core/desktop/dialogs/doc-info/time-row'; import { TimeRow } from '@affine/core/desktop/dialogs/doc-info/time-row';
import type { DocCustomPropertyInfo } from '@affine/core/modules/db'; import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import { DocDatabaseBacklinkInfo } from '@affine/core/modules/doc-info'; import { DocDatabaseBacklinkInfo } from '@affine/core/modules/doc-info';
import { DocsSearchService } from '@affine/core/modules/docs-search'; import { DocsSearchService } from '@affine/core/modules/docs-search';
import { GuardService } from '@affine/core/modules/permissions';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { PlusIcon } from '@blocksuite/icons/rc'; import { PlusIcon } from '@blocksuite/icons/rc';
import { LiveData, useLiveData, useServices } from '@toeverything/infra'; import { LiveData, useLiveData, useServices } from '@toeverything/infra';
@@ -31,17 +31,14 @@ export const DocInfoSheet = ({
docId: string; docId: string;
defaultOpenProperty?: DefaultOpenProperty; defaultOpenProperty?: DefaultOpenProperty;
}) => { }) => {
const { docsSearchService, docsService, guardService } = useServices({ const { docsSearchService, docsService } = useServices({
DocsSearchService, DocsSearchService,
DocsService, DocsService,
GuardService,
}); });
const t = useI18n(); const t = useI18n();
const canEditPropertyInfo = useLiveData( const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
guardService.can$('Workspace_Properties_Update') const canEditProperty = useGuard('Doc_Update', docId);
);
const canEditProperty = useLiveData(guardService.can$('Doc_Update', docId));
const links = useLiveData( const links = useLiveData(
useMemo( useMemo(
() => LiveData.from(docsSearchService.watchRefsFrom(docId), null), () => LiveData.from(docsSearchService.watchRefsFrom(docId), null),

View File

@@ -1,5 +1,5 @@
import { Loading } from '@affine/component'; import { Loading } from '@affine/component';
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard'; import { Guard } from '@affine/core/components/guard';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta';
@@ -134,7 +134,7 @@ export const ExplorerDocNode = ({
operations={finalOperations} operations={finalOperations}
data-testid={`explorer-doc-${docId}`} data-testid={`explorer-doc-${docId}`}
> >
<DocPermissionGuard docId={docId} permission="Doc_Read"> <Guard docId={docId} permission="Doc_Read">
{canRead => {canRead =>
canRead canRead
? children?.map((child, index) => ( ? children?.map((child, index) => (
@@ -146,8 +146,8 @@ export const ExplorerDocNode = ({
)) ))
: null : null
} }
</DocPermissionGuard> </Guard>
<DocPermissionGuard docId={docId} permission="Doc_Update"> <Guard docId={docId} permission="Doc_Update">
{canEdit => {canEdit =>
canEdit ? ( canEdit ? (
<AddItemPlaceholder <AddItemPlaceholder
@@ -156,7 +156,7 @@ export const ExplorerDocNode = ({
/> />
) : null ) : null
} }
</DocPermissionGuard> </Guard>
</ExplorerTreeNode> </ExplorerTreeNode>
); );
}; };

View File

@@ -6,7 +6,7 @@ import {
useConfirmModal, useConfirmModal,
} from '@affine/component'; } from '@affine/component';
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils'; import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard'; import { Guard } from '@affine/core/components/guard';
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
@@ -187,7 +187,7 @@ export const useExplorerDocNodeOperationsMenu = (
{ {
index: 10, index: 10,
view: ( view: (
<DocPermissionGuard docId={docId} permission="Doc_Update"> <Guard docId={docId} permission="Doc_Update">
{canEdit => ( {canEdit => (
<DocRenameSubMenu <DocRenameSubMenu
onConfirm={handleRename} onConfirm={handleRename}
@@ -195,7 +195,7 @@ export const useExplorerDocNodeOperationsMenu = (
disabled={!canEdit} disabled={!canEdit}
/> />
)} )}
</DocPermissionGuard> </Guard>
), ),
}, },
{ {
@@ -224,7 +224,7 @@ export const useExplorerDocNodeOperationsMenu = (
{ {
index: 97, index: 97,
view: ( view: (
<DocPermissionGuard docId={docId} permission="Doc_Update"> <Guard docId={docId} permission="Doc_Update">
{canEdit => ( {canEdit => (
<MenuItem <MenuItem
prefixIcon={<LinkedPageIcon />} prefixIcon={<LinkedPageIcon />}
@@ -234,7 +234,7 @@ export const useExplorerDocNodeOperationsMenu = (
{t['com.affine.page-operation.add-linked-page']()} {t['com.affine.page-operation.add-linked-page']()}
</MenuItem> </MenuItem>
)} )}
</DocPermissionGuard> </Guard>
), ),
}, },
{ {
@@ -273,7 +273,7 @@ export const useExplorerDocNodeOperationsMenu = (
{ {
index: 10000, index: 10000,
view: ( view: (
<DocPermissionGuard docId={docId} permission="Doc_Trash"> <Guard docId={docId} permission="Doc_Trash">
{canMoveToTrash => ( {canMoveToTrash => (
<MenuItem <MenuItem
type={'danger'} type={'danger'}
@@ -284,7 +284,7 @@ export const useExplorerDocNodeOperationsMenu = (
{t['com.affine.moveToTrash.title']()} {t['com.affine.moveToTrash.title']()}
</MenuItem> </MenuItem>
)} )}
</DocPermissionGuard> </Guard>
), ),
}, },
], ],

View File

@@ -5,7 +5,7 @@ import {
MobileMenuSub, MobileMenuSub,
useConfirmModal, useConfirmModal,
} from '@affine/component'; } from '@affine/component';
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard'; import { Guard } from '@affine/core/components/guard';
import { MoveToTrash } from '@affine/core/components/page-list'; import { MoveToTrash } from '@affine/core/components/page-list';
import { import {
type DocRecord, type DocRecord,
@@ -59,7 +59,7 @@ export const ResolveConflictOperations = ({
return ( return (
<> <>
<DocPermissionGuard docId={docRecord.id} permission="Doc_Update"> <Guard docId={docRecord.id} permission="Doc_Update">
{canEdit => ( {canEdit => (
<MobileMenuItem <MobileMenuItem
prefixIcon={<CalendarXmarkIcon />} prefixIcon={<CalendarXmarkIcon />}
@@ -72,15 +72,15 @@ export const ResolveConflictOperations = ({
{t['com.affine.page-properties.property.journal-remove']()} {t['com.affine.page-properties.property.journal-remove']()}
</MobileMenuItem> </MobileMenuItem>
)} )}
</DocPermissionGuard> </Guard>
<DocPermissionGuard docId={docRecord.id} permission="Doc_Trash"> <Guard docId={docRecord.id} permission="Doc_Trash">
{canTrash => ( {canTrash => (
<MoveToTrash <MoveToTrash
onSelect={() => handleOpenTrashModal(docRecord)} onSelect={() => handleOpenTrashModal(docRecord)}
disabled={!canTrash} disabled={!canTrash}
/> />
)} )}
</DocPermissionGuard> </Guard>
</> </>
); );
}; };

View File

@@ -2,6 +2,7 @@ import { useThemeColorV2 } from '@affine/component';
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton'; import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor'; import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary'; import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
import { useGuard } from '@affine/core/components/guard';
import { useActiveBlocksuiteEditor } from '@affine/core/components/hooks/use-block-suite-editor'; import { useActiveBlocksuiteEditor } from '@affine/core/components/hooks/use-block-suite-editor';
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper'; import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
import { PageDetailEditor } from '@affine/core/components/page-detail-editor'; import { PageDetailEditor } from '@affine/core/components/page-detail-editor';
@@ -16,7 +17,6 @@ import { EditorService } from '@affine/core/modules/editor';
import { FeatureFlagService } from '@affine/core/modules/feature-flag'; import { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { GlobalContextService } from '@affine/core/modules/global-context'; import { GlobalContextService } from '@affine/core/modules/global-context';
import { JournalService } from '@affine/core/modules/journal'; import { JournalService } from '@affine/core/modules/journal';
import { GuardService } from '@affine/core/modules/permissions';
import { WorkbenchService } from '@affine/core/modules/workbench'; import { WorkbenchService } from '@affine/core/modules/workbench';
import { ViewService } from '@affine/core/modules/workbench/services/view'; import { ViewService } from '@affine/core/modules/workbench/services/view';
import { WorkspaceService } from '@affine/core/modules/workspace'; import { WorkspaceService } from '@affine/core/modules/workspace';
@@ -55,7 +55,6 @@ const DetailPageImpl = () => {
globalContextService, globalContextService,
featureFlagService, featureFlagService,
aIButtonService, aIButtonService,
guardService,
} = useServices({ } = useServices({
WorkbenchService, WorkbenchService,
ViewService, ViewService,
@@ -65,7 +64,6 @@ const DetailPageImpl = () => {
GlobalContextService, GlobalContextService,
FeatureFlagService, FeatureFlagService,
AIButtonService, AIButtonService,
GuardService,
}); });
const editor = editorService.editor; const editor = editorService.editor;
const workspace = workspaceService.workspace; const workspace = workspaceService.workspace;
@@ -192,7 +190,7 @@ const DetailPageImpl = () => {
[docCollection.id, editor, jumpToPageBlock, openPage, server] [docCollection.id, editor, jumpToPageBlock, openPage, server]
); );
const canEdit = useLiveData(guardService.can$('Doc_Update', doc.id)); const canEdit = useGuard('Doc_Update', doc.id);
const readonly = const readonly =
!canEdit || !canEdit ||
@@ -254,8 +252,7 @@ const MobileDetailPage = ({
const [showTitle, setShowTitle] = useState(checkShowTitle); const [showTitle, setShowTitle] = useState(checkShowTitle);
const title = useLiveData(docDisplayMetaService.title$(pageId)); const title = useLiveData(docDisplayMetaService.title$(pageId));
const guardService = useService(GuardService); const canAccess = useGuard('Doc_Read', pageId);
const canAccess = useLiveData(guardService.can$('Doc_Read', pageId));
const allJournalDates = useLiveData(journalService.allJournalDates$); const allJournalDates = useLiveData(journalService.allJournalDates$);

View File

@@ -6,12 +6,12 @@ import {
MobileMenuItem, MobileMenuItem,
} from '@affine/component/ui/menu'; } from '@affine/component/ui/menu';
import { useFavorite } from '@affine/core/blocksuite/block-suite-header/favorite'; import { useFavorite } from '@affine/core/blocksuite/block-suite-header/favorite';
import { useGuard } from '@affine/core/components/guard';
import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
import { DocInfoSheet } from '@affine/core/mobile/components'; import { DocInfoSheet } from '@affine/core/mobile/components';
import { MobileTocMenu } from '@affine/core/mobile/components/toc-menu'; import { MobileTocMenu } from '@affine/core/mobile/components/toc-menu';
import { DocService } from '@affine/core/modules/doc'; import { DocService } from '@affine/core/modules/doc';
import { EditorService } from '@affine/core/modules/editor'; import { EditorService } from '@affine/core/modules/editor';
import { GuardService } from '@affine/core/modules/permissions';
import { ViewService } from '@affine/core/modules/workbench/services/view'; import { ViewService } from '@affine/core/modules/workbench/services/view';
import { preventDefault } from '@affine/core/utils'; import { preventDefault } from '@affine/core/utils';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
@@ -35,8 +35,7 @@ export const PageHeaderMenuButton = () => {
const t = useI18n(); const t = useI18n();
const docId = useService(DocService).doc.id; const docId = useService(DocService).doc.id;
const guardService = useService(GuardService); const canEdit = useGuard('Doc_Update', docId);
const canEdit = useLiveData(guardService.can$('Doc_Update', docId));
const editorService = useService(EditorService); const editorService = useService(EditorService);
const editorContainer = useLiveData(editorService.editor.editorContainer$); const editorContainer = useLiveData(editorService.editor.editorContainer$);

View File

@@ -5,7 +5,7 @@ import {
toast, toast,
Tooltip, Tooltip,
} from '@affine/component'; } from '@affine/component';
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard'; import { Guard } from '@affine/core/components/guard';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { WorkspaceDialogService } from '@affine/core/modules/dialogs'; import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
import { DocsService } from '@affine/core/modules/doc'; import { DocsService } from '@affine/core/modules/doc';
@@ -274,7 +274,7 @@ export const ExplorerDocNode = ({
dropEffect={handleDropEffectOnDoc} dropEffect={handleDropEffectOnDoc}
data-testid={`explorer-doc-${docId}`} data-testid={`explorer-doc-${docId}`}
> >
<DocPermissionGuard docId={docId} permission="Doc_Read"> <Guard docId={docId} permission="Doc_Read">
{canRead => {canRead =>
canRead canRead
? children?.map((child, index) => ( ? children?.map((child, index) => (
@@ -291,7 +291,7 @@ export const ExplorerDocNode = ({
)) ))
: null : null
} }
</DocPermissionGuard> </Guard>
</ExplorerTreeNode> </ExplorerTreeNode>
); );
}; };

View File

@@ -6,7 +6,7 @@ import {
useConfirmModal, useConfirmModal,
} from '@affine/component'; } from '@affine/component';
import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils'; import { usePageHelper } from '@affine/core/blocksuite/block-suite-page-list/utils';
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard'; import { Guard } from '@affine/core/components/guard';
import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper'; import { useBlockSuiteMetaHelper } from '@affine/core/components/hooks/affine/use-block-suite-meta-helper';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { IsFavoriteIcon } from '@affine/core/components/pure/icons'; import { IsFavoriteIcon } from '@affine/core/components/pure/icons';
@@ -175,7 +175,7 @@ export const useExplorerDocNodeOperations = (
{ {
index: 99, index: 99,
view: ( view: (
<DocPermissionGuard docId={docId} permission="Doc_Update"> <Guard docId={docId} permission="Doc_Update">
{canEdit => ( {canEdit => (
<MenuItem <MenuItem
prefixIcon={<LinkedPageIcon />} prefixIcon={<LinkedPageIcon />}
@@ -185,7 +185,7 @@ export const useExplorerDocNodeOperations = (
{t['com.affine.page-operation.add-linked-page']()} {t['com.affine.page-operation.add-linked-page']()}
</MenuItem> </MenuItem>
)} )}
</DocPermissionGuard> </Guard>
), ),
}, },
{ {
@@ -239,7 +239,7 @@ export const useExplorerDocNodeOperations = (
{ {
index: 10000, index: 10000,
view: ( view: (
<DocPermissionGuard docId={docId} permission="Doc_Trash"> <Guard docId={docId} permission="Doc_Trash">
{canMoveToTrash => ( {canMoveToTrash => (
<MenuItem <MenuItem
type={'danger'} type={'danger'}
@@ -250,7 +250,7 @@ export const useExplorerDocNodeOperations = (
{t['com.affine.moveToTrash.title']()} {t['com.affine.moveToTrash.title']()}
</MenuItem> </MenuItem>
)} )}
</DocPermissionGuard> </Guard>
), ),
}, },
], ],

View File

@@ -10,7 +10,7 @@ import {
useDropTarget, useDropTarget,
} from '@affine/component'; } from '@affine/component';
import { RenameModal } from '@affine/component/rename-modal'; import { RenameModal } from '@affine/component/rename-modal';
import { DocPermissionGuard } from '@affine/core/components/guard/doc-guard'; import { Guard } from '@affine/core/components/guard';
import { AppSidebarService } from '@affine/core/modules/app-sidebar'; import { AppSidebarService } from '@affine/core/modules/app-sidebar';
import type { DocPermissionActions } from '@affine/core/modules/permissions'; import type { DocPermissionActions } from '@affine/core/modules/permissions';
import { WorkbenchLink } from '@affine/core/modules/workbench'; import { WorkbenchLink } from '@affine/core/modules/workbench';
@@ -285,7 +285,7 @@ export const ExplorerTreeNode = ({
? { ? {
index: 0, index: 0,
view: renameableGuard ? ( view: renameableGuard ? (
<DocPermissionGuard <Guard
permission={renameableGuard.action} permission={renameableGuard.action}
docId={renameableGuard.docId} docId={renameableGuard.docId}
> >
@@ -300,7 +300,7 @@ export const ExplorerTreeNode = ({
{t['com.affine.menu.rename']()} {t['com.affine.menu.rename']()}
</MenuItem> </MenuItem>
)} )}
</DocPermissionGuard> </Guard>
) : ( ) : (
<MenuItem <MenuItem
key={'explorer-tree-rename'} key={'explorer-tree-rename'}

View File

@@ -4,9 +4,9 @@ import { AIProvider } from '@affine/core/blocksuite/ai';
import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor'; import type { AffineEditorContainer } from '@affine/core/blocksuite/block-suite-editor';
import { EditorOutlineViewer } from '@affine/core/blocksuite/outline-viewer'; import { EditorOutlineViewer } from '@affine/core/blocksuite/outline-viewer';
import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary'; import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary';
import { useGuard } from '@affine/core/components/guard';
import { PageNotFound } from '@affine/core/desktop/pages/404'; import { PageNotFound } from '@affine/core/desktop/pages/404';
import { EditorService } from '@affine/core/modules/editor'; import { EditorService } from '@affine/core/modules/editor';
import { GuardService } from '@affine/core/modules/permissions';
import { DebugLogger } from '@affine/debug'; import { DebugLogger } from '@affine/debug';
import { GfxControllerIdentifier } from '@blocksuite/affine/block-std/gfx'; import { GfxControllerIdentifier } from '@blocksuite/affine/block-std/gfx';
import { DisposableGroup } from '@blocksuite/affine/global/disposable'; import { DisposableGroup } from '@blocksuite/affine/global/disposable';
@@ -85,7 +85,6 @@ function DocPeekPreviewEditor({
const defaultOpenProperty = useLiveData(editor.defaultOpenProperty$); const defaultOpenProperty = useLiveData(editor.defaultOpenProperty$);
const workbench = useService(WorkbenchService).workbench; const workbench = useService(WorkbenchService).workbench;
const peekView = useService(PeekViewService).peekView; const peekView = useService(PeekViewService).peekView;
const guardService = useService(GuardService);
const editorElement = useLiveData(editor.editorContainer$); const editorElement = useLiveData(editor.editorContainer$);
const isInTrash = useLiveData(doc.record.trash$); const isInTrash = useLiveData(doc.record.trash$);
@@ -151,7 +150,7 @@ function DocPeekPreviewEditor({
peekView.close(); peekView.close();
}, [doc, peekView, workbench]); }, [doc, peekView, workbench]);
const canEdit = useLiveData(guardService.can$('Doc_Update', doc.id)); const canEdit = useGuard('Doc_Update', doc.id);
const readonly = !canEdit || isInTrash; const readonly = !canEdit || isInTrash;
@@ -220,8 +219,7 @@ export function DocPeekPreview({
!animating !animating
); );
const guardService = useService(GuardService); const canAccess = useGuard('Doc_Read', docId);
const canAccess = useLiveData(guardService.can$('Doc_Read', docId));
// if sync engine has been synced and the page is null, show 404 page. // if sync engine has been synced and the page is null, show 404 page.
if (!doc || !editor || !canAccess) { if (!doc || !editor || !canAccess) {

View File

@@ -66,17 +66,6 @@ export class GuardService extends Service {
const docId = args[0]; const docId = args[0];
return LiveData.from( return LiveData.from(
new Observable(subscriber => { new Observable(subscriber => {
// revalidate permission
if (docId) {
this.revalidateDocPermission(docId);
} else {
this.revalidateWorkspacePermission();
}
// revalidate workspace permission if it's not initialized
if (this.isAdmin$.value === null) {
this.workspacePermissionService.permission.revalidate();
}
let prev: boolean | undefined = undefined; let prev: boolean | undefined = undefined;
const subscription = combineLatest([ const subscription = combineLatest([
@@ -128,6 +117,29 @@ export class GuardService extends Service {
return permissions[action as keyof typeof permissions] ?? false; return permissions[action as keyof typeof permissions] ?? false;
} }
revalidateCan<T extends WorkspacePermissionActions | DocPermissionActions>(
_action: T,
...args: T extends DocPermissionActions ? [string] : []
) {
// revalidate workspace permission if it's not initialized
if (this.isAdmin$.value === null) {
this.workspacePermissionService.permission.revalidate();
}
if (this.isAdmin$.value === true) {
// if the user is admin, the permission is always true
return;
}
const docId = args[0];
// revalidate permission
if (docId) {
this.revalidateDocPermission(docId);
} else {
this.revalidateWorkspacePermission();
}
}
private readonly revalidateWorkspacePermission = effect( private readonly revalidateWorkspacePermission = effect(
exhaustMapWithTrailing(() => exhaustMapWithTrailing(() =>
fromPromise(() => this.guardStore.getWorkspacePermissions()).pipe( fromPromise(() => this.guardStore.getWorkspacePermissions()).pipe(

View File

@@ -8,12 +8,12 @@ import {
Tooltip, Tooltip,
useConfirmModal, useConfirmModal,
} from '@affine/component'; } from '@affine/component';
import { useGuard } from '@affine/core/components/guard';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { DocService } from '@affine/core/modules/doc'; import { DocService } from '@affine/core/modules/doc';
import { import {
DocGrantedUsersService, DocGrantedUsersService,
type GrantedUser, type GrantedUser,
GuardService,
WorkspacePermissionService, WorkspacePermissionService,
} from '@affine/core/modules/permissions'; } from '@affine/core/modules/permissions';
import { UserFriendlyError } from '@affine/error'; import { UserFriendlyError } from '@affine/error';
@@ -131,7 +131,6 @@ const Options = ({
const t = useI18n(); const t = useI18n();
const docGrantedUsersService = useService(DocGrantedUsersService); const docGrantedUsersService = useService(DocGrantedUsersService);
const docService = useService(DocService); const docService = useService(DocService);
const guardService = useService(GuardService);
const workspacePermissionService = useService(WorkspacePermissionService); const workspacePermissionService = useService(WorkspacePermissionService);
const isWorkspaceOwner = useLiveData( const isWorkspaceOwner = useLiveData(
workspacePermissionService.permission.isOwner$ workspacePermissionService.permission.isOwner$
@@ -140,11 +139,8 @@ const Options = ({
const { openConfirmModal } = useConfirmModal(); const { openConfirmModal } = useConfirmModal();
const canTransferOwner = const canTransferOwner =
useLiveData(guardService.can$('Doc_TransferOwner', docService.doc.id)) && useGuard('Doc_TransferOwner', docService.doc.id) && !!isWorkspaceOwner;
!!isWorkspaceOwner; const canManageUsers = useGuard('Doc_Users_Manage', docService.doc.id);
const canManageUsers = useLiveData(
guardService.can$('Doc_Users_Manage', docService.doc.id)
);
const updateUserRole = useCallback( const updateUserRole = useCallback(
async (userId: string, role: DocRole) => { async (userId: string, role: DocRole) => {

View File

@@ -1,9 +1,9 @@
import { Skeleton } from '@affine/component'; import { Skeleton } from '@affine/component';
import { useGuard } from '@affine/core/components/guard';
import { DocService } from '@affine/core/modules/doc'; import { DocService } from '@affine/core/modules/doc';
import { import {
DocGrantedUsersService, DocGrantedUsersService,
type GrantedUser, type GrantedUser,
GuardService,
} from '@affine/core/modules/permissions'; } from '@affine/core/modules/permissions';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { ArrowLeftBigIcon } from '@blocksuite/icons/rc'; import { ArrowLeftBigIcon } from '@blocksuite/icons/rc';
@@ -33,11 +33,8 @@ export const MemberManagement = ({
docGrantedUsersService.grantedUserCount$ docGrantedUsersService.grantedUserCount$
); );
const docService = useService(DocService); const docService = useService(DocService);
const guardService = useService(GuardService);
const canManageUsers = useLiveData( const canManageUsers = useGuard('Doc_Users_Manage', docService.doc.id);
guardService.can$('Doc_Users_Manage', docService.doc.id)
);
const t = useI18n(); const t = useI18n();

View File

@@ -1,8 +1,8 @@
import { Divider, Skeleton } from '@affine/component'; import { Divider, Skeleton } from '@affine/component';
import { Button } from '@affine/component/ui/button'; import { Button } from '@affine/component/ui/button';
import { useGuard } from '@affine/core/components/guard';
import { ServerService } from '@affine/core/modules/cloud'; import { ServerService } from '@affine/core/modules/cloud';
import { DocService } from '@affine/core/modules/doc'; import { DocService } from '@affine/core/modules/doc';
import { GuardService } from '@affine/core/modules/permissions';
import { ShareInfoService } from '@affine/core/modules/share-doc'; import { ShareInfoService } from '@affine/core/modules/share-doc';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
@@ -64,15 +64,10 @@ export const AFFiNESharePage = (
const shareInfoService = useService(ShareInfoService); const shareInfoService = useService(ShareInfoService);
const serverService = useService(ServerService); const serverService = useService(ServerService);
const docService = useService(DocService); const docService = useService(DocService);
const guardService = useService(GuardService);
const canManageUsers = useLiveData( const canManageUsers = useGuard('Doc_Users_Manage', docService.doc.id);
guardService.can$('Doc_Users_Manage', docService.doc.id)
);
const canPublish = useLiveData( const canPublish = useGuard('Doc_Publish', docService.doc.id);
guardService.can$('Doc_Publish', docService.doc.id)
);
useEffect(() => { useEffect(() => {
shareInfoService.shareInfo.revalidate(); shareInfoService.shareInfo.revalidate();