diff --git a/packages/constants/lib/index.d.ts b/packages/constants/lib/index.d.ts index a6f5f6b2..bb08b90c 100644 --- a/packages/constants/lib/index.d.ts +++ b/packages/constants/lib/index.d.ts @@ -1,2 +1,7 @@ +/// 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; +}; diff --git a/packages/constants/lib/index.js b/packages/constants/lib/index.js index cc09876b..d4583e50 100644 --- a/packages/constants/lib/index.js +++ b/packages/constants/lib/index.js @@ -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 \ No newline at end of file diff --git a/packages/constants/lib/index.js.map b/packages/constants/lib/index.js.map index 0605370f..6140044a 100644 --- a/packages/constants/lib/index.js.map +++ b/packages/constants/lib/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"} \ No newline at end of file +{"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"} \ No newline at end of file diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts index a58ee235..96c73493 100644 --- a/packages/constants/src/index.ts +++ b/packages/constants/src/index.ts @@ -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, + ]) + ), +}; diff --git a/packages/server/src/controllers/document.controller.ts b/packages/server/src/controllers/document.controller.ts index 93dc5eef..338e9bf5 100644 --- a/packages/server/src/controllers/document.controller.ts +++ b/packages/server/src/controllers/document.controller.ts @@ -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); + } } diff --git a/packages/server/src/controllers/wiki.controller.ts b/packages/server/src/controllers/wiki.controller.ts index e1f1f8a8..f5de2ac4 100644 --- a/packages/server/src/controllers/wiki.controller.ts +++ b/packages/server/src/controllers/wiki.controller.ts @@ -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) diff --git a/packages/server/src/guard/document-auth.guard.ts b/packages/server/src/guard/document-auth.guard.ts new file mode 100644 index 00000000..ab600cb2 --- /dev/null +++ b/packages/server/src/guard/document-auth.guard.ts @@ -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 { + const needAuth = this.reflector.get(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; + } +} diff --git a/packages/server/src/guard/document-status.guard.ts b/packages/server/src/guard/document-status.guard.ts new file mode 100644 index 00000000..2efcc14a --- /dev/null +++ b/packages/server/src/guard/document-status.guard.ts @@ -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 { + const targetStatus = this.reflector.get(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; + } +} diff --git a/packages/server/src/guard/wiki-status.guard.ts b/packages/server/src/guard/wiki-status.guard.ts new file mode 100644 index 00000000..69ae6689 --- /dev/null +++ b/packages/server/src/guard/wiki-status.guard.ts @@ -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 { + const targetStatus = this.reflector.get(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; + } +} diff --git a/packages/server/src/guard/wiki-user.guard.ts b/packages/server/src/guard/wiki-user.guard.ts new file mode 100644 index 00000000..cf7655ae --- /dev/null +++ b/packages/server/src/guard/wiki-user.guard.ts @@ -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 { + const targetUserRole = this.reflector.get(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; + } +} diff --git a/packages/server/src/modules/wiki.module.ts b/packages/server/src/modules/wiki.module.ts index 7608926e..90d60bcc 100644 --- a/packages/server/src/modules/wiki.module.ts +++ b/packages/server/src/modules/wiki.module.ts @@ -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], diff --git a/packages/server/src/services/collaboration.service.ts b/packages/server/src/services/collaboration.service.ts index b3509bd1..f21be713 100644 --- a/packages/server/src/services/collaboration.service.ts +++ b/packages/server/src/services/collaboration.service.ts @@ -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; } } } diff --git a/packages/server/src/services/document-version.service.ts b/packages/server/src/services/document-version.service.ts index c77f5086..c7e16e4a 100644 --- a/packages/server/src/services/document-version.service.ts +++ b/packages/server/src/services/document-version.service.ts @@ -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): Array<{ version: string; data: string }> { + private versionDataToArray(data: Record): Array { 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> { + public async getDocumentVersions(documentId: IDocument['id']): Promise> { if (this.error || !this.redis) { throw new HttpException(this.error, HttpStatus.NOT_IMPLEMENTED); } diff --git a/packages/server/src/services/document.service.ts b/packages/server/src/services/document.service.ts index a602066e..28c04859 100644 --- a/packages/server/src/services/document.service.ts +++ b/packages/server/src/services/document.service.ts @@ -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, + public readonly documentAuthorityRepo: Repository, + @InjectRepository(DocumentEntity) - private readonly documentRepo: Repository, + public readonly documentRepo: Repository, + @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 { 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 diff --git a/packages/server/src/services/file.service.ts b/packages/server/src/services/file.service.ts index bb2b9b8a..4affd9ba 100644 --- a/packages/server/src/services/file.service.ts +++ b/packages/server/src/services/file.service.ts @@ -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; diff --git a/packages/server/src/services/template.service.ts b/packages/server/src/services/template.service.ts index ff1746ba..5c414be7 100644 --- a/packages/server/src/services/template.service.ts +++ b/packages/server/src/services/template.service.ts @@ -12,6 +12,7 @@ export class TemplateService { constructor( @InjectRepository(TemplateEntity) private readonly templateRepo: Repository, + @Inject(forwardRef(() => UserService)) private readonly userService: UserService ) {} diff --git a/packages/server/src/services/user.service.ts b/packages/server/src/services/user.service.ts index 7378701f..9c70f055 100644 --- a/packages/server/src/services/user.service.ts +++ b/packages/server/src/services/user.service.ts @@ -20,32 +20,21 @@ export class UserService { constructor( @InjectRepository(UserEntity) private readonly userRepo: Repository, + 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 查询用户 diff --git a/packages/server/src/services/wiki.service.ts b/packages/server/src/services/wiki.service.ts index 14c3a19e..82ded160 100644 --- a/packages/server/src/services/wiki.service.ts +++ b/packages/server/src/services/wiki.service.ts @@ -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, + @InjectRepository(WikiUserEntity) private readonly wikiUserRepo: Repository, + @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 { - 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