feat(infra): framework
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import type { GetWorkspacePublicPagesQuery } from '@affine/graphql';
|
||||
import type { GlobalCache, WorkspaceService } from '@toeverything/infra';
|
||||
import {
|
||||
backoffRetry,
|
||||
catchErrorInto,
|
||||
effect,
|
||||
Entity,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
onComplete,
|
||||
onStart,
|
||||
} from '@toeverything/infra';
|
||||
import { EMPTY, mergeMap, switchMap } from 'rxjs';
|
||||
|
||||
import { isBackendError, isNetworkError } from '../../cloud';
|
||||
import type { ShareDocsStore } from '../stores/share-docs';
|
||||
|
||||
type ShareDocListType =
|
||||
GetWorkspacePublicPagesQuery['workspace']['publicPages'];
|
||||
|
||||
export const logger = new DebugLogger('affine:share-doc-list');
|
||||
|
||||
export class ShareDocsList extends Entity {
|
||||
list$ = LiveData.from(this.cache.watch<ShareDocListType>('share-docs'), []);
|
||||
isLoading$ = new LiveData<boolean>(false);
|
||||
error$ = new LiveData<any>(null);
|
||||
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly store: ShareDocsStore,
|
||||
private readonly cache: GlobalCache
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
revalidate = effect(
|
||||
switchMap(() =>
|
||||
fromPromise(signal =>
|
||||
this.store.getWorkspacesShareDocs(
|
||||
this.workspaceService.workspace.id,
|
||||
signal
|
||||
)
|
||||
).pipe(
|
||||
backoffRetry({
|
||||
when: isNetworkError,
|
||||
count: Infinity,
|
||||
}),
|
||||
backoffRetry({
|
||||
when: isBackendError,
|
||||
}),
|
||||
mergeMap(list => {
|
||||
this.cache.set('share-docs', list);
|
||||
return EMPTY;
|
||||
}),
|
||||
catchErrorInto(this.error$, err =>
|
||||
logger.error('revalidate share docs error', err)
|
||||
),
|
||||
onStart(() => {
|
||||
this.isLoading$.next(true);
|
||||
}),
|
||||
onComplete(() => {
|
||||
this.isLoading$.next(false);
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import type {
|
||||
GetWorkspacePublicPageByIdQuery,
|
||||
PublicPageMode,
|
||||
} from '@affine/graphql';
|
||||
import type { DocService, WorkspaceService } from '@toeverything/infra';
|
||||
import {
|
||||
backoffRetry,
|
||||
catchErrorInto,
|
||||
effect,
|
||||
Entity,
|
||||
fromPromise,
|
||||
LiveData,
|
||||
mapInto,
|
||||
onComplete,
|
||||
onStart,
|
||||
} from '@toeverything/infra';
|
||||
import { switchMap } from 'rxjs';
|
||||
|
||||
import { isBackendError, isNetworkError } from '../../cloud';
|
||||
import type { ShareStore } from '../stores/share';
|
||||
|
||||
type ShareInfoType = GetWorkspacePublicPageByIdQuery['workspace']['publicPage'];
|
||||
|
||||
export class Share extends Entity {
|
||||
info$ = new LiveData<ShareInfoType | undefined | null>(null);
|
||||
isShared$ = this.info$.map(info =>
|
||||
// null means not loaded yet, undefined means not shared
|
||||
info !== null ? info !== undefined : null
|
||||
);
|
||||
sharedMode$ = this.info$.map(info => (info !== null ? info?.mode : null));
|
||||
|
||||
error$ = new LiveData<any>(null);
|
||||
isRevalidating$ = new LiveData<boolean>(false);
|
||||
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly docService: DocService,
|
||||
private readonly store: ShareStore
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
revalidate = effect(
|
||||
switchMap(() => {
|
||||
return fromPromise(signal =>
|
||||
this.store.getShareInfoByDocId(
|
||||
this.workspaceService.workspace.id,
|
||||
this.docService.doc.id,
|
||||
signal
|
||||
)
|
||||
).pipe(
|
||||
backoffRetry({
|
||||
when: isNetworkError,
|
||||
count: Infinity,
|
||||
}),
|
||||
backoffRetry({
|
||||
when: isBackendError,
|
||||
}),
|
||||
mapInto(this.info$),
|
||||
catchErrorInto(this.error$),
|
||||
onStart(() => this.isRevalidating$.next(true)),
|
||||
onComplete(() => this.isRevalidating$.next(false))
|
||||
);
|
||||
})
|
||||
);
|
||||
|
||||
waitForRevalidation(signal?: AbortSignal) {
|
||||
this.revalidate();
|
||||
return this.isRevalidating$.waitFor(v => v === false, signal);
|
||||
}
|
||||
|
||||
async enableShare(mode: PublicPageMode) {
|
||||
await this.store.enableSharePage(
|
||||
this.workspaceService.workspace.id,
|
||||
this.docService.doc.id,
|
||||
mode
|
||||
);
|
||||
await this.waitForRevalidation();
|
||||
}
|
||||
|
||||
async changeShare(mode: PublicPageMode) {
|
||||
await this.enableShare(mode);
|
||||
}
|
||||
|
||||
async disableShare() {
|
||||
await this.store.disableSharePage(
|
||||
this.workspaceService.workspace.id,
|
||||
this.docService.doc.id
|
||||
);
|
||||
await this.waitForRevalidation();
|
||||
}
|
||||
}
|
||||
35
packages/frontend/core/src/modules/share-doc/index.ts
Normal file
35
packages/frontend/core/src/modules/share-doc/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
export { ShareService } from './services/share';
|
||||
export { ShareDocsService } from './services/share-docs';
|
||||
|
||||
import {
|
||||
DocScope,
|
||||
DocService,
|
||||
type Framework,
|
||||
WorkspaceLocalCache,
|
||||
WorkspaceScope,
|
||||
WorkspaceService,
|
||||
} from '@toeverything/infra';
|
||||
|
||||
import { GraphQLService } from '../cloud';
|
||||
import { ShareDocsList } from './entities/share-docs-list';
|
||||
import { Share } from './entities/share-info';
|
||||
import { ShareService } from './services/share';
|
||||
import { ShareDocsService } from './services/share-docs';
|
||||
import { ShareStore } from './stores/share';
|
||||
import { ShareDocsStore } from './stores/share-docs';
|
||||
|
||||
export function configureShareDocsModule(framework: Framework) {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.service(ShareDocsService)
|
||||
.store(ShareDocsStore, [GraphQLService])
|
||||
.entity(ShareDocsList, [
|
||||
WorkspaceService,
|
||||
ShareDocsStore,
|
||||
WorkspaceLocalCache,
|
||||
])
|
||||
.scope(DocScope)
|
||||
.service(ShareService)
|
||||
.entity(Share, [WorkspaceService, DocService, ShareStore])
|
||||
.store(ShareStore, [GraphQLService]);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import { ShareDocsList } from '../entities/share-docs-list';
|
||||
|
||||
export class ShareDocsService extends Service {
|
||||
shareDocs = this.framework.createEntity(ShareDocsList);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Service } from '@toeverything/infra';
|
||||
|
||||
import { Share } from '../entities/share-info';
|
||||
|
||||
export class ShareService extends Service {
|
||||
share = this.framework.createEntity(Share);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { GraphQLService } from '@affine/core/modules/cloud';
|
||||
import { getWorkspacePublicPagesQuery } from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
export class ShareDocsStore extends Store {
|
||||
constructor(private readonly graphqlService: GraphQLService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async getWorkspacesShareDocs(workspaceId: string, signal?: AbortSignal) {
|
||||
const data = await this.graphqlService.gql({
|
||||
query: getWorkspacePublicPagesQuery,
|
||||
variables: {
|
||||
workspaceId: workspaceId,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
return data.workspace.publicPages;
|
||||
}
|
||||
}
|
||||
69
packages/frontend/core/src/modules/share-doc/stores/share.ts
Normal file
69
packages/frontend/core/src/modules/share-doc/stores/share.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { PublicPageMode } from '@affine/graphql';
|
||||
import {
|
||||
getWorkspacePublicPageByIdQuery,
|
||||
publishPageMutation,
|
||||
revokePublicPageMutation,
|
||||
} from '@affine/graphql';
|
||||
import { Store } from '@toeverything/infra';
|
||||
|
||||
import type { GraphQLService } from '../../cloud';
|
||||
|
||||
export class ShareStore extends Store {
|
||||
constructor(private readonly gqlService: GraphQLService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async getShareInfoByDocId(
|
||||
workspaceId: string,
|
||||
docId: string,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
const data = await this.gqlService.gql({
|
||||
query: getWorkspacePublicPageByIdQuery,
|
||||
variables: {
|
||||
pageId: docId,
|
||||
workspaceId,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
return data.workspace.publicPage ?? undefined;
|
||||
}
|
||||
|
||||
async enableSharePage(
|
||||
workspaceId: string,
|
||||
pageId: string,
|
||||
docMode?: PublicPageMode,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
await this.gqlService.gql({
|
||||
query: publishPageMutation,
|
||||
variables: {
|
||||
pageId,
|
||||
workspaceId,
|
||||
mode: docMode,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async disableSharePage(
|
||||
workspaceId: string,
|
||||
pageId: string,
|
||||
signal?: AbortSignal
|
||||
) {
|
||||
await this.gqlService.gql({
|
||||
query: revokePublicPageMutation,
|
||||
variables: {
|
||||
pageId,
|
||||
workspaceId,
|
||||
},
|
||||
context: {
|
||||
signal,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user