diff --git a/nest-cli.json b/nest-cli.json index d0091b82..43e80913 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -1,5 +1,19 @@ { "collection": "@nestjs/schematics", "sourceRoot": "src", - "compilerOptions": {} -} + "compilerOptions": { + "plugins": [ + { + "name": "@nestjs/swagger", + "options": { + "classValidatorShim": false, + "introspectComments": true, + "dtoFileNameSuffix": [ + ".dto.ts", + ".model.ts" + ] + } + } + ] + } +} \ No newline at end of file diff --git a/src/modules/post/post.controller.ts b/src/modules/post/post.controller.ts index ac483311..bc124d00 100644 --- a/src/modules/post/post.controller.ts +++ b/src/modules/post/post.controller.ts @@ -1,6 +1,24 @@ -import { Controller } from '@nestjs/common' +import { Body, Controller, Param, Post, Put } from '@nestjs/common' import { ApiTags } from '@nestjs/swagger' +import { Types } from 'mongoose' +import { MongoIdDto } from '~/shared/dto/id.dto' +import { PostModel } from './post.model' +import { PostService } from './post.service' @Controller('posts') @ApiTags('Post Routes') -export class PostController {} +export class PostController { + constructor(private readonly postService: PostService) {} + + @Post('/') + async create(@Body() body: PostModel) { + const _id = Types.ObjectId() + + return await this.postService.create({ + ...body, + slug: body.slug ?? _id.toHexString(), + }) + } + @Put('/:id') + async update(@Param() params: MongoIdDto) {} +} diff --git a/src/modules/post/post.model.ts b/src/modules/post/post.model.ts index f7bc72b1..a0a6ef27 100644 --- a/src/modules/post/post.model.ts +++ b/src/modules/post/post.model.ts @@ -1,5 +1,13 @@ +import { ApiHideProperty, ApiProperty } from '@nestjs/swagger' import { index, modelOptions, prop, Ref, Severity } from '@typegoose/typegoose' -import { Schema } from 'mongoose' +import { + ArrayUnique, + IsBoolean, + IsMongoId, + IsNotEmpty, + IsOptional, + IsString, +} from 'class-validator' import { CountMixed as Count, WriteBaseModel } from '~/shared/model/base.model' import { CategoryModel as Category } from '../category/category.model' @@ -9,12 +17,19 @@ import { CategoryModel as Category } from '../category/category.model' @modelOptions({ options: { customName: 'Post', allowMixed: Severity.ALLOW } }) export class PostModel extends WriteBaseModel { @prop({ trim: true, unique: true, required: true }) + @IsString() + @IsNotEmpty() slug!: string @prop() + @IsOptional() + @IsNotEmpty() + @IsString() summary?: string @prop({ ref: () => Category, required: true }) + @IsMongoId() + @ApiProperty({ example: '5eb2c62a613a5ab0642f1f7a' }) categoryId: Ref @prop({ @@ -23,22 +38,28 @@ export class PostModel extends WriteBaseModel { localField: 'categoryId', justOne: true, }) + @ApiHideProperty() public category: Ref @prop({ default: false }) + @IsBoolean() + @IsOptional() hide?: boolean @prop({ default: true }) + @IsBoolean() + @IsOptional() copyright?: boolean @prop({ type: String, }) + @IsNotEmpty({ each: true }) + @IsString({ each: true }) + @ArrayUnique() + @IsOptional() tags?: string[] - @prop({ type: Count, default: { read: 0, like: 0 }, _id: false }) + @ApiHideProperty() count?: Count - - @prop({ type: Schema.Types.Mixed }) - options?: Record } diff --git a/src/modules/post/post.service.ts b/src/modules/post/post.service.ts index 59870b14..4ba70fff 100644 --- a/src/modules/post/post.service.ts +++ b/src/modules/post/post.service.ts @@ -1,12 +1,15 @@ import { + BadRequestException, forwardRef, Inject, Injectable, - UnprocessableEntityException, } from '@nestjs/common' import { ReturnModelType } from '@typegoose/typegoose' +import { omit } from 'lodash' import { InjectModel } from 'nestjs-typegoose' import { CannotFindException } from '~/common/exceptions/cant-find.exception' +import { EventTypes } from '~/processors/gateway/events.types' +import { WebEventsGateway } from '~/processors/gateway/web/events.gateway' import { CategoryService } from '../category/category.service' import { PostModel } from './post.model' @@ -17,16 +20,20 @@ export class PostService { private readonly postModel: ReturnModelType, @Inject(forwardRef(() => CategoryService)) private categoryService: CategoryService, + private readonly webgateway: WebEventsGateway, ) {} - async create(post: Partial) { + async create(post: PostModel) { const { categoryId } = post const category = await this.categoryService.findCategoryById( categoryId as any as string, ) if (!category) { - throw new UnprocessableEntityException('分类丢失了 ಠ_ಠ') + throw new BadRequestException('分类丢失了 ಠ_ಠ') + } + if (await this.isAvailableSlug(post.slug)) { + throw new BadRequestException('slug 重复') } const res = await this.postModel.create({ ...post, @@ -35,14 +42,40 @@ export class PostService { modified: null, }) // TODO: clean cache + process.nextTick(async () => { + this.webgateway.broadcast(EventTypes.POST_CREATE, { + ...res.toJSON(), + category, + }) + // TODO + // this.service.RecordImageDimensions(newPostDocument._id) + }) + return res } - async findPostById(id: string) { + async findById(id: string) { const doc = await this.postModel.findById(id).populate('category') if (!doc) { throw new CannotFindException() } return doc } + + async updateById(id: string, data: Partial) { + // 看看 category 改了没 + const { categoryId } = data + if (categoryId) { + } + this.postModel.updateOne( + { + _id: id, + }, + omit(data, ['id', '_id']), + ) + } + + async isAvailableSlug(slug: string) { + return !!(await this.postModel.countDocuments({ slug })) + } } diff --git a/src/shared/model/base.model.ts b/src/shared/model/base.model.ts index cff5319f..1429afde 100644 --- a/src/shared/model/base.model.ts +++ b/src/shared/model/base.model.ts @@ -1,6 +1,8 @@ +import { ApiHideProperty } from '@nestjs/swagger' import { modelOptions, prop } from '@typegoose/typegoose' -import { IsNotEmpty, IsString } from 'class-validator' +import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator' export class BaseModel { + @ApiHideProperty() created?: Date } @@ -26,9 +28,12 @@ class Image { export abstract class BaseCommentIndexModel extends BaseModel { @prop({ default: 0 }) + @ApiHideProperty() commentsIndex?: number @prop({ default: true }) + @IsBoolean() + @IsOptional() allowComment: boolean } @@ -43,9 +48,11 @@ export abstract class WriteBaseModel extends BaseCommentIndexModel { text: string @prop({ type: Image }) + @ApiHideProperty() images?: Image[] @prop({ default: null }) + @ApiHideProperty() modified: Date | null }