From f75e7eda6c9fba0fe51f6515ecca28b2d24939ef Mon Sep 17 00:00:00 2001 From: Innei Date: Mon, 6 Sep 2021 15:19:28 +0800 Subject: [PATCH] feat: category module done --- package.json | 8 +- patch/v2.0.0-alpha.1.ts | 2 +- paw.paw | Bin 29009 -> 31824 bytes pnpm-lock.yaml | 12 ++ src/modules/category/category.controller.ts | 177 +++++++++++++++++--- src/modules/category/category.dto.ts | 24 +-- src/modules/category/category.model.ts | 11 ++ src/modules/category/category.service.ts | 88 +++++++++- src/shared/model/base.model.ts | 3 +- 9 files changed, 273 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index 33702915..a8b2ca48 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "@nestjs/config": "^1.0.1", "@nestjs/core": "^8.0.6", "@nestjs/jwt": "^8.0.0", + "@nestjs/mapped-types": "*", "@nestjs/passport": "^8.0.1", "@nestjs/platform-fastify": "^8.0.6", "@nestjs/platform-socket.io": "8.0.6", @@ -59,12 +60,14 @@ "class-transformer": "^0.4.0", "class-validator": "^0.13.1", "dayjs": "^1.10.6", + "dotenv": "*", "ejs": "^3.1.6", "fastify-swagger": "^4.9.0", "image-size": "^1.0.0", + "inquirer": "*", "lodash": "*", "mongoose": "*", - "dotenv": "*", + "mongoose-lean-id": "^0.2.0", "mongoose-lean-virtuals": "^0.8.0", "mongoose-paginate-v2": "^1.4.2", "nanoid": "^3.1.25", @@ -76,8 +79,7 @@ "redis": "3.1.2", "reflect-metadata": "^0.1.13", "rxjs": "^7.3.0", - "snakecase-keys": "^4.0.2", - "inquirer": "*" + "snakecase-keys": "^4.0.2" }, "devDependencies": { "@innei-util/eslint-config-ts": "^0.2.3", diff --git a/patch/v2.0.0-alpha.1.ts b/patch/v2.0.0-alpha.1.ts index ad867756..cffa6ffe 100644 --- a/patch/v2.0.0-alpha.1.ts +++ b/patch/v2.0.0-alpha.1.ts @@ -12,6 +12,6 @@ patch(async ({ models: { note, post, category } }) => { }, ) }), - category.aggregate([{ $unset: 'count' }]), + category.updateMany({}, { $unset: { count: '' } }), ]) }) diff --git a/paw.paw b/paw.paw index 6be5f7f558d35296d10698ac8a590b5b8f49d249..c0d04aa93bdf401b6cd76039f789dad80ff69312 100644 GIT binary patch delta 13969 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 delta 11765 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 diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45ce7803..08560b17 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,7 @@ specifiers: '@nestjs/config': ^1.0.1 '@nestjs/core': ^8.0.6 '@nestjs/jwt': ^8.0.0 + '@nestjs/mapped-types': '*' '@nestjs/passport': ^8.0.1 '@nestjs/platform-fastify': ^8.0.6 '@nestjs/platform-socket.io': 8.0.6 @@ -52,6 +53,7 @@ specifiers: lint-staged: ^11.1.2 lodash: '*' mongoose: '*' + mongoose-lean-id: ^0.2.0 mongoose-lean-virtuals: ^0.8.0 mongoose-paginate-v2: ^1.4.2 nanoid: ^3.1.25 @@ -82,6 +84,7 @@ dependencies: '@nestjs/config': 1.0.1_e2399148d990aa1f9a4b6ba2ac7a5b74 '@nestjs/core': 8.0.6_214ebf00327c8ed1d6618d61764e6a91 '@nestjs/jwt': 8.0.0_@nestjs+common@8.0.6 + '@nestjs/mapped-types': 1.0.0_98ff46db2ce9f8b87e20a19aa1e55592 '@nestjs/passport': 8.0.1_2c02db70fddcb59258fa0eed39c4b725 '@nestjs/platform-fastify': 8.0.6_67f7e5db8827badcb202b1d38f6b1aea '@nestjs/platform-socket.io': 8.0.6_875c1aa90becd3a53d7e39e33971fbfe @@ -105,6 +108,7 @@ dependencies: inquirer: 8.1.1 lodash: 4.17.21 mongoose: 5.13.8 + mongoose-lean-id: 0.2.0_mongoose@5.13.8 mongoose-lean-virtuals: 0.8.0_mongoose@5.13.8 mongoose-paginate-v2: 1.4.2 nanoid: 3.1.25 @@ -5130,6 +5134,14 @@ packages: saslprep: 1.0.3 dev: false + /mongoose-lean-id/0.2.0_mongoose@5.13.8: + resolution: {integrity: sha512-ioC++wVTfprE2wh7Baw4jdZ1mCIpK3uw74sWf53enNCKZHVhKsL7fMyB1ryb4SmGwOj4StebTGy7YwimjZ0Qlg==} + peerDependencies: + mongoose: 4.x || 5.x + dependencies: + mongoose: 5.13.8 + dev: false + /mongoose-lean-virtuals/0.8.0_mongoose@5.13.8: resolution: {integrity: sha512-pOdHnLPXtPoVJgx/Fjju2eejaoWjiE3OEaYosF0ELnpUv8KxoIOjNdHLgKyhD+7W7Nd0S755LzZJgSESE2/acg==} peerDependencies: diff --git a/src/modules/category/category.controller.ts b/src/modules/category/category.controller.ts index 7f959eaa..2807e4fb 100644 --- a/src/modules/category/category.controller.ts +++ b/src/modules/category/category.controller.ts @@ -1,7 +1,37 @@ -import { Controller, forwardRef, Get, Inject, Query } from '@nestjs/common' +import { + BadRequestException, + Body, + Controller, + Delete, + forwardRef, + Get, + HttpCode, + Inject, + Param, + Patch, + Post, + Put, + Query, +} from '@nestjs/common' +import { ApiQuery } from '@nestjs/swagger' +import { Types } from 'mongoose' +import { Auth } from '~/common/decorator/auth.decorator' import { ApiName } from '~/common/decorator/openapi.decorator' +import { IsMaster } from '~/common/decorator/role.decorator' +import { CannotFindException } from '~/common/exceptions/cant-find.exception' +import { MongoIdDto } from '~/shared/dto/id.dto' +import { addConditionToSeeHideContent } from '~/utils/query.util' import { PostService } from '../post/post.service' -import { CategoryType, MultiCategoriesQueryDto } from './category.dto' +import { + MultiCategoriesQueryDto, + MultiQueryTagAndCategoryDto, + SlugOrIdDto, +} from './category.dto' +import { + CategoryModel, + CategoryType, + PartialCategoryModel, +} from './category.model' import { CategoryService } from './category.service' @Controller({ path: 'categories' }) @@ -14,33 +44,35 @@ export class CategoryController { ) {} @Get('/') - async getCategories(@Query() query: MultiCategoriesQueryDto) { + async getCategories( + @Query() query: MultiCategoriesQueryDto, + @IsMaster() isMaster: boolean, + ) { const { ids, joint, type = CategoryType.Category } = query // categories is category's mongo id if (ids) { return joint ? await Promise.all( ids.map(async (id) => { - return await this.postService.model.find( - { categoryId: id }, - { - select: 'title slug _id categoryId created modified', - sort: { created: -1 }, - }, - ) + return await this.postService.model + .find( + { categoryId: id, ...addConditionToSeeHideContent(isMaster) }, + 'title slug _id categoryId created modified', + ) + .sort({ created: -1 }) + .lean() }), ) : await Promise.all( ids.map(async (id) => { - const posts = await this.postService.model.find( - { categoryId: id }, - { - select: 'title slug _id created modified', - sort: { created: -1 }, - }, - ) - const category = await this.categoryService.model - .findById(id) + const posts = await this.postService.model + .find( + { categoryId: id, ...addConditionToSeeHideContent(isMaster) }, + 'title slug _id created modified', + ) + .sort({ created: -1 }) .lean() + const category = await this.categoryService.findCategoryById(id) + return { category: { ...category, children: posts }, } @@ -48,7 +80,112 @@ export class CategoryController { ) } return type === CategoryType.Category - ? await this.categoryService.model.find({ type }).lean() + ? await this.categoryService.findAllCategory() : await this.categoryService.getPostTagsSum() } + + @Get('/:query') + @ApiQuery({ + description: '混合查询 分类 和 标签云', + name: 'tag', + enum: ['true', 'false'], + required: false, + }) + async getCategoryById( + @Param() { query }: SlugOrIdDto, + @Query() { tag }: MultiQueryTagAndCategoryDto, + @IsMaster() isMaster: boolean, + ) { + if (!query) { + throw new BadRequestException() + } + if (tag === true) { + return { + tag, + data: await this.categoryService.findArticleWithTag( + query, + addConditionToSeeHideContent(isMaster), + ), + } + } + + const isId = Types.ObjectId.isValid(query) + const res = isId + ? await this.categoryService.model + .findById(query) + .sort({ created: -1 }) + .lean() + : await this.categoryService.model + .findOne({ slug: query }) + .sort({ created: -1 }) + .lean() + + if (!res) { + throw new CannotFindException() + } + + const children = + (await this.categoryService.findCategoryPost(res._id, { + $and: [ + tag ? { tags: tag } : {}, + addConditionToSeeHideContent(isMaster), + ], + })) || [] + return { data: { ...res, children } } + } + + @Post('/') + @Auth() + async create(@Body() body: CategoryModel) { + const { name, slug } = body + return this.categoryService.model.create({ name, slug: slug ?? name }) + } + + @Put('/:id') + @Auth() + async modify(@Param() params: MongoIdDto, @Body() body: CategoryModel) { + const { type, slug, name } = body + const { id } = params + await this.categoryService.model.updateOne( + { _id: id }, + { + slug, + type, + name, + }, + ) + return await this.categoryService.model.findById(id) + } + + @Patch('/:id') + @HttpCode(204) + @Auth() + async patch(@Param() params: MongoIdDto, @Body() body: PartialCategoryModel) { + const { id } = params + await this.categoryService.model.updateOne({ _id: id }, body) + return + } + + @Delete('/:id') + @Auth() + async deleteCategory(@Param() params: MongoIdDto) { + const { id } = params + const category = await this.categoryService.model.findById(id) + if (!category) { + throw new CannotFindException() + } + const postsInCategory = await this.categoryService.findPostsInCategory( + category._id, + ) + if (postsInCategory.length > 0) { + throw new BadRequestException('该分类中有其他文章, 无法被删除') + } + const res = await this.categoryService.model.deleteOne({ + _id: category._id, + }) + if ((await this.categoryService.model.countDocuments({})) === 0) { + await this.categoryService.createDefaultCategory() + } + return res + } } diff --git a/src/modules/category/category.dto.ts b/src/modules/category/category.dto.ts index 4b56a278..d7d5f5db 100644 --- a/src/modules/category/category.dto.ts +++ b/src/modules/category/category.dto.ts @@ -3,7 +3,6 @@ import { ApiProperty } from '@nestjs/swagger' import { Transform } from 'class-transformer' import { IsBoolean, - IsEnum, IsMongoId, IsNotEmpty, IsOptional, @@ -11,28 +10,7 @@ import { } from 'class-validator' import { uniq } from 'lodash' import { IsBooleanOrString } from '~/utils/validator/isBooleanOrString' - -export enum CategoryType { - Category, - Tag, -} - -export class CategoryDto { - @IsString() - @IsNotEmpty() - @ApiProperty() - name: string - - @IsEnum(CategoryType) - @IsOptional() - @ApiProperty({ enum: [0, 1] }) - type?: CategoryType - - @IsString() - @IsNotEmpty() - @IsOptional() - slug?: string -} +import { CategoryType } from './category.model' export class SlugOrIdDto { @IsString() diff --git a/src/modules/category/category.model.ts b/src/modules/category/category.model.ts index 8f9dee60..c15b894c 100644 --- a/src/modules/category/category.model.ts +++ b/src/modules/category/category.model.ts @@ -1,4 +1,6 @@ +import { PartialType } from '@nestjs/mapped-types' import { DocumentType, index, modelOptions, prop } from '@typegoose/typegoose' +import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator' import { BaseModel } from '~/shared/model/base.model' export type CategoryDocument = DocumentType @@ -12,11 +14,20 @@ export enum CategoryType { @modelOptions({ options: { customName: 'Category' } }) export class CategoryModel extends BaseModel { @prop({ unique: true, trim: true, required: true }) + @IsString() + @IsNotEmpty() name!: string @prop({ default: CategoryType.Category }) + @IsEnum(CategoryType) + @IsOptional() type?: CategoryType @prop({ unique: true, required: true }) + @IsString() + @IsNotEmpty() + @IsOptional() slug!: string } + +export class PartialCategoryModel extends PartialType(CategoryModel) {} diff --git a/src/modules/category/category.service.ts b/src/modules/category/category.service.ts index 52ced48d..5ef2941f 100644 --- a/src/modules/category/category.service.ts +++ b/src/modules/category/category.service.ts @@ -1,6 +1,10 @@ import { forwardRef, Inject, Injectable } from '@nestjs/common' -import { ReturnModelType } from '@typegoose/typegoose' +import { DocumentType, ReturnModelType } from '@typegoose/typegoose' +import { omit } from 'lodash' +import { FilterQuery } from 'mongoose' import { InjectModel } from 'nestjs-typegoose' +import { CannotFindException } from '~/common/exceptions/cant-find.exception' +import { PostModel } from '../post/post.model' import { PostService } from '../post/post.service' import { CategoryModel } from './category.model' @@ -11,10 +15,35 @@ export class CategoryService { private readonly categoryModel: ReturnModelType, @Inject(forwardRef(() => PostService)) private readonly postService: PostService, - ) {} + ) { + this.createDefaultCategory() + } - findCategoryById(categoryId: string) { - return this.categoryModel.findById(categoryId) + async findCategoryById(categoryId: string) { + const [category, count] = await Promise.all([ + this.model.findById(categoryId).lean(), + this.postService.model.countDocuments({ categoryId }), + ]) + return { + ...category, + count, + } + } + + async findAllCategory() { + const data = await this.model.find().lean() + const counts = await Promise.all( + data.map((item) => { + const id = item._id + return this.postService.model.countDocuments({ categoryId: id }) + }), + ) + + for (let i = 0; i < data.length; i++) { + Reflect.set(data[i], 'count', counts[i]) + } + + return data } get model() { @@ -38,4 +67,55 @@ export class CategoryService { ]) return data } + + async findArticleWithTag( + tag: string, + condition: FilterQuery> = {}, + ): Promise { + const posts = await this.postService.model + .find( + { + tags: tag, + ...condition, + }, + undefined, + { lean: true }, + ) + .populate('category') + if (!posts.length) { + throw new CannotFindException() + } + return posts.map(({ _id, title, slug, category, created }) => ({ + _id, + title, + slug, + category: omit(category, ['count', '__v', 'created', 'modified']), + created, + })) + } + + async findCategoryPost(categoryId: string, condition: any = {}) { + return await this.postService.model + .find({ + categoryId, + ...condition, + }) + .select('title created slug _id') + .sort({ created: -1 }) + } + + async findPostsInCategory(id: string) { + return await this.postService.model.find({ + categoryId: id, + }) + } + + async createDefaultCategory() { + if ((await this.model.countDocuments()) === 0) { + return await this.model.create({ + name: '默认分类', + slug: 'default', + }) + } + } } diff --git a/src/shared/model/base.model.ts b/src/shared/model/base.model.ts index 16918060..4c7d2ca9 100644 --- a/src/shared/model/base.model.ts +++ b/src/shared/model/base.model.ts @@ -1,11 +1,12 @@ import { ApiHideProperty } from '@nestjs/swagger' import { modelOptions, plugin, prop } from '@typegoose/typegoose' import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator' +import LeanId from 'mongoose-lean-id' import mongooseLeanVirtuals from 'mongoose-lean-virtuals' import Paginate from 'mongoose-paginate-v2' - @plugin(mongooseLeanVirtuals) @plugin(Paginate) +@plugin(LeanId) export class BaseModel { @ApiHideProperty() created?: Date