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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,6 @@ import type {
DatabaseValueCell,
} from '@affine/core/modules/doc-info/types';
import { DocIntegrationPropertiesTable } from '@affine/core/modules/integration';
import { GuardService } from '@affine/core/modules/permissions';
import { ViewService, WorkbenchService } from '@affine/core/modules/workbench';
import type { AffineDNDData } from '@affine/core/types/dnd';
import { useI18n } from '@affine/i18n';
@@ -32,6 +31,7 @@ import clsx from 'clsx';
import type React from 'react';
import { forwardRef, useCallback, useMemo, useState } from 'react';
import { useGuard } from '../guard';
import { DocPropertyIcon } from './icons/doc-property-icon';
import { CreatePropertyMenuItems } from './menu/create-doc-property';
import { EditDocPropertyMenuItems } from './menu/edit-doc-property';
@@ -290,18 +290,13 @@ const DocWorkspacePropertiesTableBody = forwardRef<
const workbenchService = useService(WorkbenchService);
const viewService = useServiceOptional(ViewService);
const docService = useService(DocService);
const guardService = useService(GuardService);
const properties = useLiveData(docsService.propertyList.sortedProperties$);
const [addMoreCollapsed, setAddMoreCollapsed] = useState(true);
const [newPropertyId, setNewPropertyId] = useState<string | null>(null);
const canEditProperty = useLiveData(
guardService.can$('Doc_Update', docService.doc.id)
);
const canEditPropertyInfo = useLiveData(
guardService.can$('Workspace_Properties_Update')
);
const canEditProperty = useGuard('Doc_Update', docService.doc.id);
const canEditPropertyInfo = useGuard('Workspace_Properties_Update');
const handlePropertyAdded = useCallback(
(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,
FavoriteService,
} from '@affine/core/modules/favorite';
import { GuardService } from '@affine/core/modules/permissions';
import { WorkbenchService } from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace';
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 type { CollectionService } from '../../modules/collection';
import { useGuard } from '../guard';
import { IsFavoriteIcon } from '../pure/icons';
import { FavoriteTag } from './components/favorite-tag';
import * as styles from './list.css';
@@ -68,15 +68,13 @@ const PageOperationCellMenuItem = ({
workspaceService,
compatibleFavoriteItemsAdapter: favAdapter,
workbenchService,
guardService,
} = useServices({
WorkspaceService,
CompatibleFavoriteItemsAdapter,
WorkbenchService,
GuardService,
});
const canMoveToTrash = useLiveData(guardService.can$('Doc_Trash', page.id));
const canMoveToTrash = useGuard('Doc_Trash', page.id);
const currentWorkspace = workspaceService.workspace;
const favourite = useLiveData(favAdapter.isFavorite$(page.id, 'doc'));
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 { PageAIOnboarding } from '@affine/core/components/affine/ai-onboarding';
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 { useEnableAI } from '@affine/core/components/hooks/affine/use-enable-ai';
import { DocService } from '@affine/core/modules/doc';
import { EditorService } from '@affine/core/modules/editor';
import { GlobalContextService } from '@affine/core/modules/global-context';
import { PeekViewService } from '@affine/core/modules/peek-view';
import { GuardService } from '@affine/core/modules/permissions';
import { RecentDocsService } from '@affine/core/modules/quicksearch';
import { ViewService } from '@affine/core/modules/workbench';
import { WorkspaceService } from '@affine/core/modules/workspace';
@@ -69,7 +69,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
docService,
workspaceService,
globalContextService,
guardService,
} = useServices({
WorkbenchService,
ViewService,
@@ -77,7 +76,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
DocService,
WorkspaceService,
GlobalContextService,
GuardService,
});
const workbench = workbenchService.workbench;
const editor = editorService.editor;
@@ -262,7 +260,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
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;
@@ -368,7 +366,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
export const Component = () => {
const params = useParams();
const recentPages = useService(RecentDocsService);
const guardService = useService(GuardService);
useEffect(() => {
if (params.pageId) {
@@ -380,9 +377,7 @@ export const Component = () => {
}, [params, recentPages]);
const pageId = params.pageId;
const canAccess = useLiveData(
pageId ? guardService.can$('Doc_Read', pageId) : undefined
);
const canAccess = useGuard('Doc_Read', pageId ?? '');
return pageId ? (
<DetailPageWrapper

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ import {
useDropTarget,
} from '@affine/component';
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 type { DocPermissionActions } from '@affine/core/modules/permissions';
import { WorkbenchLink } from '@affine/core/modules/workbench';
@@ -285,7 +285,7 @@ export const ExplorerTreeNode = ({
? {
index: 0,
view: renameableGuard ? (
<DocPermissionGuard
<Guard
permission={renameableGuard.action}
docId={renameableGuard.docId}
>
@@ -300,7 +300,7 @@ export const ExplorerTreeNode = ({
{t['com.affine.menu.rename']()}
</MenuItem>
)}
</DocPermissionGuard>
</Guard>
) : (
<MenuItem
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 { EditorOutlineViewer } from '@affine/core/blocksuite/outline-viewer';
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 { EditorService } from '@affine/core/modules/editor';
import { GuardService } from '@affine/core/modules/permissions';
import { DebugLogger } from '@affine/debug';
import { GfxControllerIdentifier } from '@blocksuite/affine/block-std/gfx';
import { DisposableGroup } from '@blocksuite/affine/global/disposable';
@@ -85,7 +85,6 @@ function DocPeekPreviewEditor({
const defaultOpenProperty = useLiveData(editor.defaultOpenProperty$);
const workbench = useService(WorkbenchService).workbench;
const peekView = useService(PeekViewService).peekView;
const guardService = useService(GuardService);
const editorElement = useLiveData(editor.editorContainer$);
const isInTrash = useLiveData(doc.record.trash$);
@@ -151,7 +150,7 @@ function DocPeekPreviewEditor({
peekView.close();
}, [doc, peekView, workbench]);
const canEdit = useLiveData(guardService.can$('Doc_Update', doc.id));
const canEdit = useGuard('Doc_Update', doc.id);
const readonly = !canEdit || isInTrash;
@@ -220,8 +219,7 @@ export function DocPeekPreview({
!animating
);
const guardService = useService(GuardService);
const canAccess = useLiveData(guardService.can$('Doc_Read', docId));
const canAccess = useGuard('Doc_Read', docId);
// if sync engine has been synced and the page is null, show 404 page.
if (!doc || !editor || !canAccess) {

View File

@@ -66,17 +66,6 @@ export class GuardService extends Service {
const docId = args[0];
return LiveData.from(
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;
const subscription = combineLatest([
@@ -128,6 +117,29 @@ export class GuardService extends Service {
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(
exhaustMapWithTrailing(() =>
fromPromise(() => this.guardStore.getWorkspacePermissions()).pipe(

View File

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

View File

@@ -1,9 +1,9 @@
import { Skeleton } from '@affine/component';
import { useGuard } from '@affine/core/components/guard';
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';
@@ -33,11 +33,8 @@ export const MemberManagement = ({
docGrantedUsersService.grantedUserCount$
);
const docService = useService(DocService);
const guardService = useService(GuardService);
const canManageUsers = useLiveData(
guardService.can$('Doc_Users_Manage', docService.doc.id)
);
const canManageUsers = useGuard('Doc_Users_Manage', docService.doc.id);
const t = useI18n();

View File

@@ -1,8 +1,8 @@
import { Divider, Skeleton } from '@affine/component';
import { Button } from '@affine/component/ui/button';
import { useGuard } from '@affine/core/components/guard';
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';
@@ -64,15 +64,10 @@ export const AFFiNESharePage = (
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 canManageUsers = useGuard('Doc_Users_Manage', docService.doc.id);
const canPublish = useLiveData(
guardService.can$('Doc_Publish', docService.doc.id)
);
const canPublish = useGuard('Doc_Publish', docService.doc.id);
useEffect(() => {
shareInfoService.shareInfo.revalidate();