refactor: use guard

pull/25/head
fantasticit 2022-04-04 21:42:52 +08:00
parent dffbc3e464
commit 52329d92f0
18 changed files with 812 additions and 553 deletions

View File

@ -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;
};

View File

@ -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

View File

@ -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"}

View File

@ -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,
])
),
};

View File

@ -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);
}
}

View File

@ -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)

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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],

View File

@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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;

View File

@ -12,6 +12,7 @@ export class TemplateService {
constructor(
@InjectRepository(TemplateEntity)
private readonly templateRepo: Repository<TemplateEntity>,
@Inject(forwardRef(() => UserService))
private readonly userService: UserService
) {}

View File

@ -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

View File

@ -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