mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 01:09:54 +08:00
refactor(server): use DocModel to access doc meta (#10593)
This commit is contained in:
@@ -7,7 +7,7 @@ import Sinon from 'sinon';
|
|||||||
|
|
||||||
import { PgWorkspaceDocStorageAdapter } from '../../core/doc';
|
import { PgWorkspaceDocStorageAdapter } from '../../core/doc';
|
||||||
import { WorkspaceBlobStorage } from '../../core/storage';
|
import { WorkspaceBlobStorage } from '../../core/storage';
|
||||||
import { Models, WorkspaceRole } from '../../models';
|
import { Models, PublicDocMode, WorkspaceRole } from '../../models';
|
||||||
import { createTestingApp, TestingApp, TestUser } from '../utils';
|
import { createTestingApp, TestingApp, TestUser } from '../utils';
|
||||||
|
|
||||||
const test = ava as TestFn<{
|
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 => {
|
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({
|
doc.getDoc.resolves({
|
||||||
spaceId: '',
|
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.status, HttpStatus.OK);
|
||||||
t.is(res.get('publish-mode'), 'page');
|
t.is(res.get('publish-mode'), 'page');
|
||||||
|
|
||||||
await db.workspaceDoc.update({
|
await models.doc.upsertMeta('private', 'public', {
|
||||||
where: { workspaceId_docId: { workspaceId: 'private', docId: 'public' } },
|
mode: PublicDocMode.Edgeless,
|
||||||
data: { mode: 1 },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
res = await app.GET('/api/workspaces/private/docs/public');
|
res = await app.GET('/api/workspaces/private/docs/public');
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ test('should render page success', async t => {
|
|||||||
text.insert(5, ' ');
|
text.insert(5, ' ');
|
||||||
|
|
||||||
await adapter.pushDocUpdates(workspace.id, docId, updates, user.id);
|
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);
|
await app.GET(`/workspace/${workspace.id}/${docId}`).expect(200);
|
||||||
t.pass();
|
t.pass();
|
||||||
|
|||||||
@@ -102,10 +102,7 @@ export class DocRendererController {
|
|||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
docId: string
|
docId: string
|
||||||
): Promise<RenderOptions | null> {
|
): Promise<RenderOptions | null> {
|
||||||
let allowUrlPreview = await this.models.workspace.isPublicPage(
|
let allowUrlPreview = await this.models.doc.isPublic(workspaceId, docId);
|
||||||
workspaceId,
|
|
||||||
docId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!allowUrlPreview) {
|
if (!allowUrlPreview) {
|
||||||
// if page is private, but workspace url preview is on
|
// if page is private, but workspace url preview is on
|
||||||
|
|||||||
@@ -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 => {
|
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({
|
const role = await ac.getRole({
|
||||||
workspaceId: ws.id,
|
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 => {
|
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({
|
const role = await ac.getRole({
|
||||||
workspaceId: ws.id,
|
workspaceId: ws.id,
|
||||||
|
|||||||
@@ -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 => {
|
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({
|
const role = await ac.getRole({
|
||||||
workspaceId: ws.id,
|
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 => {
|
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({
|
const { permissions } = await ac.role({
|
||||||
workspaceId: ws.id,
|
workspaceId: ws.id,
|
||||||
|
|||||||
@@ -96,7 +96,12 @@ export class DocAccessController extends AccessController<'doc'> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async defaultDocRole(workspaceId: string, docId: string) {
|
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 {
|
return {
|
||||||
external: doc?.public ? DocRole.External : null,
|
external: doc?.public ? DocRole.External : null,
|
||||||
workspace: doc?.defaultRole ?? DocRole.Manager,
|
workspace: doc?.defaultRole ?? DocRole.Manager,
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ export class WorkspaceAccessController extends AccessController<'ws'> {
|
|||||||
// NOTE(@forehalo): special case for public page
|
// NOTE(@forehalo): special case for public page
|
||||||
// Currently, we can not only load binary of a public Doc to render in a shared 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.
|
// so we need to ensure anyone has basic 'read' permission to a workspace that has public pages.
|
||||||
if (
|
if (!role && (await this.models.doc.hasPublic(resource.workspaceId))) {
|
||||||
!role &&
|
|
||||||
(await this.models.workspace.hasPublicDoc(resource.workspaceId))
|
|
||||||
) {
|
|
||||||
role = WorkspaceRole.External;
|
role = WorkspaceRole.External;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -101,12 +101,17 @@ export class WorkspacesController {
|
|||||||
|
|
||||||
if (!docId.isWorkspace) {
|
if (!docId.isWorkspace) {
|
||||||
// fetch the publish page mode for publish page
|
// 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.workspace,
|
||||||
docId.guid
|
docId.guid,
|
||||||
|
{
|
||||||
|
select: {
|
||||||
|
mode: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
);
|
);
|
||||||
const publishPageMode =
|
const publishPageMode =
|
||||||
doc?.mode === PublicDocMode.Edgeless ? 'edgeless' : 'page';
|
docMeta?.mode === PublicDocMode.Edgeless ? 'edgeless' : 'page';
|
||||||
|
|
||||||
res.setHeader('publish-mode', publishPageMode);
|
res.setHeader('publish-mode', publishPageMode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export class WorkspaceDocResolver {
|
|||||||
complexity: 2,
|
complexity: 2,
|
||||||
})
|
})
|
||||||
async publicDocs(@Parent() workspace: WorkspaceType) {
|
async publicDocs(@Parent() workspace: WorkspaceType) {
|
||||||
return this.models.workspace.getPublicDocs(workspace.id);
|
return this.models.doc.findPublics(workspace.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ResolveField(() => DocType, {
|
@ResolveField(() => DocType, {
|
||||||
@@ -199,8 +199,7 @@ export class WorkspaceDocResolver {
|
|||||||
@Parent() workspace: WorkspaceType,
|
@Parent() workspace: WorkspaceType,
|
||||||
@Args('docId') docId: string
|
@Args('docId') docId: string
|
||||||
): Promise<DocType> {
|
): Promise<DocType> {
|
||||||
const doc = await this.models.workspace.getDoc(workspace.id, docId);
|
const doc = await this.models.doc.getMeta(workspace.id, docId);
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
@@ -257,11 +256,7 @@ export class WorkspaceDocResolver {
|
|||||||
|
|
||||||
await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Publish');
|
await this.ac.user(user.id).doc(workspaceId, docId).assert('Doc.Publish');
|
||||||
|
|
||||||
const doc = await this.models.workspace.publishDoc(
|
const doc = await this.models.doc.publish(workspaceId, docId, mode);
|
||||||
workspaceId,
|
|
||||||
docId,
|
|
||||||
mode
|
|
||||||
);
|
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
`Publish page ${docId} with mode ${mode} in workspace ${workspaceId}`
|
`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');
|
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}`);
|
this.logger.log(`Revoke public doc ${docId} in workspace ${workspaceId}`);
|
||||||
|
|
||||||
@@ -575,7 +570,7 @@ export class DocResolver {
|
|||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
await this.models.workspace.setDocDefaultRole(
|
await this.models.doc.setDefaultRole(
|
||||||
input.workspaceId,
|
input.workspaceId,
|
||||||
input.docId,
|
input.docId,
|
||||||
input.role
|
input.role
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import { Transactional } from '@nestjs-cls/transactional';
|
|||||||
import type { Update } from '@prisma/client';
|
import type { Update } from '@prisma/client';
|
||||||
import { Prisma } from '@prisma/client';
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
|
import { DocIsNotPublic } from '../base/error';
|
||||||
import { BaseModel } from './base';
|
import { BaseModel } from './base';
|
||||||
import { Doc, publicUserSelect } from './common';
|
import { Doc, DocRole, PublicDocMode, publicUserSelect } from './common';
|
||||||
|
|
||||||
export interface DocRecord extends Doc {}
|
export interface DocRecord extends Doc {}
|
||||||
|
|
||||||
@@ -342,6 +343,12 @@ export class DocModel extends BaseModel {
|
|||||||
})) as Prisma.WorkspaceDocGetPayload<{ select: Select }> | null;
|
})) 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.
|
* 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
|
// #endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,8 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { Transactional } from '@nestjs-cls/transactional';
|
import { Transactional } from '@nestjs-cls/transactional';
|
||||||
import { type Workspace } from '@prisma/client';
|
import { type Workspace } from '@prisma/client';
|
||||||
|
|
||||||
import { DocIsNotPublic, EventBus } from '../base';
|
import { EventBus } from '../base';
|
||||||
import { BaseModel } from './base';
|
import { BaseModel } from './base';
|
||||||
import { DocRole, PublicDocMode } from './common';
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Events {
|
interface Events {
|
||||||
@@ -90,78 +89,4 @@ export class WorkspaceModel extends BaseModel {
|
|||||||
return workspace?.enableUrlPreview ?? false;
|
return workspace?.enableUrlPreview ?? false;
|
||||||
}
|
}
|
||||||
// #endregion
|
// #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
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user