mirror of https://github.com/fantasticit/think.git
refactor: use guard
parent
dffbc3e464
commit
52329d92f0
|
@ -1,2 +1,7 @@
|
|||
/// <reference types="node" />
|
||||
export declare const DEFAULT_WIKI_AVATAR = "https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default0-96.png";
|
||||
export declare const WIKI_AVATARS: string[];
|
||||
export declare const EMPTY_DOCUMNENT: {
|
||||
content: string;
|
||||
state: Buffer;
|
||||
};
|
||||
|
|
|
@ -1,19 +1,37 @@
|
|||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.WIKI_AVATARS = exports.DEFAULT_WIKI_AVATAR = void 0;
|
||||
exports.DEFAULT_WIKI_AVATAR = "https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default0-96.png";
|
||||
exports.EMPTY_DOCUMNENT = exports.WIKI_AVATARS = exports.DEFAULT_WIKI_AVATAR = void 0;
|
||||
exports.DEFAULT_WIKI_AVATAR = 'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default0-96.png';
|
||||
exports.WIKI_AVATARS = [
|
||||
exports.DEFAULT_WIKI_AVATAR,
|
||||
"https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default2-96.png",
|
||||
"https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default7-96.png",
|
||||
"https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default8-96.png",
|
||||
"https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default14-96.png",
|
||||
"https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default21-96.png",
|
||||
"https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default23-96.png",
|
||||
"https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default1-96%20(1).png",
|
||||
"https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default4-96.png",
|
||||
"https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default12-96.png",
|
||||
"https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default17-96.png",
|
||||
"https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png",
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default2-96.png',
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default7-96.png',
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default8-96.png',
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default14-96.png',
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default21-96.png',
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default23-96.png',
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default1-96%20(1).png',
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default4-96.png',
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default12-96.png',
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default17-96.png',
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
|
||||
];
|
||||
exports.EMPTY_DOCUMNENT = {
|
||||
content: JSON.stringify({
|
||||
default: {
|
||||
type: 'doc',
|
||||
content: [{ type: 'title', content: [{ type: 'text', text: '未命名文档' }] }],
|
||||
},
|
||||
}),
|
||||
state: Buffer.from(new Uint8Array([
|
||||
1, 14, 204, 224, 154, 225, 13, 0, 7, 1, 7, 100, 101, 102, 97, 117, 108, 116, 3, 5, 116, 105, 116, 108, 101, 1, 0,
|
||||
204, 224, 154, 225, 13, 0, 1, 0, 1, 135, 204, 224, 154, 225, 13, 0, 3, 9, 112, 97, 114, 97, 103, 114, 97, 112,
|
||||
104, 40, 0, 204, 224, 154, 225, 13, 3, 6, 105, 110, 100, 101, 110, 116, 1, 125, 0, 40, 0, 204, 224, 154, 225, 13,
|
||||
3, 9, 116, 101, 120, 116, 65, 108, 105, 103, 110, 1, 119, 4, 108, 101, 102, 116, 0, 4, 71, 204, 224, 154, 225, 13,
|
||||
1, 6, 1, 0, 204, 224, 154, 225, 13, 10, 3, 132, 204, 224, 154, 225, 13, 13, 3, 230, 156, 170, 129, 204, 224, 154,
|
||||
225, 13, 14, 6, 132, 204, 224, 154, 225, 13, 20, 6, 229, 145, 189, 229, 144, 141, 129, 204, 224, 154, 225, 13, 22,
|
||||
5, 132, 204, 224, 154, 225, 13, 27, 6, 230, 150, 135, 230, 161, 163, 1, 204, 224, 154, 225, 13, 5, 1, 2, 6, 4, 11,
|
||||
3, 15, 6, 23, 5,
|
||||
])),
|
||||
};
|
||||
//# sourceMappingURL=index.js.map
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAa,QAAA,mBAAmB,GAC9B,sEAAsE,CAAC;AAE5D,QAAA,YAAY,GAAG;IAC1B,2BAAmB;IACnB,sEAAsE;IACtE,sEAAsE;IACtE,sEAAsE;IACtE,uEAAuE;IACvE,uEAAuE;IACvE,uEAAuE;IACvE,4EAA4E;IAC5E,sEAAsE;IACtE,uEAAuE;IACvE,uEAAuE;IACvE,uEAAuE;CACxE,CAAC"}
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAa,QAAA,mBAAmB,GAAG,sEAAsE,CAAC;AAE7F,QAAA,YAAY,GAAG;IAC1B,2BAAmB;IACnB,sEAAsE;IACtE,sEAAsE;IACtE,sEAAsE;IACtE,uEAAuE;IACvE,uEAAuE;IACvE,uEAAuE;IACvE,4EAA4E;IAC5E,sEAAsE;IACtE,uEAAuE;IACvE,uEAAuE;IACvE,uEAAuE;CACxE,CAAC;AAEW,QAAA,eAAe,GAAG;IAC7B,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC;QACtB,OAAO,EAAE;YACP,IAAI,EAAE,KAAK;YACX,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;SACzE;KACF,CAAC;IACF,KAAK,EAAE,MAAM,CAAC,IAAI,CAChB,IAAI,UAAU,CAAC;QACb,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;QAChH,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG;QAC7G,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAChH,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACjH,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG;QAChH,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE;QACjH,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;QACjH,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC;KAChB,CAAC,CACH;CACF,CAAC"}
|
|
@ -14,3 +14,24 @@ export const WIKI_AVATARS = [
|
|||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default17-96.png',
|
||||
'https://wipi.oss-cn-shanghai.aliyuncs.com/2022-02-01/default18-96.png',
|
||||
];
|
||||
|
||||
export const EMPTY_DOCUMNENT = {
|
||||
content: JSON.stringify({
|
||||
default: {
|
||||
type: 'doc',
|
||||
content: [{ type: 'title', content: [{ type: 'text', text: '未命名文档' }] }],
|
||||
},
|
||||
}),
|
||||
state: Buffer.from(
|
||||
new Uint8Array([
|
||||
1, 14, 204, 224, 154, 225, 13, 0, 7, 1, 7, 100, 101, 102, 97, 117, 108, 116, 3, 5, 116, 105, 116, 108, 101, 1, 0,
|
||||
204, 224, 154, 225, 13, 0, 1, 0, 1, 135, 204, 224, 154, 225, 13, 0, 3, 9, 112, 97, 114, 97, 103, 114, 97, 112,
|
||||
104, 40, 0, 204, 224, 154, 225, 13, 3, 6, 105, 110, 100, 101, 110, 116, 1, 125, 0, 40, 0, 204, 224, 154, 225, 13,
|
||||
3, 9, 116, 101, 120, 116, 65, 108, 105, 103, 110, 1, 119, 4, 108, 101, 102, 116, 0, 4, 71, 204, 224, 154, 225, 13,
|
||||
1, 6, 1, 0, 204, 224, 154, 225, 13, 10, 3, 132, 204, 224, 154, 225, 13, 13, 3, 230, 156, 170, 129, 204, 224, 154,
|
||||
225, 13, 14, 6, 132, 204, 224, 154, 225, 13, 20, 6, 229, 145, 189, 229, 144, 141, 129, 204, 224, 154, 225, 13, 22,
|
||||
5, 132, 204, 224, 154, 225, 13, 27, 6, 230, 150, 135, 230, 161, 163, 1, 204, 224, 154, 225, 13, 5, 1, 2, 6, 4, 11,
|
||||
3, 15, 6, 23, 5,
|
||||
])
|
||||
),
|
||||
};
|
||||
|
|
|
@ -14,6 +14,9 @@ import {
|
|||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { JwtGuard } from '@guard/jwt.guard';
|
||||
import { DocumentAuthorityGuard, CheckDocumentAuthority } from '@guard/document-auth.guard';
|
||||
import { DocumentStatusGuard, CheckDocumentStatus } from '@guard/document-status.guard';
|
||||
import { DocumentStatus } from '@think/domains';
|
||||
import { DocumentService } from '@services/document.service';
|
||||
import { DocAuthDto } from '@dtos/doc-auth.dto';
|
||||
import { CreateDocumentDto } from '@dtos/create-document.dto';
|
||||
|
@ -21,6 +24,8 @@ import { UpdateDocumentDto } from '@dtos/update-document.dto';
|
|||
import { ShareDocumentDto } from '@dtos/share-document.dto';
|
||||
|
||||
@Controller('document')
|
||||
@UseGuards(DocumentAuthorityGuard)
|
||||
@UseGuards(DocumentStatusGuard)
|
||||
export class DocumentController {
|
||||
constructor(private readonly documentService: DocumentService) {}
|
||||
|
||||
|
@ -32,54 +37,104 @@ export class DocumentController {
|
|||
return await this.documentService.createDocument(req.user, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档详情
|
||||
* @param req
|
||||
* @param documentId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('detail/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckDocumentAuthority('readable')
|
||||
@UseGuards(JwtGuard)
|
||||
async getDocumentDetail(@Request() req, @Param('id') documentId) {
|
||||
return await this.documentService.getDocumentDetail(req.user, documentId, req.headers['user-agent']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新文档
|
||||
* @param req
|
||||
* @param documentId
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('update/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckDocumentAuthority('editable')
|
||||
@UseGuards(JwtGuard)
|
||||
async updateDocument(@Request() req, @Param('id') documentId, @Body() dto: UpdateDocumentDto) {
|
||||
return await this.documentService.updateDocument(req.user, documentId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档版本记录
|
||||
* @param req
|
||||
* @param documentId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('version/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckDocumentAuthority('readable')
|
||||
@UseGuards(JwtGuard)
|
||||
async getDocumentVersion(@Request() req, @Param('id') documentId) {
|
||||
return await this.documentService.getDocumentVersion(req.user, documentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档下的子文档
|
||||
* @param req
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('children')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckDocumentAuthority('readable')
|
||||
@UseGuards(JwtGuard)
|
||||
async getChildrenDocuments(@Request() req, @Body() data) {
|
||||
return await this.documentService.getChildrenDocuments(req.user, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文档
|
||||
* @param req
|
||||
* @param documentId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Delete('delete/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckDocumentAuthority('createUser')
|
||||
@UseGuards(JwtGuard)
|
||||
async deleteDocument(@Request() req, @Param('id') documentId) {
|
||||
return await this.documentService.deleteDocument(req.user, documentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分享(或关闭分享)文档
|
||||
* @param req
|
||||
* @param documentId
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('share/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckDocumentAuthority('editable')
|
||||
@UseGuards(JwtGuard)
|
||||
async shareDocument(@Request() req, @Param('id') documentId, @Body() dto: ShareDocumentDto) {
|
||||
return await this.documentService.shareDocument(req.user, documentId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索文档
|
||||
* @param req
|
||||
* @param keyword
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('search')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
@ -88,52 +143,74 @@ export class DocumentController {
|
|||
return await this.documentService.search(req.user, keyword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档成员
|
||||
* @param req
|
||||
* @param documentId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('user/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckDocumentAuthority('readable')
|
||||
@UseGuards(JwtGuard)
|
||||
async getDocUsers(@Request() req, @Param('id') documentId) {
|
||||
return await this.documentService.getDocUsers(req.user, documentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加文档成员
|
||||
* 只有文档创建着才可操作
|
||||
* @param req
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('user/:id/add')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckDocumentAuthority('createUser')
|
||||
@UseGuards(JwtGuard)
|
||||
async addDocUser(@Request() req, @Body() dto: DocAuthDto) {
|
||||
return await this.documentService.addDocUser(req.user, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改文档成员(一般是权限操作)
|
||||
* 只有文档创建着才可操作
|
||||
* @param req
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('user/:id/update')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckDocumentAuthority('createUser')
|
||||
@UseGuards(JwtGuard)
|
||||
async updateDocUser(@Request() req, @Body() dto: DocAuthDto) {
|
||||
return await this.documentService.updateDocUser(req.user, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文档成员
|
||||
* 只有文档创建着才可操作
|
||||
* @param req
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('user/:id/delete')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckDocumentAuthority('createUser')
|
||||
@UseGuards(JwtGuard)
|
||||
async deleteDocUser(@Request() req, @Body() dto: DocAuthDto) {
|
||||
return await this.documentService.deleteDocUser(req.user, dto);
|
||||
}
|
||||
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('public/detail/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getShareDocumentDetail(@Request() req, @Param('id') documentId, @Body() dto: ShareDocumentDto) {
|
||||
return await this.documentService.getPublicDocumentDetail(documentId, dto, req.headers['user-agent']);
|
||||
}
|
||||
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('public/children')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getShareChildrenDocuments(@Body() data) {
|
||||
return await this.documentService.getShareChildrenDocuments(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户最近访问的文档
|
||||
* @param req
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('recent')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
@ -141,4 +218,32 @@ export class DocumentController {
|
|||
async getWorkspaceDocuments(@Request() req) {
|
||||
return await this.documentService.getRecentDocuments(req.user);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开文档详情
|
||||
* @param req
|
||||
* @param documentId
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('public/detail/:id')
|
||||
@CheckDocumentStatus(DocumentStatus.public)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getShareDocumentDetail(@Request() req, @Param('id') documentId, @Body() dto: ShareDocumentDto) {
|
||||
return await this.documentService.getPublicDocumentDetail(documentId, dto, req.headers['user-agent']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开文档的子文档
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('public/children')
|
||||
@CheckDocumentStatus(DocumentStatus.public)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getShareChildrenDocuments(@Body() data) {
|
||||
return await this.documentService.getShareChildrenDocuments(data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,9 @@ import {
|
|||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { JwtGuard } from '@guard/jwt.guard';
|
||||
import { IPagination } from '@think/domains';
|
||||
import { IPagination, WikiUserRole, WikiStatus } from '@think/domains';
|
||||
import { WikiUserRoleGuard, CheckWikiUserRole } from '@guard/wiki-user.guard';
|
||||
import { WikiStatusGuard, CheckWikiStatus } from '@guard/wiki-status.guard';
|
||||
import { WikiService } from '@services/wiki.service';
|
||||
import { WikiUserDto } from '@dtos/wiki-user.dto';
|
||||
import { CreateWikiDto } from '@dtos/create-wiki.dto';
|
||||
|
@ -23,9 +25,17 @@ import { UpdateWikiDto } from '@dtos/update-wiki.dto';
|
|||
import { ShareWikiDto } from '@dtos/share-wiki.dto';
|
||||
|
||||
@Controller('wiki')
|
||||
@UseGuards(WikiUserRoleGuard)
|
||||
@UseGuards(WikiStatusGuard)
|
||||
export class WikiController {
|
||||
constructor(private readonly wikiService: WikiService) {}
|
||||
|
||||
/**
|
||||
* 新建知识库
|
||||
* @param req
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('create')
|
||||
@HttpCode(HttpStatus.CREATED)
|
||||
|
@ -34,6 +44,12 @@ export class WikiController {
|
|||
return await this.wikiService.createWiki(req.user, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有知识库(创建的、参与的)
|
||||
* @param req
|
||||
* @param pagination
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('list/all')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
@ -42,6 +58,12 @@ export class WikiController {
|
|||
return await this.wikiService.getAllWikis(req.user, pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户拥有的知识库(一般是创建的,尚未实现知识库转移)
|
||||
* @param req
|
||||
* @param pagination
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('list/own')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
@ -50,6 +72,12 @@ export class WikiController {
|
|||
return await this.wikiService.getOwnWikis(req.user, pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户参与的知识库
|
||||
* @param req
|
||||
* @param pagination
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('list/join')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
@ -58,123 +86,244 @@ export class WikiController {
|
|||
return await this.wikiService.getJoinWikis(req.user, pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库详情
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('detail/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole()
|
||||
@UseGuards(JwtGuard)
|
||||
async getWikiDetail(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.getWikiDetail(req.user, wikiId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库首页文档(首页文档为自动创建)
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('homedoc/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole()
|
||||
@UseGuards(JwtGuard)
|
||||
async getWikiHomeDocument(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.getWikiHomeDocument(req.user, wikiId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改知识库
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Patch('update/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole(WikiUserRole.admin)
|
||||
@UseGuards(JwtGuard)
|
||||
async updateWiki(@Request() req, @Param('id') wikiId, @Body() dto: UpdateWikiDto) {
|
||||
return await this.wikiService.updateWiki(req.user, wikiId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除知识库
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Delete('delete/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole(WikiUserRole.admin)
|
||||
@UseGuards(JwtGuard)
|
||||
async deleteWiki(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.deleteWiki(req.user, wikiId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看知识库成员
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('user/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole(WikiUserRole.admin)
|
||||
@UseGuards(JwtGuard)
|
||||
async getWikiUsers(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.getWikiUsers({ userId: req.user.id, wikiId });
|
||||
async getWikiUsers(@Param('id') wikiId) {
|
||||
return await this.wikiService.getWikiUsers(wikiId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加知识库成员
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('user/:id/add')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole(WikiUserRole.admin)
|
||||
@UseGuards(JwtGuard)
|
||||
async addWikiUser(@Request() req, @Param('id') wikiId, @Body() dto: WikiUserDto) {
|
||||
return await this.wikiService.addWikiUser(req.user, wikiId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新知识库成员(一般为角色操作)
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('user/:id/update')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole(WikiUserRole.admin)
|
||||
@UseGuards(JwtGuard)
|
||||
async updateWikiUser(@Request() req, @Param('id') wikiId, @Body() dto: WikiUserDto) {
|
||||
return await this.wikiService.updateWikiUser(req.user, wikiId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除知识库成员
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('user/:id/delete')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole(WikiUserRole.admin)
|
||||
@UseGuards(JwtGuard)
|
||||
async deleteWikiUser(@Request() req, @Param('id') wikiId, @Body() dto: WikiUserDto) {
|
||||
return await this.wikiService.deleteWikiUser(req.user, wikiId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分享(或关闭分享)知识库
|
||||
* 只有管理员可操作
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('share/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole(WikiUserRole.admin)
|
||||
@UseGuards(JwtGuard)
|
||||
async toggleWorkspaceStatus(@Request() req, @Param('id') wikiId, @Body() dto: ShareWikiDto) {
|
||||
return await this.wikiService.shareWiki(req.user, wikiId, dto);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库目录
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('tocs/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole()
|
||||
@UseGuards(JwtGuard)
|
||||
async getWikiTocs(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.getWikiTocs(req.user, wikiId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新知识库目录(排序、父子关系)
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @param relations
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('tocs/:id/update')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole()
|
||||
@UseGuards(JwtGuard)
|
||||
async orderWikiTocs(@Request() req, @Param('id') wikiId, @Body() relations) {
|
||||
return await this.wikiService.orderWikiTocs(req.user, wikiId, relations);
|
||||
async orderWikiTocs(@Body() relations) {
|
||||
return await this.wikiService.orderWikiTocs(relations);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库所有文档
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('docs/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@CheckWikiUserRole()
|
||||
@UseGuards(JwtGuard)
|
||||
async getWikiDocs(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.getWikiDocs(req.user, wikiId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开知识库首页文档
|
||||
* @param req
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('public/homedoc/:id')
|
||||
@CheckWikiStatus(WikiStatus.public)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getWikiPublicHomeDocument(@Request() req, @Param('id') wikiId) {
|
||||
return await this.wikiService.getWikiPublicHomeDocument(wikiId, req.headers['user-agent']);
|
||||
}
|
||||
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('public/tocs/:id')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getPublicWikiTocs(@Param('id') wikiId) {
|
||||
return await this.wikiService.getPublicWikiTocs(wikiId);
|
||||
return await this.wikiService.getPublicWikiHomeDocument(wikiId, req.headers['user-agent']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开知识库详情
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('public/detail/:id')
|
||||
@CheckWikiStatus(WikiStatus.public)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getPublicWorkspaceDetail(@Param('id') wikiId) {
|
||||
return await this.wikiService.getPublicWikiDetail(wikiId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开知识库目录
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Post('public/tocs/:id')
|
||||
@CheckWikiStatus(WikiStatus.public)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
async getPublicWikiTocs(@Param('id') wikiId) {
|
||||
return await this.wikiService.getPublicWikiTocs(wikiId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有公开知识库
|
||||
* @param pagination
|
||||
* @returns
|
||||
*/
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
@Get('public/wikis')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import { CanActivate, ExecutionContext, Injectable, SetMetadata, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { IUser } from '@think/domains';
|
||||
import { DocumentService } from '@services/document.service';
|
||||
|
||||
const KEY = 'DocumentAuthority';
|
||||
export const CheckDocumentAuthority = (auth: 'readable' | 'editable' | 'createUser' | null) => SetMetadata(KEY, auth);
|
||||
|
||||
@Injectable()
|
||||
export class DocumentAuthorityGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly reflector: Reflector,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly documentService: DocumentService
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const needAuth = this.reflector.get<string>(KEY, context.getHandler());
|
||||
|
||||
if (!needAuth) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
let token = request.headers.authorization;
|
||||
|
||||
if (/Bearer/.test(token)) {
|
||||
token = token.split(' ').pop();
|
||||
}
|
||||
|
||||
const user = this.jwtService.decode(token) as IUser;
|
||||
const { params, query, body } = request;
|
||||
const documentId = params.id || params.documentId || query.id || query.documentId || body.documentId;
|
||||
let document = null;
|
||||
|
||||
if (documentId) {
|
||||
document = await this.documentService.findById(documentId);
|
||||
} else {
|
||||
if (body.wikiId) {
|
||||
document = await this.documentService.findWikiHomeDocument(body.wikiId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!document) {
|
||||
throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
if (needAuth === 'createUser') {
|
||||
if (document.createUserId !== user.id) {
|
||||
throw new HttpException('您不是该文档的创建者,无法删除', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
} else if (needAuth) {
|
||||
const authority = await this.documentService.getDocumentAuthority(documentId, user.id);
|
||||
if (!authority || !authority[needAuth]) {
|
||||
throw new HttpException('您无权查看此文档', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import { CanActivate, ExecutionContext, Injectable, SetMetadata, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { DocumentStatus } from '@think/domains';
|
||||
import { DocumentService } from '@services/document.service';
|
||||
|
||||
const KEY = 'DocumentStatus';
|
||||
export const CheckDocumentStatus = (status: DocumentStatus) => SetMetadata(KEY, status);
|
||||
|
||||
@Injectable()
|
||||
export class DocumentStatusGuard implements CanActivate {
|
||||
constructor(private readonly reflector: Reflector, private readonly documentService: DocumentService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const targetStatus = this.reflector.get<DocumentStatus>(KEY, context.getHandler());
|
||||
|
||||
if (!targetStatus) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const { params, query, body } = request;
|
||||
const documentId = params.id || params.documentId || query.id || query.documentId || body.documentId;
|
||||
|
||||
let document = null;
|
||||
|
||||
if (documentId) {
|
||||
document = await this.documentService.findById(documentId);
|
||||
} else {
|
||||
if (body.wikiId) {
|
||||
document = await this.documentService.findWikiHomeDocument(body.wikiId);
|
||||
}
|
||||
}
|
||||
|
||||
if (!document) {
|
||||
throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
if (document.status !== targetStatus) {
|
||||
throw new HttpException(
|
||||
targetStatus === DocumentStatus.private
|
||||
? '私有文档,无法查看内容'
|
||||
: '公共文档,无法查看内容,请提 issue 到 GitHub 仓库反馈',
|
||||
HttpStatus.FORBIDDEN
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
import { CanActivate, ExecutionContext, Injectable, SetMetadata, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { WikiStatus } from '@think/domains';
|
||||
import { WikiService } from '@services/wiki.service';
|
||||
|
||||
const KEY = 'WikiStatus';
|
||||
export const CheckWikiStatus = (status: WikiStatus) => SetMetadata(KEY, status);
|
||||
|
||||
@Injectable()
|
||||
export class WikiStatusGuard implements CanActivate {
|
||||
constructor(private readonly reflector: Reflector, private readonly wikiService: WikiService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const targetStatus = this.reflector.get<WikiStatus>(KEY, context.getHandler());
|
||||
|
||||
if (!targetStatus) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const { params, query, body } = request;
|
||||
const wikiId = params.id || params.wikiId || query.id || query.wikiId || body.wikiId;
|
||||
|
||||
const wiki = await this.wikiService.findById(wikiId);
|
||||
|
||||
if (!wiki) {
|
||||
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
if (wiki.status !== targetStatus) {
|
||||
throw new HttpException(
|
||||
targetStatus === WikiStatus.private
|
||||
? '私有知识库,无法查看内容'
|
||||
: '公共知识库,无法查看内容,请提 issue 到 GitHub 仓库反馈',
|
||||
HttpStatus.FORBIDDEN
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
import { CanActivate, ExecutionContext, Injectable, SetMetadata, HttpException, HttpStatus } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { IUser, WikiUserRole } from '@think/domains';
|
||||
import { WikiService } from '@services/wiki.service';
|
||||
|
||||
const KEY = 'WIKI_USER_ROLE';
|
||||
|
||||
/**
|
||||
* 知识库成员角色检测
|
||||
* @param role 不传意味只要是成员即可
|
||||
* @returns
|
||||
*/
|
||||
export const CheckWikiUserRole = (role: WikiUserRole | null = null) => SetMetadata(KEY, role);
|
||||
|
||||
@Injectable()
|
||||
export class WikiUserRoleGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly reflector: Reflector,
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly wikiService: WikiService
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const targetUserRole = this.reflector.get<WikiUserRole | null>(KEY, context.getHandler());
|
||||
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
let token = request.headers.authorization;
|
||||
|
||||
if (/Bearer/.test(token)) {
|
||||
token = token.split(' ').pop();
|
||||
}
|
||||
|
||||
const user = this.jwtService.decode(token) as IUser;
|
||||
|
||||
const { params, query, body } = request;
|
||||
const wikiId = params.id || params.wikiId || query.id || query.wikiId || body.wikiId;
|
||||
|
||||
const wiki = await this.wikiService.findById(wikiId);
|
||||
|
||||
if (!wiki) {
|
||||
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
const wikiUser = await this.wikiService.findWikiUser(wikiId, user.id);
|
||||
|
||||
if (!wikiUser && targetUserRole && wikiUser.userRole !== targetUserRole) {
|
||||
throw new HttpException('您无权查看该知识库', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ import { UserModule } from '@modules/user.module';
|
|||
import { DocumentModule } from '@modules/document.module';
|
||||
import { MessageModule } from '@modules/message.module';
|
||||
import { CollectorModule } from '@modules/collector.module';
|
||||
import { ViewModule } from '@modules/view.module';
|
||||
import { WikiEntity } from '@entities/wiki.entity';
|
||||
import { WikiUserEntity } from '@entities/wiki-user.entity';
|
||||
import { WikiController } from '@controllers/wiki.controller';
|
||||
|
@ -15,6 +16,7 @@ import { WikiService } from '@services/wiki.service';
|
|||
forwardRef(() => UserModule),
|
||||
forwardRef(() => DocumentModule),
|
||||
forwardRef(() => MessageModule),
|
||||
forwardRef(() => ViewModule),
|
||||
forwardRef(() => CollectorModule),
|
||||
],
|
||||
providers: [WikiService],
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Injectable, HttpException, HttpStatus, Inject, forwardRef } from '@nestjs/common';
|
||||
import { DocumentStatus } from '@think/domains';
|
||||
import { getConfig } from '@think/config';
|
||||
import * as Y from 'yjs';
|
||||
import { TiptapTransformer } from '@hocuspocus/transformer';
|
||||
|
@ -77,57 +76,31 @@ export class CollaborationService {
|
|||
async onAuthenticate({ connection, token, requestParameters }: onAuthenticatePayload) {
|
||||
const targetId = requestParameters.get('targetId');
|
||||
const docType = requestParameters.get('docType');
|
||||
const user = await this.userService.decodeToken(token);
|
||||
|
||||
if (!user || !user.id) {
|
||||
throw new HttpException('您无权查看', HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
switch (docType) {
|
||||
case 'document': {
|
||||
const documentId = targetId;
|
||||
if (token === 'anoy') {
|
||||
const document = await this.documentService.findById(documentId);
|
||||
if (document.status === DocumentStatus.public) {
|
||||
connection.readOnly = true;
|
||||
return {
|
||||
user: { name: 'anoymouse' },
|
||||
};
|
||||
}
|
||||
} else {
|
||||
const user = await this.userService.decodeToken(token);
|
||||
|
||||
if (!user || !user.id) {
|
||||
throw new HttpException('您无权查看此文档', HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
const authority = await this.documentService.getDocumentAuthority(documentId, user.id);
|
||||
|
||||
if (!authority.readable) {
|
||||
throw new HttpException('您无权查看此文档', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
if (!authority.editable) {
|
||||
connection.readOnly = true;
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
};
|
||||
const authority = await this.documentService.getDocumentAuthority(targetId, user.id);
|
||||
if (!authority.readable) {
|
||||
throw new HttpException('您无权查看此文档', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
break;
|
||||
if (!authority.editable) {
|
||||
connection.readOnly = true;
|
||||
}
|
||||
return {
|
||||
user,
|
||||
};
|
||||
}
|
||||
|
||||
case 'template': {
|
||||
const templateId = targetId;
|
||||
|
||||
const user = await this.userService.decodeToken(token);
|
||||
|
||||
if (!user || !user.id) {
|
||||
throw new HttpException('您无权查看此模板', HttpStatus.UNAUTHORIZED);
|
||||
}
|
||||
|
||||
const template = await this.templateService.findById(templateId);
|
||||
|
||||
const template = await this.templateService.findById(targetId);
|
||||
if (template.createUserId !== user.id) {
|
||||
throw new HttpException('您无权查看此模板', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
return {
|
||||
user,
|
||||
};
|
||||
|
@ -148,34 +121,32 @@ export class CollaborationService {
|
|||
const targetId = requestParameters.get('targetId');
|
||||
const docType = requestParameters.get('docType');
|
||||
|
||||
let state = null;
|
||||
|
||||
switch (docType) {
|
||||
case 'document': {
|
||||
const documentId = targetId;
|
||||
const { state } = await this.documentService.findById(documentId);
|
||||
const unit8 = new Uint8Array(state);
|
||||
|
||||
if (unit8.byteLength) {
|
||||
Y.applyUpdate(document, unit8);
|
||||
}
|
||||
|
||||
return document;
|
||||
const res = await this.documentService.findById(targetId);
|
||||
state = res.state;
|
||||
break;
|
||||
}
|
||||
|
||||
case 'template': {
|
||||
const templateId = targetId;
|
||||
const { state } = await this.templateService.findById(templateId);
|
||||
const unit8 = new Uint8Array(state);
|
||||
|
||||
if (unit8.byteLength) {
|
||||
Y.applyUpdate(document, unit8);
|
||||
}
|
||||
|
||||
return document;
|
||||
const res = await this.templateService.findById(targetId);
|
||||
state = res.state;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error('未知类型');
|
||||
}
|
||||
|
||||
const unit8 = new Uint8Array(state);
|
||||
|
||||
if (unit8.byteLength) {
|
||||
Y.applyUpdate(document, unit8);
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
async onChange(data: onChangePayload) {
|
||||
|
@ -194,9 +165,9 @@ export class CollaborationService {
|
|||
this.debounceTime * 2
|
||||
);
|
||||
};
|
||||
const updateTemplate = this.templateService.updateTemplate.bind(this.templateService);
|
||||
|
||||
const updateHandler =
|
||||
docType === 'document' ? updateDocument : this.templateService.updateTemplate.bind(this.templateService);
|
||||
const updateHandler = docType === 'document' ? updateDocument : updateTemplate;
|
||||
|
||||
this.debounce(`onStoreDocument-${targetId}`, () => {
|
||||
this.onStoreDocument(updateHandler, data).catch((error) => {
|
||||
|
@ -208,13 +179,12 @@ export class CollaborationService {
|
|||
}
|
||||
|
||||
async onStoreDocument(updateHandler, data: onChangePayload) {
|
||||
const { requestParameters, context } = data;
|
||||
const { requestParameters } = data;
|
||||
const targetId = requestParameters.get('targetId');
|
||||
const userId = requestParameters.get('userId');
|
||||
|
||||
if (!userId) {
|
||||
console.error('COLLABORATION: can not get user info');
|
||||
return;
|
||||
throw new HttpException('无用户信息,拒绝存储文档数据变更', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
const node = TiptapTransformer.fromYdoc(data.document);
|
||||
|
@ -233,33 +203,24 @@ export class CollaborationService {
|
|||
const docType = requestParameters.get('docType');
|
||||
const userId = requestParameters.get('userId');
|
||||
|
||||
switch (docType) {
|
||||
case 'document': {
|
||||
const documentId = targetId;
|
||||
const ret = await this.documentService.findById(documentId);
|
||||
|
||||
if (ret && !ret.title) {
|
||||
await this.documentService.updateDocument({ id: userId } as OutUser, targetId, {
|
||||
title: '未命名文档',
|
||||
});
|
||||
}
|
||||
break;
|
||||
if (docType === 'document') {
|
||||
const data = await this.documentService.findById(targetId);
|
||||
if (data && !data.title) {
|
||||
await this.documentService.updateDocument({ id: userId } as OutUser, targetId, {
|
||||
title: '未命名文档',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case 'template': {
|
||||
const templateId = targetId;
|
||||
const ret = await this.templateService.findById(templateId);
|
||||
|
||||
if (ret && !ret.title) {
|
||||
await this.templateService.updateTemplate({ id: userId } as OutUser, targetId, {
|
||||
title: '未命名模板',
|
||||
});
|
||||
}
|
||||
break;
|
||||
if (docType === 'template') {
|
||||
const data = await this.templateService.findById(targetId);
|
||||
if (data && !data.title) {
|
||||
await this.templateService.updateTemplate({ id: userId } as OutUser, targetId, {
|
||||
title: '未命名模板',
|
||||
});
|
||||
}
|
||||
|
||||
default:
|
||||
throw new Error('未知类型');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import { IDocument } from '@think/domains';
|
|||
import { getConfig } from '@think/config';
|
||||
import * as lodash from 'lodash';
|
||||
|
||||
type VerisonDataItem = { version: string; data: string };
|
||||
|
||||
@Injectable()
|
||||
export class DocumentVersionService {
|
||||
private redis: Redis;
|
||||
|
@ -14,7 +16,7 @@ export class DocumentVersionService {
|
|||
this.init();
|
||||
}
|
||||
|
||||
private versionDataToArray(data: Record<string, string>): Array<{ version: string; data: string }> {
|
||||
private versionDataToArray(data: Record<string, string>): Array<VerisonDataItem> {
|
||||
return Object.keys(data)
|
||||
.sort((a, b) => +b - +a)
|
||||
.map((key) => ({ version: key, data: data[key] }));
|
||||
|
@ -88,7 +90,7 @@ export class DocumentVersionService {
|
|||
* @param documentId
|
||||
* @returns
|
||||
*/
|
||||
public async getDocumentVersions(documentId: IDocument['id']): Promise<Array<{ version: string; data: string }>> {
|
||||
public async getDocumentVersions(documentId: IDocument['id']): Promise<Array<VerisonDataItem>> {
|
||||
if (this.error || !this.redis) {
|
||||
throw new HttpException(this.error, HttpStatus.NOT_IMPLEMENTED);
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Injectable, HttpException, HttpStatus, Inject, forwardRef } from '@nest
|
|||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { instanceToPlain } from 'class-transformer';
|
||||
import { EMPTY_DOCUMNENT } from '@think/constants';
|
||||
import { DocumentStatus, IDocument, WikiUserRole } from '@think/domains';
|
||||
import { DocumentAuthorityEntity } from '@entities/document-authority.entity';
|
||||
import { DocumentEntity } from '@entities/document.entity';
|
||||
|
@ -12,33 +13,11 @@ import { CollaborationService } from '@services/collaboration.service';
|
|||
import { DocumentVersionService } from '@services/document-version.service';
|
||||
import { TemplateService } from '@services/template.service';
|
||||
import { ViewService } from '@services/view.service';
|
||||
import { array2tree } from '@helpers/tree.helper';
|
||||
import { DocAuthDto } from '@dtos/doc-auth.dto';
|
||||
import { CreateDocumentDto } from '@dtos/create-document.dto';
|
||||
import { UpdateDocumentDto } from '@dtos/update-document.dto';
|
||||
import { ShareDocumentDto } from '@dtos/share-document.dto';
|
||||
|
||||
const EMPTY_DOCUMNENT = {
|
||||
content: JSON.stringify({
|
||||
default: {
|
||||
type: 'doc',
|
||||
content: [{ type: 'title', content: [{ type: 'text', text: '未命名文档' }] }],
|
||||
},
|
||||
}),
|
||||
state: Buffer.from(
|
||||
new Uint8Array([
|
||||
1, 14, 204, 224, 154, 225, 13, 0, 7, 1, 7, 100, 101, 102, 97, 117, 108, 116, 3, 5, 116, 105, 116, 108, 101, 1, 0,
|
||||
204, 224, 154, 225, 13, 0, 1, 0, 1, 135, 204, 224, 154, 225, 13, 0, 3, 9, 112, 97, 114, 97, 103, 114, 97, 112,
|
||||
104, 40, 0, 204, 224, 154, 225, 13, 3, 6, 105, 110, 100, 101, 110, 116, 1, 125, 0, 40, 0, 204, 224, 154, 225, 13,
|
||||
3, 9, 116, 101, 120, 116, 65, 108, 105, 103, 110, 1, 119, 4, 108, 101, 102, 116, 0, 4, 71, 204, 224, 154, 225, 13,
|
||||
1, 6, 1, 0, 204, 224, 154, 225, 13, 10, 3, 132, 204, 224, 154, 225, 13, 13, 3, 230, 156, 170, 129, 204, 224, 154,
|
||||
225, 13, 14, 6, 132, 204, 224, 154, 225, 13, 20, 6, 229, 145, 189, 229, 144, 141, 129, 204, 224, 154, 225, 13, 22,
|
||||
5, 132, 204, 224, 154, 225, 13, 27, 6, 230, 150, 135, 230, 161, 163, 1, 204, 224, 154, 225, 13, 5, 1, 2, 6, 4, 11,
|
||||
3, 15, 6, 23, 5,
|
||||
])
|
||||
),
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class DocumentService {
|
||||
private collaborationService: CollaborationService;
|
||||
|
@ -46,17 +25,23 @@ export class DocumentService {
|
|||
|
||||
constructor(
|
||||
@InjectRepository(DocumentAuthorityEntity)
|
||||
private readonly documentAuthorityRepo: Repository<DocumentAuthorityEntity>,
|
||||
public readonly documentAuthorityRepo: Repository<DocumentAuthorityEntity>,
|
||||
|
||||
@InjectRepository(DocumentEntity)
|
||||
private readonly documentRepo: Repository<DocumentEntity>,
|
||||
public readonly documentRepo: Repository<DocumentEntity>,
|
||||
|
||||
@Inject(forwardRef(() => MessageService))
|
||||
private readonly messageService: MessageService,
|
||||
|
||||
@Inject(forwardRef(() => UserService))
|
||||
private readonly userService: UserService,
|
||||
|
||||
@Inject(forwardRef(() => WikiService))
|
||||
private readonly wikiService: WikiService,
|
||||
|
||||
@Inject(forwardRef(() => TemplateService))
|
||||
private readonly templateService: TemplateService,
|
||||
|
||||
@Inject(forwardRef(() => ViewService))
|
||||
private readonly viewService: ViewService
|
||||
) {
|
||||
|
@ -91,6 +76,15 @@ export class DocumentService {
|
|||
return documents.map((doc) => instanceToPlain(doc));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库首页文档
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
public async findWikiHomeDocument(wikiId) {
|
||||
return await this.documentRepo.findOne({ wikiId, isWikiHome: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户在指定文档的权限
|
||||
* @param documentId
|
||||
|
@ -176,11 +170,8 @@ export class DocumentService {
|
|||
*/
|
||||
async addDocUser(user: OutUser, dto: DocAuthDto) {
|
||||
const doc = await this.documentRepo.findOne(dto.documentId);
|
||||
if (!doc) {
|
||||
throw new HttpException('文档不存在', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
const targetUser = await this.userService.findOne({ name: dto.userName });
|
||||
|
||||
if (!targetUser) {
|
||||
throw new HttpException('用户不存在', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
@ -226,16 +217,6 @@ export class DocumentService {
|
|||
*/
|
||||
async deleteDocUser(user: OutUser, dto: DocAuthDto): Promise<void> {
|
||||
const doc = await this.documentRepo.findOne({ id: dto.documentId });
|
||||
if (!doc) {
|
||||
throw new HttpException('文档不存在', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
const isCurrentUserCreator = user.id === doc.createUserId;
|
||||
|
||||
if (!isCurrentUserCreator) {
|
||||
throw new HttpException('您无权限进行该操作', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
const targetUser = await this.userService.findOne({ name: dto.userName });
|
||||
|
||||
if (targetUser.id === doc.createUserId) {
|
||||
|
@ -249,7 +230,7 @@ export class DocumentService {
|
|||
|
||||
await this.messageService.notify(targetUser, {
|
||||
title: `您已被移出文档「${doc.title}」`,
|
||||
message: `管理员已将您从文档「${doc.title}」移出!`,
|
||||
message: `${user.name}已将您从文档「${doc.title}」移出!`,
|
||||
url: `/wiki/${doc.wikiId}/document/${doc.id}`,
|
||||
});
|
||||
|
||||
|
@ -332,7 +313,7 @@ export class DocumentService {
|
|||
const document = await this.documentRepo.save(res);
|
||||
|
||||
// 知识库成员权限继承
|
||||
const wikiUsers = await this.wikiService.getWikiUsers({ userId: user.id, wikiId: dto.wikiId }, true);
|
||||
const wikiUsers = await this.wikiService.getWikiUsers(dto.wikiId);
|
||||
|
||||
await Promise.all([
|
||||
await this.operateDocumentAuth({
|
||||
|
@ -376,12 +357,6 @@ export class DocumentService {
|
|||
*/
|
||||
async deleteDocument(user: OutUser, documentId) {
|
||||
const document = await this.documentRepo.findOne(documentId);
|
||||
if (!document) {
|
||||
throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
if (document.createUserId !== user.id) {
|
||||
throw new HttpException('您不是该文档的创建者,无法删除', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
if (document.isWikiHome) {
|
||||
throw new HttpException('该文档作为知识库首页使用,无法删除', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
@ -414,49 +389,11 @@ export class DocumentService {
|
|||
*/
|
||||
public async updateDocument(user: OutUser, documentId: string, dto: UpdateDocumentDto) {
|
||||
const document = await this.documentRepo.findOne(documentId);
|
||||
if (!document) {
|
||||
throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
const authority = await this.documentAuthorityRepo.findOne({
|
||||
documentId,
|
||||
userId: user.id,
|
||||
});
|
||||
if (!authority || !authority.editable) {
|
||||
throw new HttpException('您无权编辑此文档', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
const res = await this.documentRepo.create({ ...document, ...dto });
|
||||
const ret = await this.documentRepo.save(res);
|
||||
return instanceToPlain(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库首页文档
|
||||
* @param user
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
async getWikiHomeDocument(user: OutUser, wikiId) {
|
||||
const res = await this.documentRepo.findOne({ wikiId, isWikiHome: true });
|
||||
return instanceToPlain(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开知识库首页文档
|
||||
* @param user
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
async getWikiPublicHomeDocument(wikiId, userAgent) {
|
||||
const res = await this.documentRepo.findOne({ wikiId, isWikiHome: true });
|
||||
await this.viewService.create({
|
||||
userId: 'public',
|
||||
documentId: res.id,
|
||||
userAgent,
|
||||
});
|
||||
const views = await this.viewService.getDocumentTotalViews(res.id);
|
||||
return { ...instanceToPlain(res), views };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文档详情
|
||||
* @param user
|
||||
|
@ -464,27 +401,20 @@ export class DocumentService {
|
|||
* @returns
|
||||
*/
|
||||
public async getDocumentDetail(user: OutUser, documentId: string, userAgent) {
|
||||
// 1. 记录访问
|
||||
await this.viewService.create({ userId: user.id, documentId, userAgent });
|
||||
// 2. 查询文档
|
||||
const document = await this.documentRepo.findOne(documentId);
|
||||
|
||||
if (!document) {
|
||||
throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
// 3. 查询权限
|
||||
const authority = await this.documentAuthorityRepo.findOne({
|
||||
documentId,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (!authority || !authority.readable) {
|
||||
throw new HttpException('您无权查看此文档', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
await this.viewService.create({ userId: user.id, documentId, userAgent });
|
||||
// 4. 查询访问
|
||||
const views = await this.viewService.getDocumentTotalViews(documentId);
|
||||
|
||||
// 5. 生成响应
|
||||
const doc = instanceToPlain(document);
|
||||
const createUser = await this.userService.findById(doc.createUserId);
|
||||
|
||||
return { document: { ...doc, views, createUser }, authority };
|
||||
}
|
||||
|
||||
|
@ -495,21 +425,6 @@ export class DocumentService {
|
|||
* @returns
|
||||
*/
|
||||
public async getDocumentVersion(user: OutUser, documentId: string) {
|
||||
const document = await this.documentRepo.findOne(documentId);
|
||||
|
||||
if (!document) {
|
||||
throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
const authority = await this.documentAuthorityRepo.findOne({
|
||||
documentId,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (!authority || !authority.readable) {
|
||||
throw new HttpException('您无权查看此文档', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
const data = await this.documentVersionService.getDocumentVersions(documentId);
|
||||
return data;
|
||||
}
|
||||
|
@ -520,19 +435,6 @@ export class DocumentService {
|
|||
*/
|
||||
async shareDocument(user: OutUser, documentId, dto: ShareDocumentDto, nextStatus = null) {
|
||||
const document = await this.documentRepo.findOne(documentId);
|
||||
if (!document) {
|
||||
throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
const authority = await this.documentAuthorityRepo.findOne({
|
||||
documentId,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (!authority || !authority.editable) {
|
||||
throw new HttpException('您无权编辑此文档', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
nextStatus = !nextStatus
|
||||
? document.status === DocumentStatus.private
|
||||
? DocumentStatus.public
|
||||
|
@ -553,14 +455,6 @@ export class DocumentService {
|
|||
async getPublicDocumentDetail(documentId, dto: ShareDocumentDto, userAgent) {
|
||||
const document = await this.documentRepo.findOne(documentId);
|
||||
|
||||
if (!document) {
|
||||
throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
if (document.status !== DocumentStatus.public) {
|
||||
throw new HttpException('私有文档,无法查看内容', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
if (document.sharePassword && !dto.sharePassword) {
|
||||
throw new HttpException('输入密码后查看内容', HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
@ -596,21 +490,8 @@ export class DocumentService {
|
|||
? await this.documentRepo.findOne(documentId)
|
||||
: await this.documentRepo.findOne({ wikiId, isWikiHome: true });
|
||||
|
||||
if (!document) {
|
||||
throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
let unSortDocuments = [];
|
||||
|
||||
const authority = await this.documentAuthorityRepo.findOne({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (!authority || !authority.readable) {
|
||||
throw new HttpException('您无权查看该文档下的子文档', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
if (document.isWikiHome) {
|
||||
unSortDocuments = await this.documentRepo.find({
|
||||
wikiId: document.wikiId,
|
||||
|
@ -661,16 +542,8 @@ export class DocumentService {
|
|||
? await this.documentRepo.findOne(documentId)
|
||||
: await this.documentRepo.findOne({ wikiId, isWikiHome: true });
|
||||
|
||||
if (!document) {
|
||||
throw new HttpException('文档不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
let unSortDocuments = [];
|
||||
|
||||
if (document.status !== DocumentStatus.public) {
|
||||
throw new HttpException('私有文档,无法查看内容', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
if (document.isWikiHome) {
|
||||
unSortDocuments = await this.documentRepo.find({
|
||||
wikiId: document.wikiId,
|
||||
|
@ -717,153 +590,6 @@ export class DocumentService {
|
|||
return docsWithCreateUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库的文档目录
|
||||
* @param user
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
public async getWikiTocs(user: OutUser, wikiId: string) {
|
||||
const ONE_DAY_TIME = 24 * 60 * 60 * 1000;
|
||||
await this.wikiService.getWikiDetail(user, wikiId);
|
||||
// @ts-ignore
|
||||
const records = await this.documentAuthorityRepo.find({
|
||||
userId: user.id,
|
||||
wikiId,
|
||||
});
|
||||
|
||||
const ids = records.map((record) => record.documentId);
|
||||
const documents = await this.documentRepo.findByIds(ids, {
|
||||
order: { createdAt: 'ASC' },
|
||||
});
|
||||
documents.sort((a, b) => a.index - b.index);
|
||||
|
||||
documents.forEach((doc) => {
|
||||
delete doc.state;
|
||||
});
|
||||
|
||||
const docs = documents
|
||||
.filter((doc) => !doc.isWikiHome)
|
||||
.map((doc) => {
|
||||
const res = instanceToPlain(doc);
|
||||
res.key = res.id;
|
||||
res.label = res.title;
|
||||
return res;
|
||||
});
|
||||
|
||||
const docsWithCreateUser = await Promise.all(
|
||||
docs.map(async (doc) => {
|
||||
const createUser = await this.userService.findById(doc.createUserId);
|
||||
return { ...doc, createUser };
|
||||
})
|
||||
);
|
||||
|
||||
return array2tree(docsWithCreateUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重排知识库目录
|
||||
* @param user
|
||||
* @param wikiId
|
||||
* @param relations
|
||||
*/
|
||||
public async orderWikiTocs(
|
||||
user: OutUser,
|
||||
wikiId: string,
|
||||
relations: Array<{ id: string; parentDocumentId?: string; index: number }>
|
||||
) {
|
||||
await this.wikiService.getWikiDetail(user, wikiId);
|
||||
await Promise.all(
|
||||
relations.map(async (relation) => {
|
||||
const { id, parentDocumentId, index } = relation;
|
||||
const doc = await this.documentRepo.findOne(id);
|
||||
|
||||
if (doc) {
|
||||
const newData = await this.documentRepo.merge(doc, {
|
||||
parentDocumentId,
|
||||
index,
|
||||
});
|
||||
await this.documentRepo.save(newData);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库公开的文档目录
|
||||
* @param user
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
public async getPublicWikiTocs(wikiId: string) {
|
||||
await this.wikiService.getPublicWikiDetail(wikiId);
|
||||
// @ts-ignore
|
||||
const unSortDocuments = await this.documentRepo.find({
|
||||
wikiId,
|
||||
status: DocumentStatus.public,
|
||||
});
|
||||
|
||||
const documents = await this.documentRepo.findByIds(
|
||||
unSortDocuments.map((d) => d.id),
|
||||
{
|
||||
order: { createdAt: 'ASC' },
|
||||
}
|
||||
);
|
||||
|
||||
documents.sort((a, b) => a.index - b.index);
|
||||
|
||||
const docs = documents
|
||||
.filter((doc) => !doc.isWikiHome)
|
||||
.map((doc) => {
|
||||
const res = instanceToPlain(doc);
|
||||
res.key = res.id;
|
||||
res.label = res.title;
|
||||
return res;
|
||||
});
|
||||
|
||||
docs.forEach((doc) => {
|
||||
delete doc.state;
|
||||
});
|
||||
|
||||
return array2tree(docs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库所有的文档
|
||||
* @param user
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
public async getWikiDocs(user: OutUser, wikiId: string) {
|
||||
this.wikiService.getWikiDetail(user, wikiId);
|
||||
const records = await this.documentAuthorityRepo.find({
|
||||
userId: user.id,
|
||||
wikiId,
|
||||
});
|
||||
const ids = records.map((record) => record.documentId);
|
||||
const documents = await this.documentRepo.findByIds(ids);
|
||||
documents.forEach((doc) => {
|
||||
delete doc.state;
|
||||
});
|
||||
|
||||
const docs = documents
|
||||
.filter((doc) => !doc.isWikiHome)
|
||||
.map((doc) => {
|
||||
const res = instanceToPlain(doc);
|
||||
res.key = res.id;
|
||||
return res;
|
||||
});
|
||||
|
||||
const docsWithCreateUser = await Promise.all(
|
||||
docs.map(async (doc) => {
|
||||
const createUser = await this.userService.findById(doc.createUserId);
|
||||
return { ...doc, createUser };
|
||||
})
|
||||
);
|
||||
|
||||
return docsWithCreateUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户最近更新的10篇文档
|
||||
* @param user
|
||||
|
|
|
@ -17,8 +17,7 @@ export class FileService {
|
|||
* @param file
|
||||
*/
|
||||
async uploadFile(file) {
|
||||
console.log('upload', file);
|
||||
const { originalname, mimetype, size, buffer } = file;
|
||||
const { originalname, buffer } = file;
|
||||
const filename = `/${dateFormat(new Date(), 'yyyy-MM-dd')}/${uniqueid()}/${originalname}`;
|
||||
const url = await this.ossClient.putFile(filename, buffer);
|
||||
return url;
|
||||
|
|
|
@ -12,6 +12,7 @@ export class TemplateService {
|
|||
constructor(
|
||||
@InjectRepository(TemplateEntity)
|
||||
private readonly templateRepo: Repository<TemplateEntity>,
|
||||
|
||||
@Inject(forwardRef(() => UserService))
|
||||
private readonly userService: UserService
|
||||
) {}
|
||||
|
|
|
@ -20,32 +20,21 @@ export class UserService {
|
|||
constructor(
|
||||
@InjectRepository(UserEntity)
|
||||
private readonly userRepo: Repository<UserEntity>,
|
||||
|
||||
private readonly confifgService: ConfigService,
|
||||
|
||||
@Inject(forwardRef(() => JwtService))
|
||||
private readonly jwtService: JwtService,
|
||||
|
||||
@Inject(forwardRef(() => MessageService))
|
||||
private readonly messageService: MessageService,
|
||||
|
||||
@Inject(forwardRef(() => CollectorService))
|
||||
private readonly collectorService: CollectorService,
|
||||
|
||||
@Inject(forwardRef(() => WikiService))
|
||||
private readonly wikiService: WikiService
|
||||
) {
|
||||
this.createSuperAdmin();
|
||||
}
|
||||
|
||||
private async createSuperAdmin() {
|
||||
const superadmin = this.confifgService.get('superadmin');
|
||||
if (!superadmin) return;
|
||||
if (!(await this.userRepo.findOne({ name: superadmin.name }))) {
|
||||
const res = await this.userRepo.create({
|
||||
...superadmin,
|
||||
confirmPassword: superadmin.password,
|
||||
role: UserRole.superadmin,
|
||||
});
|
||||
await this.userRepo.save(res);
|
||||
}
|
||||
console.info('已注册超管用户,请及时修改默认密码');
|
||||
}
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 根据 id 查询用户
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { Injectable, HttpException, HttpStatus, Inject, forwardRef } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { instanceToPlain } from 'class-transformer';
|
||||
import { WikiStatus, WikiUserRole, DocumentStatus, IPagination } from '@think/domains';
|
||||
import { array2tree } from '@helpers/tree.helper';
|
||||
import { WikiEntity } from '@entities/wiki.entity';
|
||||
import { WikiUserEntity } from '@entities/wiki-user.entity';
|
||||
import { UserService } from '@services/user.service';
|
||||
import { MessageService } from '@services/message.service';
|
||||
import { CollectorService } from '@services/collector.service';
|
||||
import { OutUser } from '@services/user.service';
|
||||
import { ViewService } from '@services/view.service';
|
||||
import { DocumentService } from '@services/document.service';
|
||||
import { WikiUserDto } from '@dtos/wiki-user.dto';
|
||||
import { CreateWikiDto } from '@dtos/create-wiki.dto';
|
||||
|
@ -19,18 +22,36 @@ export class WikiService {
|
|||
constructor(
|
||||
@InjectRepository(WikiEntity)
|
||||
private readonly wikiRepo: Repository<WikiEntity>,
|
||||
|
||||
@InjectRepository(WikiUserEntity)
|
||||
private readonly wikiUserRepo: Repository<WikiUserEntity>,
|
||||
|
||||
@Inject(forwardRef(() => MessageService))
|
||||
private readonly messageService: MessageService,
|
||||
|
||||
@Inject(forwardRef(() => CollectorService))
|
||||
private readonly collectorService: CollectorService,
|
||||
|
||||
@Inject(forwardRef(() => DocumentService))
|
||||
private readonly documentService: DocumentService,
|
||||
|
||||
@Inject(forwardRef(() => UserService))
|
||||
private readonly userService: UserService
|
||||
private readonly userService: UserService,
|
||||
|
||||
@Inject(forwardRef(() => ViewService))
|
||||
private readonly viewService: ViewService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 按 id 查取知识库
|
||||
* @param user
|
||||
* @param dto
|
||||
* @returns
|
||||
*/
|
||||
public async findById(id: string) {
|
||||
return await this.wikiRepo.findOne(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 id 查取一组知识库
|
||||
* @param user
|
||||
|
@ -42,6 +63,19 @@ export class WikiService {
|
|||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库成员信息
|
||||
* @param wikiId
|
||||
* @param userId
|
||||
* @returns
|
||||
*/
|
||||
public async findWikiUser(wikiId: string, userId: string) {
|
||||
return await this.wikiUserRepo.findOne({
|
||||
userId,
|
||||
wikiId,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作知识库成员(添加、修改角色)
|
||||
* @param param0
|
||||
|
@ -133,6 +167,7 @@ export class WikiService {
|
|||
}
|
||||
|
||||
const homeDoc = await this.getWikiHomeDocument(user, wikiId);
|
||||
|
||||
await this.documentService.operateDocumentAuth({
|
||||
currentUserId: user.id,
|
||||
documentId: homeDoc.id,
|
||||
|
@ -140,6 +175,7 @@ export class WikiService {
|
|||
readable: true,
|
||||
editable: dto.userRole === WikiUserRole.admin,
|
||||
});
|
||||
|
||||
return this.operateWikiUser({
|
||||
wikiId,
|
||||
currentUserId: user.id,
|
||||
|
@ -177,17 +213,6 @@ export class WikiService {
|
|||
* @returns
|
||||
*/
|
||||
async deleteWikiUser(user: OutUser, wikiId, dto: WikiUserDto): Promise<void> {
|
||||
const currentWikiUserRole = (
|
||||
await this.wikiUserRepo.findOne({
|
||||
wikiId,
|
||||
userId: user.id,
|
||||
})
|
||||
).userRole;
|
||||
|
||||
if (currentWikiUserRole !== WikiUserRole.admin) {
|
||||
throw new HttpException('您无权限进行该操作', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
const targetUser = await this.userService.findOne({ name: dto.userName });
|
||||
|
||||
if (!targetUser) {
|
||||
|
@ -207,7 +232,7 @@ export class WikiService {
|
|||
|
||||
await this.messageService.notify(targetUser, {
|
||||
title: `您已被移出知识库「${wiki.name}」`,
|
||||
message: `管理员已将您从知识库「${wiki.name}」移出!`,
|
||||
message: `${user.name}已将您从知识库「${wiki.name}」移出!`,
|
||||
url: `/wiki/${wiki.id}`,
|
||||
});
|
||||
|
||||
|
@ -218,15 +243,8 @@ export class WikiService {
|
|||
* 获取知识库成员
|
||||
* @param userId
|
||||
* @param wikiId
|
||||
* @param internalInvoke 是否为内部调用,内部调用忽略权限检查
|
||||
*/
|
||||
async getWikiUsers({ userId, wikiId }, internalInvoke = false) {
|
||||
const currenWikiUser = await this.getWikiUserDetail({ userId, wikiId });
|
||||
|
||||
if (currenWikiUser.userRole !== WikiUserRole.admin && !internalInvoke) {
|
||||
throw new HttpException('您无权限查看', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
async getWikiUsers(wikiId) {
|
||||
const records = await this.wikiUserRepo.find({ wikiId });
|
||||
const ids = records.map((record) => record.userId);
|
||||
const users = await this.userService.findByIds(ids);
|
||||
|
@ -382,21 +400,7 @@ export class WikiService {
|
|||
* @returns
|
||||
*/
|
||||
async getWikiDetail(user: OutUser, wikiId: string) {
|
||||
const wikiUser = await this.wikiUserRepo.findOne({
|
||||
userId: user.id,
|
||||
wikiId,
|
||||
});
|
||||
|
||||
if (!wikiUser) {
|
||||
throw new HttpException('您无权查看该知识库', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
const wiki = await this.wikiRepo.findOne(wikiId);
|
||||
|
||||
if (!wiki) {
|
||||
throw new HttpException('访问的知识库不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
const createUser = await this.userService.findById(wiki.createUserId);
|
||||
return { ...wiki, createUser };
|
||||
}
|
||||
|
@ -408,48 +412,8 @@ export class WikiService {
|
|||
* @returns
|
||||
*/
|
||||
async getWikiHomeDocument(user: OutUser, wikiId) {
|
||||
await this.getWikiUserDetail({ wikiId, userId: user.id });
|
||||
return this.documentService.getWikiHomeDocument(user, wikiId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开知识库首页文档(首页文档由系统自动创建)
|
||||
* @param user
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
async getWikiPublicHomeDocument(wikiId, userAgent) {
|
||||
const wiki = await this.wikiRepo.findOne(wikiId);
|
||||
|
||||
if (!wiki) {
|
||||
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
if (wiki.status !== WikiStatus.public) {
|
||||
throw new HttpException('私有文档,无法查看内容', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
return this.documentService.getWikiPublicHomeDocument(wikiId, userAgent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开知识库详情
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
async getPublicWikiDetail(wikiId: string) {
|
||||
const wiki = await this.wikiRepo.findOne(wikiId);
|
||||
|
||||
if (!wiki) {
|
||||
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
if (wiki.status !== WikiStatus.public) {
|
||||
throw new HttpException('私有文档,无法查看内容', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
const createUser = await this.userService.findById(wiki.createUserId);
|
||||
return { ...wiki, createUser };
|
||||
const res = await this.documentService.documentRepo.findOne({ wikiId, isWikiHome: true });
|
||||
return instanceToPlain(res);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -460,15 +424,6 @@ export class WikiService {
|
|||
* @returns
|
||||
*/
|
||||
async updateWiki(user: OutUser, wikiId, dto: UpdateWikiDto) {
|
||||
const workspaceUser = await this.getWikiUserDetail({
|
||||
wikiId,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (workspaceUser.userRole !== WikiUserRole.admin) {
|
||||
throw new HttpException('您没有权限更新该知识库信息', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
|
||||
const oldData = await this.wikiRepo.findOne(wikiId);
|
||||
if (!oldData) {
|
||||
throw new HttpException('目标知识库不存在', HttpStatus.NOT_FOUND);
|
||||
|
@ -488,10 +443,6 @@ export class WikiService {
|
|||
* @returns
|
||||
*/
|
||||
async deleteWiki(user: OutUser, wikiId) {
|
||||
const wikiUser = await this.getWikiUserDetail({ wikiId, userId: user.id });
|
||||
if (wikiUser.userRole !== WikiUserRole.admin) {
|
||||
throw new HttpException('您没有权限操作该知识库', HttpStatus.FORBIDDEN);
|
||||
}
|
||||
const wiki = await this.wikiRepo.findOne(wikiId);
|
||||
if (user.id !== wiki.createUserId) {
|
||||
throw new HttpException('您不是创建者,无法删除该知识库', HttpStatus.FORBIDDEN);
|
||||
|
@ -565,7 +516,38 @@ export class WikiService {
|
|||
* @returns
|
||||
*/
|
||||
async getWikiTocs(user: OutUser, wikiId) {
|
||||
return await this.documentService.getWikiTocs(user, wikiId);
|
||||
const records = await this.documentService.documentAuthorityRepo.find({
|
||||
userId: user.id,
|
||||
wikiId,
|
||||
});
|
||||
|
||||
const ids = records.map((record) => record.documentId);
|
||||
const documents = await this.documentService.documentRepo.findByIds(ids, {
|
||||
order: { createdAt: 'ASC' },
|
||||
});
|
||||
documents.sort((a, b) => a.index - b.index);
|
||||
|
||||
documents.forEach((doc) => {
|
||||
delete doc.state;
|
||||
});
|
||||
|
||||
const docs = documents
|
||||
.filter((doc) => !doc.isWikiHome)
|
||||
.map((doc) => {
|
||||
const res = instanceToPlain(doc);
|
||||
res.key = res.id;
|
||||
res.label = res.title;
|
||||
return res;
|
||||
});
|
||||
|
||||
const docsWithCreateUser = await Promise.all(
|
||||
docs.map(async (doc) => {
|
||||
const createUser = await this.userService.findById(doc.createUserId);
|
||||
return { ...doc, createUser };
|
||||
})
|
||||
);
|
||||
|
||||
return array2tree(docsWithCreateUser);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -574,22 +556,59 @@ export class WikiService {
|
|||
* @param wikiId
|
||||
* @param relations
|
||||
*/
|
||||
public async orderWikiTocs(
|
||||
user: OutUser,
|
||||
wikiId: string,
|
||||
relations: Array<{ id: string; parentDocumentId?: string; index: number }>
|
||||
) {
|
||||
return await this.documentService.orderWikiTocs(user, wikiId, relations);
|
||||
public async orderWikiTocs(relations: Array<{ id: string; parentDocumentId?: string; index: number }>) {
|
||||
await Promise.all(
|
||||
relations.map(async (relation) => {
|
||||
const { id, parentDocumentId, index } = relation;
|
||||
const doc = await this.documentService.documentRepo.findOne(id);
|
||||
|
||||
if (doc) {
|
||||
const newData = await this.documentService.documentRepo.merge(doc, {
|
||||
parentDocumentId,
|
||||
index,
|
||||
});
|
||||
await this.documentService.documentRepo.save(newData);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取知识库目录
|
||||
* 获取知识库所有文档(无结构嵌套)
|
||||
* @param user
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
async getWikiDocs(user: OutUser, wikiId) {
|
||||
return await this.documentService.getWikiDocs(user, wikiId);
|
||||
// 通过文档成员表获取当前用户可查阅的所有文档
|
||||
const records = await this.documentService.documentAuthorityRepo.find({
|
||||
userId: user.id,
|
||||
wikiId,
|
||||
});
|
||||
|
||||
const ids = records.map((record) => record.documentId);
|
||||
|
||||
const documents = await this.documentService.documentRepo.findByIds(ids);
|
||||
documents.forEach((doc) => {
|
||||
delete doc.state;
|
||||
});
|
||||
|
||||
const docs = documents
|
||||
.filter((doc) => !doc.isWikiHome)
|
||||
.map((doc) => {
|
||||
const res = instanceToPlain(doc);
|
||||
res.key = res.id;
|
||||
return res;
|
||||
});
|
||||
|
||||
const docsWithCreateUser = await Promise.all(
|
||||
docs.map(async (doc) => {
|
||||
const createUser = await this.userService.findById(doc.createUserId);
|
||||
return { ...doc, createUser };
|
||||
})
|
||||
);
|
||||
|
||||
return docsWithCreateUser;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -598,11 +617,67 @@ export class WikiService {
|
|||
* @returns
|
||||
*/
|
||||
async getPublicWikiTocs(wikiId) {
|
||||
return await this.documentService.getPublicWikiTocs(wikiId);
|
||||
const unSortDocuments = await this.documentService.documentRepo.find({
|
||||
wikiId,
|
||||
status: DocumentStatus.public,
|
||||
});
|
||||
|
||||
const documents = await this.documentService.documentRepo.findByIds(
|
||||
unSortDocuments.map((d) => d.id),
|
||||
{
|
||||
order: { createdAt: 'ASC' },
|
||||
}
|
||||
);
|
||||
|
||||
documents.sort((a, b) => a.index - b.index);
|
||||
|
||||
const docs = documents
|
||||
.filter((doc) => !doc.isWikiHome)
|
||||
.map((doc) => {
|
||||
const res = instanceToPlain(doc);
|
||||
res.key = res.id;
|
||||
res.label = res.title;
|
||||
return res;
|
||||
});
|
||||
|
||||
docs.forEach((doc) => {
|
||||
delete doc.state;
|
||||
});
|
||||
|
||||
return array2tree(docs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户所有知识库
|
||||
* 获取公开知识库首页文档(首页文档由系统自动创建)
|
||||
* @param user
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
async getPublicWikiHomeDocument(wikiId, userAgent) {
|
||||
const res = await this.documentService.documentRepo.findOne({ wikiId, isWikiHome: true });
|
||||
|
||||
await this.viewService.create({
|
||||
userId: 'public',
|
||||
documentId: res.id,
|
||||
userAgent,
|
||||
});
|
||||
const views = await this.viewService.getDocumentTotalViews(res.id);
|
||||
return { ...instanceToPlain(res), views };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取公开知识库详情
|
||||
* @param wikiId
|
||||
* @returns
|
||||
*/
|
||||
async getPublicWikiDetail(wikiId: string) {
|
||||
const wiki = await this.wikiRepo.findOne(wikiId);
|
||||
const createUser = await this.userService.findById(wiki.createUserId);
|
||||
return { ...wiki, createUser };
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有公开知识库
|
||||
* @param user
|
||||
* @param pagination
|
||||
* @returns
|
||||
|
|
Loading…
Reference in New Issue