refactor(server): improve workspace content retrieval from database (#10964)
This commit is contained in:
@@ -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}`,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user