feat(core): add public user service (#10695)

This commit is contained in:
EYHN
2025-03-07 08:00:27 +00:00
parent a08e76c3b5
commit 4677049b5c
10 changed files with 375 additions and 9 deletions

View File

@@ -7,6 +7,7 @@ import {
type PageEditor,
} from '@affine/core/blocksuite/editors';
import { useEnableAI } from '@affine/core/components/hooks/affine/use-enable-ai';
import { AuthService, PublicUserService } from '@affine/core/modules/cloud';
import type { DocCustomPropertyInfo } from '@affine/core/modules/db';
import { DocService, DocsService } from '@affine/core/modules/doc';
import type {
@@ -70,6 +71,7 @@ import {
type ReferenceReactRenderer,
} from '../extensions/reference-renderer';
import { patchSideBarService } from '../extensions/side-bar-service';
import { patchUserExtensions } from '../extensions/user';
import { patchUserListExtensions } from '../extensions/user-list';
import { BiDirectionalLinkPanel } from './bi-directional-link-panel';
import { BlocksuiteEditorJournalDocTitle } from './journal-doc-title';
@@ -93,6 +95,8 @@ const usePatchSpecs = (mode: DocMode) => {
workspaceService,
featureFlagService,
memberSearchService,
publicUserService,
authService,
} = useServices({
PeekViewService,
DocService,
@@ -101,7 +105,10 @@ const usePatchSpecs = (mode: DocMode) => {
EditorService,
FeatureFlagService,
MemberSearchService,
PublicUserService,
AuthService,
});
const isCloud = workspaceService.workspace.flavour !== 'local';
const framework = useFramework();
const referenceRenderer: ReferenceReactRenderer = useMemo(() => {
return function customReference(reference) {
@@ -155,11 +162,16 @@ const usePatchSpecs = (mode: DocMode) => {
patchPeekViewService(peekViewService),
patchOpenDocExtension(),
EdgelessClipboardWatcher,
patchUserListExtensions(memberSearchService),
patchDocUrlExtensions(framework),
patchQuickSearchService(framework),
patchSideBarService(framework),
patchDocModeService(docService, docsService, editorService),
isCloud
? [
patchUserListExtensions(memberSearchService),
patchUserExtensions(publicUserService, authService),
]
: [],
mode === 'edgeless' && enableTurboRenderer
? [ViewportTurboRendererExtension]
: [],
@@ -185,10 +197,13 @@ const usePatchSpecs = (mode: DocMode) => {
referenceRenderer,
confirmModal,
peekViewService,
memberSearchService,
docService,
docsService,
editorService,
isCloud,
memberSearchService,
publicUserService,
authService,
enableTurboRenderer,
featureFlagService.flags.enable_pdf_embed_preview.value,
]);

View File

@@ -0,0 +1,30 @@
import type {
AuthService,
PublicUserService,
} from '@affine/core/modules/cloud';
import { UserServiceExtension } from '@blocksuite/affine/blocks';
export function patchUserExtensions(
publicUserService: PublicUserService,
authService: AuthService
) {
return UserServiceExtension({
getCurrentUser() {
const account = authService.session.account$.value;
return account
? {
id: account.id,
avatar: account.avatar,
name: account.label,
}
: null;
},
// eslint-disable-next-line rxjs/finnish
userInfo$(id) {
return publicUserService.publicUser$(id).signal;
},
revalidateUserInfo(id) {
publicUserService.revalidate(id);
},
});
}

View File

@@ -16,6 +16,7 @@ export { EventSourceService } from './services/eventsource';
export { FetchService } from './services/fetch';
export { GraphQLService } from './services/graphql';
export { InvoicesService } from './services/invoices';
export { PublicUserService } from './services/public-user';
export { SelfhostGenerateLicenseService } from './services/selfhost-generate-license';
export { SelfhostLicenseService } from './services/selfhost-license';
export { ServerService } from './services/server';
@@ -63,6 +64,7 @@ import { EventSourceService } from './services/eventsource';
import { FetchService } from './services/fetch';
import { GraphQLService } from './services/graphql';
import { InvoicesService } from './services/invoices';
import { PublicUserService } from './services/public-user';
import { SelfhostGenerateLicenseService } from './services/selfhost-generate-license';
import { SelfhostLicenseService } from './services/selfhost-license';
import { ServerService } from './services/server';
@@ -79,6 +81,7 @@ import { AuthStore } from './stores/auth';
import { CloudDocMetaStore } from './stores/cloud-doc-meta';
import { InviteInfoStore } from './stores/invite-info';
import { InvoicesStore } from './stores/invoices';
import { PublicUserStore } from './stores/public-user';
import { SelfhostGenerateLicenseStore } from './stores/selfhost-generate-license';
import { SelfhostLicenseStore } from './stores/selfhost-license';
import { ServerConfigStore } from './stores/server-config';
@@ -147,7 +150,9 @@ export function configureCloudModule(framework: Framework) {
.store(SelfhostGenerateLicenseStore, [GraphQLService])
.store(InviteInfoStore, [GraphQLService])
.service(AcceptInviteService, [AcceptInviteStore, InviteInfoStore])
.store(AcceptInviteStore, [GraphQLService]);
.store(AcceptInviteStore, [GraphQLService])
.service(PublicUserService, [PublicUserStore])
.store(PublicUserStore, [GraphQLService]);
framework
.scope(WorkspaceScope)

View File

@@ -0,0 +1,75 @@
import {
effect,
fromPromise,
LiveData,
Service,
smartRetry,
} from '@toeverything/infra';
import { catchError, EMPTY, exhaustMap, groupBy, mergeMap } from 'rxjs';
import type { PublicUserStore } from '../stores/public-user';
type RemovedUserInfo = {
id: string;
removed: true;
};
type ExistedUserInfo = {
id: string;
name?: string | null;
avatar?: string | null;
removed?: false;
};
export type PublicUserInfo = RemovedUserInfo | ExistedUserInfo;
export class PublicUserService extends Service {
constructor(private readonly store: PublicUserStore) {
super();
}
publicUsers$ = new LiveData<Map<string, PublicUserInfo | null>>(new Map());
publicUser$(id: string) {
return this.publicUsers$.selector(map => map.get(id) ?? null);
}
error$ = new LiveData<any | null>(null);
revalidate = effect(
groupBy((id: string) => id),
mergeMap(id$ =>
id$.pipe(
exhaustMap(id =>
fromPromise(async signal => {
const user = await this.store.getPublicUserById(id, signal);
if (!user) {
return {
id,
removed: true,
};
}
return {
id,
name: user.name,
avatarUrl: user.avatarUrl,
};
}).pipe(
smartRetry(),
catchError(error => {
console.error(error);
this.error$.next(error);
return EMPTY;
}),
mergeMap(user => {
const publicUsers = new Map(this.publicUsers$.value);
publicUsers.set(user.id, user);
this.publicUsers$.next(publicUsers);
return EMPTY;
})
)
)
)
)
);
}

View File

@@ -0,0 +1,24 @@
import { getPublicUserByIdQuery } from '@affine/graphql';
import { Store } from '@toeverything/infra';
import type { GraphQLService } from '../services/graphql';
export class PublicUserStore extends Store {
constructor(private readonly gqlService: GraphQLService) {
super();
}
async getPublicUserById(id: string, signal?: AbortSignal) {
const result = await this.gqlService.gql({
query: getPublicUserByIdQuery,
variables: {
id,
},
context: {
signal,
},
});
return result.publicUserById;
}
}