refactor(server): move bin content parser to doc reader (#10302)

This commit is contained in:
fengmk2
2025-02-20 07:19:48 +00:00
parent 9f4311f654
commit b4097aef8e
18 changed files with 477 additions and 234 deletions

View File

@@ -1,85 +0,0 @@
import { randomUUID } from 'node:crypto';
import { User, Workspace } from '@prisma/client';
import ava, { TestFn } from 'ava';
import { Doc as YDoc } from 'yjs';
import { createTestingApp, type TestingApp } from '../../../__tests__/utils';
import { AppModule } from '../../../app.module';
import { Config } from '../../../base';
import { ConfigModule } from '../../../base/config';
import { Models } from '../../../models';
import { PgWorkspaceDocStorageAdapter } from '../../doc';
import { DocContentService } from '..';
const test = ava as TestFn<{
models: Models;
app: TestingApp;
docContentService: DocContentService;
config: Config;
adapter: PgWorkspaceDocStorageAdapter;
}>;
test.before(async t => {
const app = await createTestingApp({
imports: [
ConfigModule.forRoot({
flavor: {
doc: false,
},
docService: {
endpoint: '',
},
}),
AppModule,
],
});
t.context.models = app.get(Models);
t.context.docContentService = app.get(DocContentService);
t.context.config = app.get(Config);
t.context.adapter = app.get(PgWorkspaceDocStorageAdapter);
t.context.app = app;
});
let user: User;
let workspace: Workspace;
test.beforeEach(async t => {
t.context.config.docService.endpoint = t.context.app.url();
await t.context.app.initTestingDB();
user = await t.context.models.user.create({
email: 'test@affine.pro',
});
workspace = await t.context.models.workspace.create(user.id);
});
test.after.always(async t => {
await t.context.app.close();
});
test('should get doc content from doc service rpc', async t => {
const docId = randomUUID();
const { docContentService } = t.context;
const doc = new YDoc();
const text = doc.getText('content');
const updates: Buffer[] = [];
doc.on('update', update => {
updates.push(Buffer.from(update));
});
text.insert(0, 'hello');
text.insert(5, 'world');
text.insert(5, ' ');
await t.context.adapter.pushDocUpdates(workspace.id, docId, updates, user.id);
const docContent = await docContentService.getPageContent(
workspace.id,
docId
);
// TODO(@fengmk2): should create a test ydoc with blocks
t.is(docContent, null);
});

View File

@@ -8,8 +8,8 @@ import isMobile from 'is-mobile';
import { Config, metrics, URLHelper } from '../../base';
import { htmlSanitize } from '../../native';
import { Public } from '../auth';
import { DocReader } from '../doc';
import { PermissionService } from '../permission';
import { DocContentService } from './service';
interface RenderOptions {
title: string;
@@ -50,7 +50,7 @@ export class DocRendererController {
private readonly mobileAssets: HtmlAssets = defaultAssets;
constructor(
private readonly doc: DocContentService,
private readonly doc: DocReader,
private readonly permission: PermissionService,
private readonly config: Config,
private readonly url: URLHelper
@@ -114,7 +114,7 @@ export class DocRendererController {
}
if (allowUrlPreview) {
return this.doc.getPageContent(workspaceId, docId);
return this.doc.getDocContent(workspaceId, docId);
}
return null;

View File

@@ -1,27 +0,0 @@
import { FactoryProvider } from '@nestjs/common';
import { Config, OnEvent } from '../../base';
import { DocContentService } from './service';
class DocEventsListener {
constructor(private readonly doc: DocContentService) {}
@OnEvent('doc.snapshot.updated')
async handleDocSnapshotUpdated({
workspaceId,
docId,
}: Events['doc.snapshot.updated']) {
await this.doc.markDocContentCacheStale(workspaceId, docId);
}
}
export const DocEventsListenerProvider: FactoryProvider = {
provide: DocEventsListener,
useFactory: (config: Config, doc: DocContentService) => {
if (config.flavor.renderer) {
return new DocEventsListener(doc);
}
return;
},
inject: [Config, DocContentService],
};

View File

@@ -3,15 +3,9 @@ import { Module } from '@nestjs/common';
import { DocStorageModule } from '../doc';
import { PermissionModule } from '../permission';
import { DocRendererController } from './controller';
import { DocEventsListenerProvider } from './event';
import { DocContentService } from './service';
@Module({
imports: [DocStorageModule, PermissionModule],
providers: [DocContentService, DocEventsListenerProvider],
controllers: [DocRendererController],
exports: [DocContentService],
})
export class DocRendererModule {}
export { DocContentService };

View File

@@ -1,88 +0,0 @@
import { Injectable } from '@nestjs/common';
import { applyUpdate, Doc } from 'yjs';
import { Cache } from '../../base';
import { DocReader } from '../doc';
import {
type PageDocContent,
parsePageDoc,
parseWorkspaceDoc,
type WorkspaceDocContent,
} from '../utils/blocksuite';
@Injectable()
export class DocContentService {
constructor(
private readonly cache: Cache,
private readonly docReader: DocReader
) {}
async getPageContent(
workspaceId: string,
guid: string
): Promise<PageDocContent | null> {
const cacheKey = `workspace:${workspaceId}:doc:${guid}:content`;
const cachedResult = await this.cache.get<PageDocContent>(cacheKey);
if (cachedResult) {
return cachedResult;
}
const docRecord = await this.docReader.getDoc(workspaceId, guid);
if (!docRecord) {
return null;
}
const doc = new Doc();
applyUpdate(doc, docRecord.bin);
const content = parsePageDoc(doc);
if (content) {
await this.cache.set(cacheKey, content, {
ttl:
7 *
24 *
60 *
60 *
1000 /* TODO(@forehalo): we need time constants helper */,
});
}
return content;
}
async getWorkspaceContent(
workspaceId: string
): Promise<WorkspaceDocContent | null> {
const cacheKey = `workspace:${workspaceId}:content`;
const cachedResult = await this.cache.get<WorkspaceDocContent>(cacheKey);
if (cachedResult) {
return cachedResult;
}
const docRecord = await this.docReader.getDoc(workspaceId, workspaceId);
if (!docRecord) {
return null;
}
const doc = new Doc();
applyUpdate(doc, docRecord.bin);
const content = parseWorkspaceDoc(doc);
if (content) {
await this.cache.set(cacheKey, content);
}
return content;
}
async markDocContentCacheStale(workspaceId: string, docId: string) {
const key =
workspaceId === docId
? `workspace:${workspaceId}:content`
: `workspace:${workspaceId}:doc:${docId}:content`;
await this.cache.delete(key);
}
}