mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 18:16:15 +08:00
feat(server): entitlement based model (#14996)
#### PR Dependency Tree * **PR #14996** 👈 This tree was auto-generated by [Charcoal](https://github.com/danerwilliams/charcoal) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Admin mutations to grant/revoke commercial entitlements. * New Doc comment-update permission. * Realtime user/workspace quota-state endpoints and live-update rooms. * **Bug Fixes** * More accurate readable-doc filtering and permission evaluation. * **Refactor** * Workspace feature management moved to entitlement-based model; permission and quota pipelines redesigned. * Admin workspace UI now edits flags only (feature toggles removed). * **Tests** * Extensive new and updated tests for permissions, entitlements, quota, projection, and backfills. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/toeverything/AFFiNE/pull/14996?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -150,7 +150,6 @@ export const KNOWN_CONFIG_GROUPS = [
|
||||
module: 'copilot',
|
||||
fields: [
|
||||
'enabled',
|
||||
'scenarios',
|
||||
'providers.openai',
|
||||
'providers.gemini',
|
||||
'providers.anthropic',
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Input } from '@affine/admin/components/ui/input';
|
||||
import { Label } from '@affine/admin/components/ui/label';
|
||||
import { Separator } from '@affine/admin/components/ui/separator';
|
||||
import { Switch } from '@affine/admin/components/ui/switch';
|
||||
import type { FeatureType } from '@affine/graphql';
|
||||
import {
|
||||
adminUpdateWorkspaceMutation,
|
||||
adminWorkspaceQuery,
|
||||
@@ -17,10 +16,8 @@ import { AccountIcon } from '@blocksuite/icons/rc';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
import { FeatureToggleList } from '../../../components/shared/feature-toggle-list';
|
||||
import { useMutateQueryResource, useMutation } from '../../../use-mutation';
|
||||
import { useQuery } from '../../../use-query';
|
||||
import { useServerConfig } from '../../common';
|
||||
import { RightPanelHeader } from '../../header';
|
||||
import { useRightPanel } from '../../panel/context';
|
||||
import type { WorkspaceDetail } from '../schema';
|
||||
@@ -69,7 +66,6 @@ function WorkspacePanelContent({
|
||||
workspace: WorkspaceDetail;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
const serverConfig = useServerConfig();
|
||||
const { setHasDirtyChanges } = useRightPanel();
|
||||
const revalidate = useMutateQueryResource();
|
||||
const { trigger: updateWorkspace, isMutating } = useMutation({
|
||||
@@ -78,7 +74,6 @@ function WorkspacePanelContent({
|
||||
|
||||
const normalizedWorkspace = useMemo(
|
||||
() => ({
|
||||
features: [...workspace.features],
|
||||
flags: {
|
||||
public: workspace.public,
|
||||
enableAi: workspace.enableAi,
|
||||
@@ -91,14 +86,10 @@ function WorkspacePanelContent({
|
||||
[workspace]
|
||||
);
|
||||
|
||||
const [featureSelection, setFeatureSelection] = useState<FeatureType[]>(
|
||||
normalizedWorkspace.features
|
||||
);
|
||||
const [flags, setFlags] = useState(normalizedWorkspace.flags);
|
||||
const [baseline, setBaseline] = useState(normalizedWorkspace);
|
||||
|
||||
useEffect(() => {
|
||||
setFeatureSelection(normalizedWorkspace.features);
|
||||
setFlags(normalizedWorkspace.flags);
|
||||
setBaseline(normalizedWorkspace);
|
||||
}, [normalizedWorkspace]);
|
||||
@@ -110,20 +101,14 @@ function WorkspacePanelContent({
|
||||
flags.enableSharing !== baseline.flags.enableSharing ||
|
||||
flags.enableUrlPreview !== baseline.flags.enableUrlPreview ||
|
||||
flags.enableDocEmbedding !== baseline.flags.enableDocEmbedding ||
|
||||
flags.name !== baseline.flags.name ||
|
||||
featureSelection.length !== baseline.features.length ||
|
||||
featureSelection.some(f => !baseline.features.includes(f))
|
||||
flags.name !== baseline.flags.name
|
||||
);
|
||||
}, [baseline, featureSelection, flags]);
|
||||
}, [baseline, flags]);
|
||||
|
||||
useEffect(() => {
|
||||
setHasDirtyChanges(hasChanges);
|
||||
}, [hasChanges, setHasDirtyChanges]);
|
||||
|
||||
const handleFeaturesChange = useCallback((features: FeatureType[]) => {
|
||||
setFeatureSelection(features);
|
||||
}, []);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
const update = async () => {
|
||||
try {
|
||||
@@ -136,7 +121,6 @@ function WorkspacePanelContent({
|
||||
enableUrlPreview: flags.enableUrlPreview,
|
||||
enableDocEmbedding: flags.enableDocEmbedding,
|
||||
name: flags.name || null,
|
||||
features: featureSelection,
|
||||
},
|
||||
});
|
||||
await Promise.all([
|
||||
@@ -146,7 +130,6 @@ function WorkspacePanelContent({
|
||||
toast.success('Workspace updated successfully');
|
||||
setBaseline({
|
||||
flags: { ...flags },
|
||||
features: [...featureSelection],
|
||||
});
|
||||
setHasDirtyChanges(false);
|
||||
onClose();
|
||||
@@ -156,7 +139,6 @@ function WorkspacePanelContent({
|
||||
};
|
||||
update().catch(() => {});
|
||||
}, [
|
||||
featureSelection,
|
||||
flags,
|
||||
onClose,
|
||||
revalidate,
|
||||
@@ -239,18 +221,6 @@ function WorkspacePanelContent({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 rounded-xl border border-border/60 bg-card p-3 shadow-sm">
|
||||
<div className="text-sm font-medium">Features</div>
|
||||
<FeatureToggleList
|
||||
features={serverConfig.availableWorkspaceFeatures ?? []}
|
||||
selected={featureSelection}
|
||||
onChange={handleFeaturesChange}
|
||||
className="grid grid-cols-1 gap-2"
|
||||
control="checkbox"
|
||||
controlPosition="left"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<MetricCard
|
||||
label="Snapshot Size"
|
||||
|
||||
Reference in New Issue
Block a user