refactor(server): improve workspace content retrieval from database (#10964)

This commit is contained in:
fengmk2
2025-03-19 12:16:53 +00:00
parent a9b53839a6
commit ddd6c97b08
3 changed files with 59 additions and 26 deletions

View File

@@ -251,4 +251,22 @@ test('should get workspace content with custom avatar', async t => {
avatarKey, avatarKey,
avatarUrl: `http://localhost:3010/api/workspaces/${workspace.id}/blobs/${avatarKey}`, avatarUrl: `http://localhost:3010/api/workspaces/${workspace.id}/blobs/${avatarKey}`,
}); });
// should save to database
const workspace2 = await t.context.models.workspace.get(workspace.id);
t.truthy(workspace2);
t.is(workspace2!.name, 'Test Workspace');
t.is(workspace2!.avatarKey, avatarKey);
// read from database
await t.context.models.workspace.update(workspace.id, {
name: 'Test Workspace 2',
});
const workspaceContent2 = await docReader.getWorkspaceContent(workspace.id);
t.truthy(workspaceContent2);
t.deepEqual(workspaceContent2, {
id: workspace.id,
name: 'Test Workspace 2',
avatarKey,
avatarUrl: `http://localhost:3010/api/workspaces/${workspace.id}/blobs/${avatarKey}`,
});
}); });

View File

@@ -14,6 +14,7 @@ import {
getOrGenRequestId, getOrGenRequestId,
UserFriendlyError, UserFriendlyError,
} from '../../base'; } from '../../base';
import { Models } from '../../models';
import { WorkspaceBlobStorage } from '../storage'; import { WorkspaceBlobStorage } from '../storage';
import { import {
type PageDocContent, type PageDocContent,
@@ -33,7 +34,13 @@ export interface WorkspaceDocInfo {
} }
export abstract class DocReader { export abstract class DocReader {
constructor(protected readonly cache: Cache) {} protected readonly logger = new Logger(DocReader.name);
constructor(
protected readonly cache: Cache,
protected readonly models: Models,
protected readonly blobStorage: WorkspaceBlobStorage
) {}
parseDocContent(bin: Uint8Array) { parseDocContent(bin: Uint8Array) {
const doc = new YDoc(); const doc = new YDoc();
@@ -78,19 +85,36 @@ export abstract class DocReader {
return content; return content;
} }
// TODO(@fengmk2): should remove this method after frontend support workspace content update /**
* Get workspace content, try to read from database first.
* If not exists, read from `getWorkspaceContentWithoutCache()` and save it back to database.
*/
async getWorkspaceContent( async getWorkspaceContent(
workspaceId: string workspaceId: string
): Promise<WorkspaceDocInfo | null> { ): Promise<WorkspaceDocInfo | null> {
const cacheKey = this.cacheKey(workspaceId, workspaceId); const workspace = await this.models.workspace.get(workspaceId);
const cachedResult = await this.cache.get<WorkspaceDocInfo>(cacheKey); if (!workspace) {
if (cachedResult) { return null;
return cachedResult; }
if (workspace.name) {
return {
id: workspaceId,
name: workspace.name,
avatarKey: workspace.avatarKey ?? undefined,
avatarUrl: workspace.avatarKey
? this.blobStorage.getAvatarUrl(workspaceId, workspace.avatarKey)
: undefined,
};
} }
const content = await this.getWorkspaceContentWithoutCache(workspaceId); const content = await this.getWorkspaceContentWithoutCache(workspaceId);
if (content) { if (content) {
await this.cache.set(cacheKey, content); await this.models.workspace.update(workspaceId, {
name: content.name,
avatarKey: content.avatarKey,
});
} else {
this.logger.warn(`Workspace ${workspaceId} content not found`);
} }
return content; return content;
} }
@@ -128,10 +152,11 @@ export abstract class DocReader {
export class DatabaseDocReader extends DocReader { export class DatabaseDocReader extends DocReader {
constructor( constructor(
protected override readonly cache: Cache, protected override readonly cache: Cache,
protected readonly workspace: PgWorkspaceDocStorageAdapter, protected override readonly models: Models,
protected readonly blobStorage: WorkspaceBlobStorage protected override readonly blobStorage: WorkspaceBlobStorage,
protected readonly workspace: PgWorkspaceDocStorageAdapter
) { ) {
super(cache); super(cache, models, blobStorage);
} }
async getDoc(workspaceId: string, docId: string): Promise<DocRecord | null> { async getDoc(workspaceId: string, docId: string): Promise<DocRecord | null> {
@@ -190,16 +215,17 @@ export class DatabaseDocReader extends DocReader {
@Injectable() @Injectable()
export class RpcDocReader extends DatabaseDocReader { export class RpcDocReader extends DatabaseDocReader {
private readonly logger = new Logger(DocReader.name); protected override readonly logger = new Logger(DocReader.name);
constructor( constructor(
private readonly config: Config, private readonly config: Config,
private readonly crypto: CryptoHelper, private readonly crypto: CryptoHelper,
protected override readonly cache: Cache, protected override readonly cache: Cache,
protected override readonly workspace: PgWorkspaceDocStorageAdapter, protected override readonly models: Models,
protected override readonly blobStorage: WorkspaceBlobStorage protected override readonly blobStorage: WorkspaceBlobStorage,
protected override readonly workspace: PgWorkspaceDocStorageAdapter
) { ) {
super(cache, workspace, blobStorage); super(cache, models, blobStorage, workspace);
} }
private async fetch( private async fetch(

View File

@@ -74,18 +74,7 @@ export class NotificationService {
} }
private async ensureWorkspaceContentExists(workspaceId: string) { private async ensureWorkspaceContentExists(workspaceId: string) {
const workspace = await this.models.workspace.get(workspaceId); await this.docReader.getWorkspaceContent(workspaceId);
if (!workspace || workspace.name) {
return;
}
const content = await this.docReader.getWorkspaceContent(workspaceId);
if (!content?.name) {
return;
}
await this.models.workspace.update(workspaceId, {
name: content.name,
avatarKey: content.avatarKey,
});
} }
async markAsRead(userId: string, notificationId: string) { async markAsRead(userId: string, notificationId: string) {