From 8ab109ce262ba9eb4c22bc63ef9fd04dbad82c0d Mon Sep 17 00:00:00 2001 From: liuyi Date: Wed, 21 May 2025 18:50:11 +0800 Subject: [PATCH] add service --- .../content/services/comment.service.ts | 86 +++++++++++++++++++ src/modules/content/services/tag.service.ts | 2 + typings/global.d.ts | 1 + 3 files changed, 89 insertions(+) create mode 100644 src/modules/content/services/comment.service.ts diff --git a/src/modules/content/services/comment.service.ts b/src/modules/content/services/comment.service.ts new file mode 100644 index 0000000..bdd6404 --- /dev/null +++ b/src/modules/content/services/comment.service.ts @@ -0,0 +1,86 @@ +import { ForbiddenException, Injectable } from '@nestjs/common'; + +import { isNil } from 'lodash'; + +import { EntityNotFoundError, SelectQueryBuilder } from 'typeorm'; + +import { QueryCommentTreeDto } from '@/modules/content/dtos/comment.dto'; +import { CommentEntity } from '@/modules/content/entities/comment.entity'; +import { treePaginate } from '@/modules/database/utils'; + +@Injectable() +export class CommentService { + constructor( + protected repository: CommentRepository, + protected postRepository: PostRepository, + ) {} + + async findTrees(options: QueryCommentTreeDto = {}) { + return this.repository.findTrees({ + addQuery: (qb) => { + return isNil(options.post) ? qb : qb.where('post.id = :id', { id: options.post }); + }, + }); + } + + async paginate(options: QueryCommentDto) { + const { post, ...query } = options; + const addQuery = (qb: SelectQueryBuilder) => { + const condition: RecordString = {}; + if (!isNil(post)) { + condition.post = post; + } + return Object.keys(condition).length > 0 ? qb.andWhere(condition) : qb; + }; + const data = await this.repository.findRoots({ addQuery }); + let comments: CommentEntity[] = []; + for (let i = 0; i < data.length; i++) { + const comment = data[i]; + comments.push(await this.repository.findDescendantsTree(comment, { addQuery })); + } + comments = await this.repository.toFlatTrees(comments); + return treePaginate(query, comments); + } + + async create(data: CreateCommentDto) { + const parent = await this.getParent(undefined, data.parent); + if (!isNil(parent) && parent.post.id !== data.post.id) { + throw new ForbiddenException('Parent comment and child comment must belong same post!'); + } + const item = await this.repository.save({ + ...data, + parent, + post: await this.getPost(data.post), + }); + return this.repository.findOneOrFail({ where: { id: item.id } }); + } + + async delete(id: string) { + const comment = await this.repository.findOneOrFail({ where: { id: id ?? null } }); + return this.repository.remove(comment); + } + + protected async getPost(id: string) { + return isNil(id) ? null : this.postRepository.findOneOrFail({ where: { id } }); + } + + protected async getParent(current?: string, id?: string) { + if (current === id) { + return undefined; + } + let parent: CommentEntity | undefined; + if (id !== undefined) { + if (id === null) { + return null; + } + parent = await this.repository.findOne({ + where: { id }, + relations: ['parent', 'children'], + }); + if (!parent) { + throw new EntityNotFoundError(CommentEntity, `Parent Comment ${id} not found`); + } + } + return parent; + } +} diff --git a/src/modules/content/services/tag.service.ts b/src/modules/content/services/tag.service.ts index 97458d8..925b2d0 100644 --- a/src/modules/content/services/tag.service.ts +++ b/src/modules/content/services/tag.service.ts @@ -1,9 +1,11 @@ +import { Injectable } from '@nestjs/common'; import { omit } from 'lodash'; import { CreateTagDto, QueryTagDto } from '@/modules/content/dtos/tag.dto'; import { TagRepository } from '@/modules/content/repositories/tag.repository'; import { paginate } from '@/modules/database/utils'; +@Injectable() export class TagService { constructor(protected repository: TagRepository) {} diff --git a/typings/global.d.ts b/typings/global.d.ts index fa33ba1..c40c909 100644 --- a/typings/global.d.ts +++ b/typings/global.d.ts @@ -1,5 +1,6 @@ declare type RecordAny = Record; declare type RecordNever = Record; +declare type RecordString = Record; declare type RecordAnyOrNever = RecordAny | RecordNever; declare type BaseType = boolean | number | string | undefined | null;