diff --git a/packages/backend/server/src/__tests__/e2e/config/resolver.spec.ts b/packages/backend/server/src/__tests__/e2e/config/resolver.spec.ts index ef80b8db2..0eca04ab6 100644 --- a/packages/backend/server/src/__tests__/e2e/config/resolver.spec.ts +++ b/packages/backend/server/src/__tests__/e2e/config/resolver.spec.ts @@ -21,3 +21,13 @@ e2e('should comment feature enabled by default', async t => { JSON.stringify(serverConfig, null, 2) ); }); + +e2e('should enable local workspace feature by default', async t => { + const { serverConfig } = await app.gql({ query: serverConfigQuery }); + + t.is( + serverConfig.features.includes(ServerFeature.LocalWorkspace), + true, + JSON.stringify(serverConfig, null, 2) + ); +}); diff --git a/packages/backend/server/src/core/config/resolver.ts b/packages/backend/server/src/core/config/resolver.ts index fa07070a9..c100c90ef 100644 --- a/packages/backend/server/src/core/config/resolver.ts +++ b/packages/backend/server/src/core/config/resolver.ts @@ -85,6 +85,7 @@ export class ServerConfigResolver { baseUrl: this.url.requestBaseUrl, type: env.DEPLOYMENT_TYPE, features: this.server.features, + // TODO(@fengmk2): remove this field after the feature 0.25.0 is released allowGuestDemoWorkspace: this.config.flags.allowGuestDemoWorkspace, }; } diff --git a/packages/backend/server/src/core/config/service.ts b/packages/backend/server/src/core/config/service.ts index 5229b83eb..b502359c5 100644 --- a/packages/backend/server/src/core/config/service.ts +++ b/packages/backend/server/src/core/config/service.ts @@ -110,6 +110,13 @@ export class ServerService implements OnApplicationBootstrap { this.event.emit('config.changed', event); } + @OnEvent('config.changed') + onConfigChanged(event: Events['config.changed']) { + if ('flags' in event.updates) { + this.onFlagsChanged(); + } + } + async revalidateConfig() { const overrides = await this.loadDbOverrides(); this.configFactory.override(overrides); @@ -122,6 +129,7 @@ export class ServerService implements OnApplicationBootstrap { await this.event.emitAsync('config.init', { config: this.configFactory.config, }); + this.onFlagsChanged(); } private async loadDbOverrides() { @@ -134,4 +142,13 @@ export class ServerService implements OnApplicationBootstrap { return overrides; } + + private onFlagsChanged() { + const flags = this.configFactory.config.flags; + if (flags.allowGuestDemoWorkspace) { + this.enableFeature(ServerFeature.LocalWorkspace); + } else { + this.disableFeature(ServerFeature.LocalWorkspace); + } + } } diff --git a/packages/backend/server/src/core/config/types.ts b/packages/backend/server/src/core/config/types.ts index 72630b315..69ad7c448 100644 --- a/packages/backend/server/src/core/config/types.ts +++ b/packages/backend/server/src/core/config/types.ts @@ -10,6 +10,7 @@ export enum ServerFeature { OAuth = 'oauth', Indexer = 'indexer', Comment = 'comment', + LocalWorkspace = 'local_workspace', } registerEnumType(ServerFeature, { @@ -42,6 +43,8 @@ export class ServerConfigType { @Field(() => Boolean, { description: 'Whether allow guest users to create demo workspaces.', + deprecationReason: + 'This field is deprecated, please use `features` instead. Will be removed in 0.25.0', }) allowGuestDemoWorkspace!: boolean; } diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index c05ab923e..e3e70f260 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -1760,7 +1760,7 @@ enum SearchTable { type ServerConfigType { """Whether allow guest users to create demo workspaces.""" - allowGuestDemoWorkspace: Boolean! + allowGuestDemoWorkspace: Boolean! @deprecated(reason: "This field is deprecated, please use `features` instead. Will be removed in 0.25.0") """fetch latest available upgradable release of server""" availableUpgrade: ReleaseVersionType @@ -1802,6 +1802,7 @@ enum ServerFeature { Copilot CopilotEmbedding Indexer + LocalWorkspace OAuth Payment } diff --git a/packages/common/graphql/src/graphql/admin/admin-server-config.gql b/packages/common/graphql/src/graphql/admin/admin-server-config.gql index acb59fb7c..6873c7bfa 100644 --- a/packages/common/graphql/src/graphql/admin/admin-server-config.gql +++ b/packages/common/graphql/src/graphql/admin/admin-server-config.gql @@ -7,7 +7,6 @@ query adminServerConfig { baseUrl name features - allowGuestDemoWorkspace type initialized credentialsRequirement { diff --git a/packages/common/graphql/src/graphql/index.ts b/packages/common/graphql/src/graphql/index.ts index dbf149fb9..a81b0b359 100644 --- a/packages/common/graphql/src/graphql/index.ts +++ b/packages/common/graphql/src/graphql/index.ts @@ -79,7 +79,6 @@ export const adminServerConfigQuery = { baseUrl name features - allowGuestDemoWorkspace type initialized credentialsRequirement { @@ -2090,7 +2089,6 @@ export const serverConfigQuery = { baseUrl name features - allowGuestDemoWorkspace type initialized credentialsRequirement { diff --git a/packages/common/graphql/src/graphql/server-config.gql b/packages/common/graphql/src/graphql/server-config.gql index e95a5b2b1..ac8408567 100644 --- a/packages/common/graphql/src/graphql/server-config.gql +++ b/packages/common/graphql/src/graphql/server-config.gql @@ -7,7 +7,6 @@ query serverConfig { baseUrl name features - allowGuestDemoWorkspace type initialized credentialsRequirement { diff --git a/packages/common/graphql/src/schema.ts b/packages/common/graphql/src/schema.ts index 1d4348de0..e29422cfe 100644 --- a/packages/common/graphql/src/schema.ts +++ b/packages/common/graphql/src/schema.ts @@ -2349,7 +2349,10 @@ export enum SearchTable { export interface ServerConfigType { __typename?: 'ServerConfigType'; - /** Whether allow guest users to create demo workspaces. */ + /** + * Whether allow guest users to create demo workspaces. + * @deprecated This field is deprecated, please use `features` instead. Will be removed in 0.25.0 + */ allowGuestDemoWorkspace: Scalars['Boolean']['output']; /** fetch latest available upgradable release of server */ availableUpgrade: Maybe; @@ -2383,6 +2386,7 @@ export enum ServerFeature { Copilot = 'Copilot', CopilotEmbedding = 'CopilotEmbedding', Indexer = 'Indexer', + LocalWorkspace = 'LocalWorkspace', OAuth = 'OAuth', Payment = 'Payment', } @@ -2964,7 +2968,6 @@ export type AdminServerConfigQuery = { baseUrl: string; name: string; features: Array; - allowGuestDemoWorkspace: boolean; type: ServerDeploymentType; initialized: boolean; availableUserFeatures: Array; @@ -5736,7 +5739,6 @@ export type ServerConfigQuery = { baseUrl: string; name: string; features: Array; - allowGuestDemoWorkspace: boolean; type: ServerDeploymentType; initialized: boolean; credentialsRequirement: { diff --git a/packages/frontend/core/src/components/hooks/affine/__tests__/use-sign-out.spec.ts b/packages/frontend/core/src/components/hooks/affine/__tests__/use-sign-out.spec.ts index f95ec760a..84dd9c766 100644 --- a/packages/frontend/core/src/components/hooks/affine/__tests__/use-sign-out.spec.ts +++ b/packages/frontend/core/src/components/hooks/affine/__tests__/use-sign-out.spec.ts @@ -2,6 +2,7 @@ /** * @vitest-environment happy-dom */ +import { ServerFeature } from '@affine/graphql'; import { renderHook, waitFor } from '@testing-library/react'; import { beforeEach, describe, expect, test, vi } from 'vitest'; @@ -24,8 +25,10 @@ vi.mock('@toeverything/infra', () => { server: { config$: { value: { - get allowGuestDemoWorkspace() { - return allowGuestDemo; + get features() { + return allowGuestDemo !== false + ? [ServerFeature.LocalWorkspace] + : []; }, }, }, diff --git a/packages/frontend/core/src/components/hooks/affine/use-sign-out.ts b/packages/frontend/core/src/components/hooks/affine/use-sign-out.ts index bb4e0f498..5f629763a 100644 --- a/packages/frontend/core/src/components/hooks/affine/use-sign-out.ts +++ b/packages/frontend/core/src/components/hooks/affine/use-sign-out.ts @@ -5,6 +5,7 @@ import { } from '@affine/component'; import { AuthService, DefaultServerService } from '@affine/core/modules/cloud'; import { UserFriendlyError } from '@affine/error'; +import { ServerFeature } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; import { useService, useServices } from '@toeverything/infra'; import { useCallback } from 'react'; @@ -32,12 +33,15 @@ export const useSignOut = ({ const signOut = useCallback(async () => { onConfirm?.()?.catch(console.error); + const enableLocalWorkspace = + BUILD_CONFIG.isNative || + defaultServerService.server.config$.value.features.includes( + ServerFeature.LocalWorkspace + ); + try { await authService.signOut(); - if ( - defaultServerService.server.config$.value.allowGuestDemoWorkspace !== - false - ) { + if (enableLocalWorkspace) { jumpToIndex(); } else { jumpToSignIn(); diff --git a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-workspace/index.tsx b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-workspace/index.tsx index 8b0f2ff09..1192a98bc 100644 --- a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-workspace/index.tsx +++ b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/add-workspace/index.tsx @@ -1,5 +1,6 @@ import { MenuItem } from '@affine/component/ui/menu'; import { DefaultServerService } from '@affine/core/modules/cloud'; +import { ServerFeature } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; import { ImportIcon, PlusIcon } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; @@ -15,10 +16,13 @@ export const AddWorkspace = ({ }) => { const t = useI18n(); const defaultServerService = useService(DefaultServerService); - const allowGuestDemo = useLiveData( - defaultServerService.server.config$.selector(c => c.allowGuestDemoWorkspace) + const enableLocalWorkspace = useLiveData( + defaultServerService.server.config$.selector( + c => + c.features.includes(ServerFeature.LocalWorkspace) || + BUILD_CONFIG.isNative + ) ); - const guestDemoEnabled = allowGuestDemo !== false; return ( <> @@ -45,7 +49,7 @@ export const AddWorkspace = ({ className={styles.ItemContainer} >
- {guestDemoEnabled + {enableLocalWorkspace ? t['com.affine.workspaceList.addWorkspace.create']() : t['com.affine.workspaceList.addWorkspace.create-cloud']()}
diff --git a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/index.tsx b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/index.tsx index 990c8f566..4e85964bb 100644 --- a/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/index.tsx +++ b/packages/frontend/core/src/components/workspace-selector/user-with-workspace-list/index.tsx @@ -3,6 +3,7 @@ import { MenuItem } from '@affine/component/ui/menu'; import { AuthService, DefaultServerService } from '@affine/core/modules/cloud'; import { GlobalDialogService } from '@affine/core/modules/dialogs'; import { type WorkspaceMetadata } from '@affine/core/modules/workspace'; +import { ServerFeature } from '@affine/graphql'; import { useI18n } from '@affine/i18n'; import { track } from '@affine/track'; import { Logo1Icon } from '@blocksuite/icons/rc'; @@ -74,11 +75,12 @@ export const UserWithWorkspaceList = ({ }, [globalDialogService]); const onNewWorkspace = useCallback(() => { - if ( - !isAuthenticated && - defaultServerService.server.config$.value.allowGuestDemoWorkspace === - false - ) { + const enableLocalWorkspace = + BUILD_CONFIG.isNative || + defaultServerService.server.config$.value.features.includes( + ServerFeature.LocalWorkspace + ); + if (!isAuthenticated && !enableLocalWorkspace) { return openSignInModal(); } track.$.navigationPanel.workspaceList.createWorkspace(); diff --git a/packages/frontend/core/src/desktop/pages/index/index.tsx b/packages/frontend/core/src/desktop/pages/index/index.tsx index 5649fb55d..f138ddf5d 100644 --- a/packages/frontend/core/src/desktop/pages/index/index.tsx +++ b/packages/frontend/core/src/desktop/pages/index/index.tsx @@ -5,6 +5,7 @@ import { buildShowcaseWorkspace, createFirstAppData, } from '@affine/core/utils/first-app-data'; +import { ServerFeature } from '@affine/graphql'; import { useLiveData, useService, @@ -52,10 +53,12 @@ export const Component = ({ const loggedIn = useLiveData( authService.session.status$.map(s => s === 'authenticated') ); - const allowGuestDemo = + const enableLocalWorkspace = useLiveData( defaultServerService.server.config$.selector( - c => c.allowGuestDemoWorkspace + c => + c.features.includes(ServerFeature.LocalWorkspace) || + BUILD_CONFIG.isNative ) ) ?? true; @@ -92,7 +95,7 @@ export const Component = ({ return; } - if (!allowGuestDemo && !loggedIn) { + if (!enableLocalWorkspace && !loggedIn) { localStorage.removeItem('last_workspace_id'); jumpToSignIn(); return; @@ -125,7 +128,7 @@ export const Component = ({ openPage(openWorkspace.id, defaultIndexRoute, RouteLogic.REPLACE); } }, [ - allowGuestDemo, + enableLocalWorkspace, createCloudWorkspace, list, openPage, diff --git a/packages/frontend/core/src/modules/cloud/constant.ts b/packages/frontend/core/src/modules/cloud/constant.ts index 988bb8451..36bc3cd5b 100644 --- a/packages/frontend/core/src/modules/cloud/constant.ts +++ b/packages/frontend/core/src/modules/cloud/constant.ts @@ -17,7 +17,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = // since we never build desktop app in selfhosted mode, so it's fine config: { serverName: 'Affine Selfhost', - features: [], + features: [ServerFeature.LocalWorkspace], oauthProviders: [], type: ServerDeploymentType.Selfhosted, credentialsRequirement: { @@ -26,7 +26,6 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = maxLength: 32, }, }, - allowGuestDemoWorkspace: true, }, }, ] @@ -45,6 +44,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = ServerFeature.CopilotEmbedding, ServerFeature.OAuth, ServerFeature.Payment, + ServerFeature.LocalWorkspace, ], oauthProviders: [ OAuthProviderType.Google, @@ -57,7 +57,6 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = maxLength: 32, }, }, - allowGuestDemoWorkspace: true, }, }, ] @@ -78,6 +77,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = ServerFeature.CopilotEmbedding, ServerFeature.OAuth, ServerFeature.Payment, + ServerFeature.LocalWorkspace, ], oauthProviders: [ OAuthProviderType.Google, @@ -90,7 +90,6 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = maxLength: 32, }, }, - allowGuestDemoWorkspace: true, }, }, ] @@ -111,6 +110,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = ServerFeature.CopilotEmbedding, ServerFeature.OAuth, ServerFeature.Payment, + ServerFeature.LocalWorkspace, ], oauthProviders: [ OAuthProviderType.Google, @@ -123,7 +123,6 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = maxLength: 32, }, }, - allowGuestDemoWorkspace: true, }, }, ] @@ -140,6 +139,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = ServerFeature.CopilotEmbedding, ServerFeature.OAuth, ServerFeature.Payment, + ServerFeature.LocalWorkspace, ], oauthProviders: [ OAuthProviderType.Google, @@ -152,7 +152,6 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = maxLength: 32, }, }, - allowGuestDemoWorkspace: true, }, }, ] @@ -171,6 +170,7 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = ServerFeature.CopilotEmbedding, ServerFeature.OAuth, ServerFeature.Payment, + ServerFeature.LocalWorkspace, ], oauthProviders: [ OAuthProviderType.Google, @@ -183,7 +183,6 @@ export const BUILD_IN_SERVERS: (ServerMetadata & { config: ServerConfig })[] = maxLength: 32, }, }, - allowGuestDemoWorkspace: true, }, }, ] diff --git a/packages/frontend/core/src/modules/cloud/entities/server.ts b/packages/frontend/core/src/modules/cloud/entities/server.ts index d287d7fcf..aa6d425a3 100644 --- a/packages/frontend/core/src/modules/cloud/entities/server.ts +++ b/packages/frontend/core/src/modules/cloud/entities/server.ts @@ -82,7 +82,6 @@ export class Server extends Entity<{ credentialsRequirement: config.credentialsRequirement, features: config.features, oauthProviders: config.oauthProviders, - allowGuestDemoWorkspace: config.allowGuestDemoWorkspace, serverName: config.name, type: config.type, version: config.version, diff --git a/packages/frontend/core/src/modules/cloud/services/servers.ts b/packages/frontend/core/src/modules/cloud/services/servers.ts index 8b872bd3e..757390d99 100644 --- a/packages/frontend/core/src/modules/cloud/services/servers.ts +++ b/packages/frontend/core/src/modules/cloud/services/servers.ts @@ -82,7 +82,6 @@ export class ServersService extends Service { credentialsRequirement: config.credentialsRequirement, features: config.features, oauthProviders: config.oauthProviders, - allowGuestDemoWorkspace: config.allowGuestDemoWorkspace, serverName: config.name, type: config.type, initialized: config.initialized, diff --git a/packages/frontend/core/src/modules/cloud/types.ts b/packages/frontend/core/src/modules/cloud/types.ts index 5f2d34080..4d86deab8 100644 --- a/packages/frontend/core/src/modules/cloud/types.ts +++ b/packages/frontend/core/src/modules/cloud/types.ts @@ -14,7 +14,6 @@ export interface ServerMetadata { export interface ServerConfig { serverName: string; features: ServerFeature[]; - allowGuestDemoWorkspace: boolean; oauthProviders: OAuthProviderType[]; type: ServerDeploymentType; initialized?: boolean;