From 4c063515ba07d87d2a02ac8b8258816efe16f55e Mon Sep 17 00:00:00 2001 From: liuyi Date: Wed, 21 May 2025 21:20:25 +0800 Subject: [PATCH] add service --- src/modules/content/constants.ts | 1 + src/modules/content/dtos/category.dto.ts | 2 +- src/modules/content/dtos/tag.dto.ts | 2 + .../content/services/category.service.ts | 12 ++-- .../content/services/comment.service.ts | 11 ++- src/modules/content/services/index.ts | 4 ++ src/modules/content/services/post.service.ts | 72 ++++++++++++++++--- src/modules/content/services/tag.service.ts | 4 +- 8 files changed, 89 insertions(+), 19 deletions(-) create mode 100644 src/modules/content/services/index.ts diff --git a/src/modules/content/constants.ts b/src/modules/content/constants.ts index 944044a..4ea2539 100644 --- a/src/modules/content/constants.ts +++ b/src/modules/content/constants.ts @@ -7,5 +7,6 @@ export enum PostOrder { CREATED = 'createdAt', UPDATED = 'updatedAt', PUBLISHED = 'publishedAt', + COMMENTCOUNT = 'commentCount', CUSTOM = 'custom', } diff --git a/src/modules/content/dtos/category.dto.ts b/src/modules/content/dtos/category.dto.ts index 88e6e91..6a7722f 100644 --- a/src/modules/content/dtos/category.dto.ts +++ b/src/modules/content/dtos/category.dto.ts @@ -43,7 +43,7 @@ export class CreateCategoryDto { }) @ValidateIf((value) => value.parent !== null && value.parent) @IsOptional({ always: true }) - @Transform((value) => (value === 'null' ? null : value)) + @Transform(({ value }) => (value === 'null' ? null : value)) parent?: string; @Transform(({ value }) => toNumber(value)) diff --git a/src/modules/content/dtos/tag.dto.ts b/src/modules/content/dtos/tag.dto.ts index 7a56d96..c1468f7 100644 --- a/src/modules/content/dtos/tag.dto.ts +++ b/src/modules/content/dtos/tag.dto.ts @@ -11,6 +11,8 @@ import { } from 'class-validator'; import { toNumber } from 'lodash'; +import { PaginateOptions } from '@/modules/database/types'; + export class QueryTagDto implements PaginateOptions { @Transform(({ value }) => toNumber(value)) @Min(1, { message: 'The current page must be greater than 1.' }) diff --git a/src/modules/content/services/category.service.ts b/src/modules/content/services/category.service.ts index 6ea741c..af86fd9 100644 --- a/src/modules/content/services/category.service.ts +++ b/src/modules/content/services/category.service.ts @@ -2,7 +2,11 @@ import { Injectable } from '@nestjs/common'; import { isNil, omit } from 'lodash'; import { EntityNotFoundError } from 'typeorm'; -import { CreateCategoryDto, QueryCategoryDto } from '@/modules/content/dtos/category.dto'; +import { + CreateCategoryDto, + QueryCategoryDto, + UpdateCategoryDto, +} from '@/modules/content/dtos/category.dto'; import { CategoryEntity } from '@/modules/content/entities/CategoryEntity'; import { CategoryRepository } from '@/modules/content/repositories/category.repository'; import { treePaginate } from '@/modules/database/utils'; @@ -22,7 +26,7 @@ export class CategoryService { } async detail(id: string) { - return this.repository.findOneByOrFail({ where: { id }, relations: ['parent'] }); + return this.repository.findOneOrFail({ where: { id }, relations: ['parent'] }); } async create(data: CreateCategoryDto) { @@ -35,7 +39,7 @@ export class CategoryService { async update(data: UpdateCategoryDto) { await this.repository.update(data.id, omit(data, ['id', 'parent'])); - const item = await this.repository.findOneByOrFail({ + const item = await this.repository.findOneOrFail({ where: { id: data.id }, relations: ['parent'], }); @@ -53,7 +57,7 @@ export class CategoryService { } async delete(id: string) { - const item = await this.repository.findOneByOrFail({ + const item = await this.repository.findOneOrFail({ where: { id }, relations: ['parent', 'children'], }); diff --git a/src/modules/content/services/comment.service.ts b/src/modules/content/services/comment.service.ts index bdd6404..aa16a1a 100644 --- a/src/modules/content/services/comment.service.ts +++ b/src/modules/content/services/comment.service.ts @@ -4,10 +4,17 @@ import { isNil } from 'lodash'; import { EntityNotFoundError, SelectQueryBuilder } from 'typeorm'; -import { QueryCommentTreeDto } from '@/modules/content/dtos/comment.dto'; +import { + CreateCommentDto, + QueryCommentDto, + QueryCommentTreeDto, +} from '@/modules/content/dtos/comment.dto'; import { CommentEntity } from '@/modules/content/entities/comment.entity'; import { treePaginate } from '@/modules/database/utils'; +import { CommentRepository } from '../repositories/comment.repository'; +import { PostRepository } from '../repositories/post.repository'; + @Injectable() export class CommentService { constructor( @@ -44,7 +51,7 @@ export class CommentService { async create(data: CreateCommentDto) { const parent = await this.getParent(undefined, data.parent); - if (!isNil(parent) && parent.post.id !== data.post.id) { + if (!isNil(parent) && parent.post.id !== data.post) { throw new ForbiddenException('Parent comment and child comment must belong same post!'); } const item = await this.repository.save({ diff --git a/src/modules/content/services/index.ts b/src/modules/content/services/index.ts new file mode 100644 index 0000000..d8a7033 --- /dev/null +++ b/src/modules/content/services/index.ts @@ -0,0 +1,4 @@ +export * from './category.service'; +export * from './tag.service'; +export * from './post.service'; +export * from './comment.service'; diff --git a/src/modules/content/services/post.service.ts b/src/modules/content/services/post.service.ts index 8ad0241..0bf6ded 100644 --- a/src/modules/content/services/post.service.ts +++ b/src/modules/content/services/post.service.ts @@ -1,21 +1,35 @@ import { Injectable } from '@nestjs/common'; import { isNil } from '@nestjs/common/utils/shared.utils'; -import { isFunction, omit } from 'lodash'; -import { EntityNotFoundError, IsNull, Not, SelectQueryBuilder } from 'typeorm'; +import { isArray, isFunction, omit } from 'lodash'; +import { EntityNotFoundError, In, IsNull, Not, SelectQueryBuilder } from 'typeorm'; import { PostOrder } from '@/modules/content/constants'; -import { CreatePostDto, UpdatePostDto } from '@/modules/content/dtos/post.dto'; +import { CreatePostDto, QueryPostDto, UpdatePostDto } from '@/modules/content/dtos/post.dto'; import { PostEntity } from '@/modules/content/entities/post.entity'; import { PostRepository } from '@/modules/content/repositories/post.repository'; -import { PaginateOptions, QueryHook } from '@/modules/database/types'; +import { QueryHook } from '@/modules/database/types'; import { paginate } from '@/modules/database/utils'; +import { CategoryRepository } from '../repositories/category.repository'; +import { TagRepository } from '../repositories/tag.repository'; + +import { CategoryService } from './category.service'; + +type FindParams = { + [key in keyof Omit]: QueryPostDto[key]; +}; + @Injectable() export class PostService { - constructor(protected repository: PostRepository) {} + constructor( + protected repository: PostRepository, + protected categoryRepository: CategoryRepository, + protected categoryService: CategoryService, + protected tagRepository: TagRepository, + ) {} - async paginate(options: PaginateOptions, callback?: QueryHook) { + async paginate(options: QueryPostDto, callback?: QueryHook) { const qb = await this.buildListQuery(this.repository.buildBaseQB(), options, callback); return paginate(qb, options); } @@ -36,7 +50,15 @@ export class PostService { if (!isNil(data.publish)) { publishedAt = data.publish ? new Date() : null; } - const item = await this.repository.save({ ...omit(data, ['publish']), publishedAt }); + const createPostDto = { + ...omit(data, ['publish']), + category: isNil(data.category) + ? null + : await this.categoryRepository.findOneOrFail({ where: { id: data.category } }), + tags: isArray(data.tags) ? await this.tagRepository.findBy({ id: In(data.tags) }) : [], + publishedAt, + }; + const item = await this.repository.save(createPostDto); return this.detail(item.id); } @@ -45,8 +67,22 @@ export class PostService { if (!isNil(data.publish)) { publishedAt = data.publish ? new Date() : null; } + const post = await this.detail(data.id); + if (data.category !== undefined) { + post.category = isNil(data.category) + ? null + : await this.categoryRepository.findOneByOrFail({ id: data.category }); + await this.repository.save(post, { reload: true }); + } + if (isArray(data.tags)) { + await this.repository + .createQueryBuilder('post') + .relation(PostEntity, 'tags') + .of(post) + .addAndRemove(data.tags, post.tags ?? []); + } await this.repository.update(data.id, { - ...omit(data, ['id', 'publish']), + ...omit(data, ['id', 'publish', 'tags', 'category']), publishedAt, }); return this.detail(data.id); @@ -59,16 +95,22 @@ export class PostService { protected async buildListQuery( qb: SelectQueryBuilder, - options: RecordAny, + options: FindParams, callback?: QueryHook, ) { - const { orderBy, isPublished } = options; + const { orderBy, isPublished, category, tag } = options; if (typeof isPublished === 'boolean') { isPublished ? qb.where({ publishedAt: Not(IsNull()) }) : qb.where({ publishedAt: IsNull() }); } this.queryOrderBy(qb, orderBy); + if (category) { + await this.queryByCategory(category, qb); + } + if (tag) { + qb.where('tags.id = :id', { id: tag }); + } if (callback) { return callback(qb); } @@ -85,6 +127,8 @@ export class PostService { return qb.orderBy('post.publishedAt', 'DESC'); case PostOrder.CUSTOM: return qb.orderBy('post.customOrder', 'DESC'); + case PostOrder.COMMENTCOUNT: + return qb.orderBy('post.commentCount', 'DESC'); default: return qb .orderBy('post.createdAt', 'DESC') @@ -92,4 +136,12 @@ export class PostService { .addOrderBy('post.publishedAt', 'DESC'); } } + + protected async queryByCategory(id: string, qb: SelectQueryBuilder) { + const root = await this.categoryService.detail(id); + const tree = await this.categoryRepository.findDescendantsTree(root); + const flatDes = await this.categoryRepository.toFlatTrees(tree.children); + const ids = [tree.id, ...flatDes.map((item) => item.id)]; + return qb.where('categoryRepository.id IN (:...ids)', { ids }); + } } diff --git a/src/modules/content/services/tag.service.ts b/src/modules/content/services/tag.service.ts index 925b2d0..c883896 100644 --- a/src/modules/content/services/tag.service.ts +++ b/src/modules/content/services/tag.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { omit } from 'lodash'; -import { CreateTagDto, QueryTagDto } from '@/modules/content/dtos/tag.dto'; +import { CreateTagDto, QueryTagDto, UpdateTagDto } from '@/modules/content/dtos/tag.dto'; import { TagRepository } from '@/modules/content/repositories/tag.repository'; import { paginate } from '@/modules/database/utils'; @@ -31,7 +31,7 @@ export class TagService { } async delete(id: string) { - const item = this.repository.findOneByOrFail({ id }); + const item = await this.repository.findOneByOrFail({ id }); return this.repository.remove(item); } }