From 70a0337ea357927af931680176f1cf5bdce7a641 Mon Sep 17 00:00:00 2001 From: fengmk2 Date: Wed, 5 Mar 2025 12:10:28 +0000 Subject: [PATCH] refactor(server): use DocModel to access doc meta (#10593) --- .../__tests__/workspace/controller.spec.ts | 9 +-- .../doc-renderer/__tests__/controller.spec.ts | 2 +- .../src/core/doc-renderer/controller.ts | 5 +- .../src/core/permission/__tests__/doc.spec.ts | 4 +- .../permission/__tests__/workspace.spec.ts | 4 +- .../backend/server/src/core/permission/doc.ts | 7 +- .../server/src/core/permission/workspace.ts | 5 +- .../server/src/core/workspaces/controller.ts | 11 ++- .../src/core/workspaces/resolvers/doc.ts | 15 ++-- packages/backend/server/src/models/doc.ts | 55 ++++++++++++- .../backend/server/src/models/workspace.ts | 77 +------------------ 11 files changed, 85 insertions(+), 109 deletions(-) diff --git a/packages/backend/server/src/__tests__/workspace/controller.spec.ts b/packages/backend/server/src/__tests__/workspace/controller.spec.ts index 582cc47e4d..0db6d6cb5a 100644 --- a/packages/backend/server/src/__tests__/workspace/controller.spec.ts +++ b/packages/backend/server/src/__tests__/workspace/controller.spec.ts @@ -7,7 +7,7 @@ import Sinon from 'sinon'; import { PgWorkspaceDocStorageAdapter } from '../../core/doc'; import { WorkspaceBlobStorage } from '../../core/storage'; -import { Models, WorkspaceRole } from '../../models'; +import { Models, PublicDocMode, WorkspaceRole } from '../../models'; import { createTestingApp, TestingApp, TestUser } from '../utils'; const test = ava as TestFn<{ @@ -218,7 +218,7 @@ test('should be able to get doc', async t => { }); test('should be able to change page publish mode', async t => { - const { app, workspace: doc, db } = t.context; + const { app, workspace: doc, models } = t.context; doc.getDoc.resolves({ spaceId: '', @@ -232,9 +232,8 @@ test('should be able to change page publish mode', async t => { t.is(res.status, HttpStatus.OK); t.is(res.get('publish-mode'), 'page'); - await db.workspaceDoc.update({ - where: { workspaceId_docId: { workspaceId: 'private', docId: 'public' } }, - data: { mode: 1 }, + await models.doc.upsertMeta('private', 'public', { + mode: PublicDocMode.Edgeless, }); res = await app.GET('/api/workspaces/private/docs/public'); diff --git a/packages/backend/server/src/core/doc-renderer/__tests__/controller.spec.ts b/packages/backend/server/src/core/doc-renderer/__tests__/controller.spec.ts index fc277e45b9..adee142c53 100644 --- a/packages/backend/server/src/core/doc-renderer/__tests__/controller.spec.ts +++ b/packages/backend/server/src/core/doc-renderer/__tests__/controller.spec.ts @@ -72,7 +72,7 @@ test('should render page success', async t => { text.insert(5, ' '); await adapter.pushDocUpdates(workspace.id, docId, updates, user.id); - await models.workspace.publishDoc(workspace.id, docId); + await models.doc.publish(workspace.id, docId); await app.GET(`/workspace/${workspace.id}/${docId}`).expect(200); t.pass(); diff --git a/packages/backend/server/src/core/doc-renderer/controller.ts b/packages/backend/server/src/core/doc-renderer/controller.ts index 5875f9f919..f7a1b84ad1 100644 --- a/packages/backend/server/src/core/doc-renderer/controller.ts +++ b/packages/backend/server/src/core/doc-renderer/controller.ts @@ -102,10 +102,7 @@ export class DocRendererController { workspaceId: string, docId: string ): Promise { - let allowUrlPreview = await this.models.workspace.isPublicPage( - workspaceId, - docId - ); + let allowUrlPreview = await this.models.doc.isPublic(workspaceId, docId); if (!allowUrlPreview) { // if page is private, but workspace url preview is on diff --git a/packages/backend/server/src/core/permission/__tests__/doc.spec.ts b/packages/backend/server/src/core/permission/__tests__/doc.spec.ts index ee01d0e6d0..f9e1eb9591 100644 --- a/packages/backend/server/src/core/permission/__tests__/doc.spec.ts +++ b/packages/backend/server/src/core/permission/__tests__/doc.spec.ts @@ -88,7 +88,7 @@ test('should fallback to [External] if workspace is public', async t => { }); test('should return null even if workspace has other public doc', async t => { - await models.workspace.publishDoc(ws.id, 'doc1'); + await models.doc.publish(ws.id, 'doc1'); const role = await ac.getRole({ workspaceId: ws.id, @@ -100,7 +100,7 @@ test('should return null even if workspace has other public doc', async t => { }); test('should return [External] if doc is public', async t => { - await models.workspace.publishDoc(ws.id, 'doc1'); + await models.doc.publish(ws.id, 'doc1'); const role = await ac.getRole({ workspaceId: ws.id, diff --git a/packages/backend/server/src/core/permission/__tests__/workspace.spec.ts b/packages/backend/server/src/core/permission/__tests__/workspace.spec.ts index d23d248972..988ecd0ac4 100644 --- a/packages/backend/server/src/core/permission/__tests__/workspace.spec.ts +++ b/packages/backend/server/src/core/permission/__tests__/workspace.spec.ts @@ -84,7 +84,7 @@ test('should fallback to [External] if workspace is public', async t => { }); test('should return null even workspace has public doc', async t => { - await models.workspace.publishDoc(ws.id, 'doc1'); + await models.doc.publish(ws.id, 'doc1'); const role = await ac.getRole({ workspaceId: ws.id, @@ -95,7 +95,7 @@ test('should return null even workspace has public doc', async t => { }); test('should return mapped external permission for workspace has public docs', async t => { - await models.workspace.publishDoc(ws.id, 'doc1'); + await models.doc.publish(ws.id, 'doc1'); const { permissions } = await ac.role({ workspaceId: ws.id, diff --git a/packages/backend/server/src/core/permission/doc.ts b/packages/backend/server/src/core/permission/doc.ts index 3c7e86d7f0..1d2723c705 100644 --- a/packages/backend/server/src/core/permission/doc.ts +++ b/packages/backend/server/src/core/permission/doc.ts @@ -96,7 +96,12 @@ export class DocAccessController extends AccessController<'doc'> { } private async defaultDocRole(workspaceId: string, docId: string) { - const doc = await this.models.workspace.getDoc(workspaceId, docId); + const doc = await this.models.doc.getMeta(workspaceId, docId, { + select: { + public: true, + defaultRole: true, + }, + }); return { external: doc?.public ? DocRole.External : null, workspace: doc?.defaultRole ?? DocRole.Manager, diff --git a/packages/backend/server/src/core/permission/workspace.ts b/packages/backend/server/src/core/permission/workspace.ts index a58714d85a..b69d1b1c1e 100644 --- a/packages/backend/server/src/core/permission/workspace.ts +++ b/packages/backend/server/src/core/permission/workspace.ts @@ -25,10 +25,7 @@ export class WorkspaceAccessController extends AccessController<'ws'> { // NOTE(@forehalo): special case for public page // Currently, we can not only load binary of a public Doc to render in a shared page, // so we need to ensure anyone has basic 'read' permission to a workspace that has public pages. - if ( - !role && - (await this.models.workspace.hasPublicDoc(resource.workspaceId)) - ) { + if (!role && (await this.models.doc.hasPublic(resource.workspaceId))) { role = WorkspaceRole.External; } diff --git a/packages/backend/server/src/core/workspaces/controller.ts b/packages/backend/server/src/core/workspaces/controller.ts index 63dbc1100f..9af4c2eae3 100644 --- a/packages/backend/server/src/core/workspaces/controller.ts +++ b/packages/backend/server/src/core/workspaces/controller.ts @@ -101,12 +101,17 @@ export class WorkspacesController { if (!docId.isWorkspace) { // fetch the publish page mode for publish page - const doc = await this.models.workspace.getDoc( + const docMeta = await this.models.doc.getMeta( docId.workspace, - docId.guid + docId.guid, + { + select: { + mode: true, + }, + } ); const publishPageMode = - doc?.mode === PublicDocMode.Edgeless ? 'edgeless' : 'page'; + docMeta?.mode === PublicDocMode.Edgeless ? 'edgeless' : 'page'; res.setHeader('publish-mode', publishPageMode); } diff --git a/packages/backend/server/src/core/workspaces/resolvers/doc.ts b/packages/backend/server/src/core/workspaces/resolvers/doc.ts index 533ca5b556..14e744b441 100644 --- a/packages/backend/server/src/core/workspaces/resolvers/doc.ts +++ b/packages/backend/server/src/core/workspaces/resolvers/doc.ts @@ -175,7 +175,7 @@ export class WorkspaceDocResolver { complexity: 2, }) async publicDocs(@Parent() workspace: WorkspaceType) { - return this.models.workspace.getPublicDocs(workspace.id); + return this.models.doc.findPublics(workspace.id); } @ResolveField(() => DocType, { @@ -199,8 +199,7 @@ export class WorkspaceDocResolver { @Parent() workspace: WorkspaceType, @Args('docId') docId: string ): Promise { - const doc = await this.models.workspace.getDoc(workspace.id, docId); - + const doc = await this.models.doc.getMeta(workspace.id, docId); if (doc) { return doc; } @@ -257,11 +256,7 @@ export class WorkspaceDocResolver { await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Publish'); - const doc = await this.models.workspace.publishDoc( - workspaceId, - docId, - mode - ); + const doc = await this.models.doc.publish(workspaceId, docId, mode); this.logger.log( `Publish page ${docId} with mode ${mode} in workspace ${workspaceId}` @@ -297,7 +292,7 @@ export class WorkspaceDocResolver { await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Publish'); - const doc = await this.models.workspace.revokePublicDoc(workspaceId, docId); + const doc = await this.models.doc.unpublish(workspaceId, docId); this.logger.log(`Revoke public doc ${docId} in workspace ${workspaceId}`); @@ -575,7 +570,7 @@ export class DocResolver { } throw error; } - await this.models.workspace.setDocDefaultRole( + await this.models.doc.setDefaultRole( input.workspaceId, input.docId, input.role diff --git a/packages/backend/server/src/models/doc.ts b/packages/backend/server/src/models/doc.ts index 908f7af455..3ff93d5be9 100644 --- a/packages/backend/server/src/models/doc.ts +++ b/packages/backend/server/src/models/doc.ts @@ -3,8 +3,9 @@ import { Transactional } from '@nestjs-cls/transactional'; import type { Update } from '@prisma/client'; import { Prisma } from '@prisma/client'; +import { DocIsNotPublic } from '../base/error'; import { BaseModel } from './base'; -import { Doc, publicUserSelect } from './common'; +import { Doc, DocRole, PublicDocMode, publicUserSelect } from './common'; export interface DocRecord extends Doc {} @@ -342,6 +343,12 @@ export class DocModel extends BaseModel { })) as Prisma.WorkspaceDocGetPayload<{ select: Select }> | null; } + async setDefaultRole(workspaceId: string, docId: string, role: DocRole) { + return await this.upsertMeta(workspaceId, docId, { + defaultRole: role, + }); + } + /** * Find the workspace public doc metas. */ @@ -365,5 +372,51 @@ export class DocModel extends BaseModel { }, }); } + + /** + * Check if the workspace has any public docs. + */ + async hasPublic(workspaceId: string) { + const count = await this.getPublicsCount(workspaceId); + return count > 0; + } + + /** + * Publish a doc as public. + */ + async publish( + workspaceId: string, + docId: string, + mode: PublicDocMode = PublicDocMode.Page + ) { + return await this.upsertMeta(workspaceId, docId, { + public: true, + mode, + }); + } + + @Transactional() + async unpublish(workspaceId: string, docId: string) { + const docMeta = await this.getMeta(workspaceId, docId); + if (!docMeta?.public) { + throw new DocIsNotPublic(); + } + + return await this.upsertMeta(workspaceId, docId, { + public: false, + }); + } + + /** + * Check if the doc is public. + */ + async isPublic(workspaceId: string, docId: string) { + const docMeta = await this.getMeta(workspaceId, docId, { + select: { + public: true, + }, + }); + return docMeta?.public ?? false; + } // #endregion } diff --git a/packages/backend/server/src/models/workspace.ts b/packages/backend/server/src/models/workspace.ts index 24a8ab21dc..565afbba64 100644 --- a/packages/backend/server/src/models/workspace.ts +++ b/packages/backend/server/src/models/workspace.ts @@ -2,9 +2,8 @@ import { Injectable } from '@nestjs/common'; import { Transactional } from '@nestjs-cls/transactional'; import { type Workspace } from '@prisma/client'; -import { DocIsNotPublic, EventBus } from '../base'; +import { EventBus } from '../base'; import { BaseModel } from './base'; -import { DocRole, PublicDocMode } from './common'; declare global { interface Events { @@ -90,78 +89,4 @@ export class WorkspaceModel extends BaseModel { return workspace?.enableUrlPreview ?? false; } // #endregion - - // #region doc - async getDoc(workspaceId: string, docId: string) { - return await this.db.workspaceDoc.findUnique({ - where: { - workspaceId_docId: { workspaceId, docId }, - }, - }); - } - - async isPublicPage(workspaceId: string, docId: string) { - const doc = await this.getDoc(workspaceId, docId); - if (doc?.public) { - return true; - } - - const workspace = await this.get(workspaceId); - return workspace?.public ?? false; - } - - async publishDoc( - workspaceId: string, - docId: string, - mode: PublicDocMode = PublicDocMode.Page - ) { - return await this.db.workspaceDoc.upsert({ - where: { workspaceId_docId: { workspaceId, docId } }, - update: { public: true, mode }, - create: { workspaceId, docId, public: true, mode }, - }); - } - - @Transactional() - async revokePublicDoc(workspaceId: string, docId: string) { - const doc = await this.getDoc(workspaceId, docId); - - if (!doc?.public) { - throw new DocIsNotPublic(); - } - - return await this.db.workspaceDoc.update({ - where: { workspaceId_docId: { workspaceId, docId } }, - data: { public: false }, - }); - } - - async hasPublicDoc(workspaceId: string) { - const count = await this.db.workspaceDoc.count({ - where: { - workspaceId, - public: true, - }, - }); - - return count > 0; - } - - async getPublicDocs(workspaceId: string) { - return await this.db.workspaceDoc.findMany({ - where: { - workspaceId, - public: true, - }, - }); - } - - async setDocDefaultRole(workspaceId: string, docId: string, role: DocRole) { - await this.db.workspaceDoc.upsert({ - where: { workspaceId_docId: { workspaceId, docId } }, - update: { defaultRole: role }, - create: { workspaceId, docId, defaultRole: role }, - }); - } - // #endregion }