add service
This commit is contained in:
parent
8ab109ce26
commit
4c063515ba
@ -7,5 +7,6 @@ export enum PostOrder {
|
|||||||
CREATED = 'createdAt',
|
CREATED = 'createdAt',
|
||||||
UPDATED = 'updatedAt',
|
UPDATED = 'updatedAt',
|
||||||
PUBLISHED = 'publishedAt',
|
PUBLISHED = 'publishedAt',
|
||||||
|
COMMENTCOUNT = 'commentCount',
|
||||||
CUSTOM = 'custom',
|
CUSTOM = 'custom',
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ export class CreateCategoryDto {
|
|||||||
})
|
})
|
||||||
@ValidateIf((value) => value.parent !== null && value.parent)
|
@ValidateIf((value) => value.parent !== null && value.parent)
|
||||||
@IsOptional({ always: true })
|
@IsOptional({ always: true })
|
||||||
@Transform((value) => (value === 'null' ? null : value))
|
@Transform(({ value }) => (value === 'null' ? null : value))
|
||||||
parent?: string;
|
parent?: string;
|
||||||
|
|
||||||
@Transform(({ value }) => toNumber(value))
|
@Transform(({ value }) => toNumber(value))
|
||||||
|
@ -11,6 +11,8 @@ import {
|
|||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
import { toNumber } from 'lodash';
|
import { toNumber } from 'lodash';
|
||||||
|
|
||||||
|
import { PaginateOptions } from '@/modules/database/types';
|
||||||
|
|
||||||
export class QueryTagDto implements PaginateOptions {
|
export class QueryTagDto implements PaginateOptions {
|
||||||
@Transform(({ value }) => toNumber(value))
|
@Transform(({ value }) => toNumber(value))
|
||||||
@Min(1, { message: 'The current page must be greater than 1.' })
|
@Min(1, { message: 'The current page must be greater than 1.' })
|
||||||
|
@ -2,7 +2,11 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { isNil, omit } from 'lodash';
|
import { isNil, omit } from 'lodash';
|
||||||
import { EntityNotFoundError } from 'typeorm';
|
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 { CategoryEntity } from '@/modules/content/entities/CategoryEntity';
|
||||||
import { CategoryRepository } from '@/modules/content/repositories/category.repository';
|
import { CategoryRepository } from '@/modules/content/repositories/category.repository';
|
||||||
import { treePaginate } from '@/modules/database/utils';
|
import { treePaginate } from '@/modules/database/utils';
|
||||||
@ -22,7 +26,7 @@ export class CategoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async detail(id: string) {
|
async detail(id: string) {
|
||||||
return this.repository.findOneByOrFail({ where: { id }, relations: ['parent'] });
|
return this.repository.findOneOrFail({ where: { id }, relations: ['parent'] });
|
||||||
}
|
}
|
||||||
|
|
||||||
async create(data: CreateCategoryDto) {
|
async create(data: CreateCategoryDto) {
|
||||||
@ -35,7 +39,7 @@ export class CategoryService {
|
|||||||
|
|
||||||
async update(data: UpdateCategoryDto) {
|
async update(data: UpdateCategoryDto) {
|
||||||
await this.repository.update(data.id, omit(data, ['id', 'parent']));
|
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 },
|
where: { id: data.id },
|
||||||
relations: ['parent'],
|
relations: ['parent'],
|
||||||
});
|
});
|
||||||
@ -53,7 +57,7 @@ export class CategoryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string) {
|
async delete(id: string) {
|
||||||
const item = await this.repository.findOneByOrFail({
|
const item = await this.repository.findOneOrFail({
|
||||||
where: { id },
|
where: { id },
|
||||||
relations: ['parent', 'children'],
|
relations: ['parent', 'children'],
|
||||||
});
|
});
|
||||||
|
@ -4,10 +4,17 @@ import { isNil } from 'lodash';
|
|||||||
|
|
||||||
import { EntityNotFoundError, SelectQueryBuilder } from 'typeorm';
|
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 { CommentEntity } from '@/modules/content/entities/comment.entity';
|
||||||
import { treePaginate } from '@/modules/database/utils';
|
import { treePaginate } from '@/modules/database/utils';
|
||||||
|
|
||||||
|
import { CommentRepository } from '../repositories/comment.repository';
|
||||||
|
import { PostRepository } from '../repositories/post.repository';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CommentService {
|
export class CommentService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -44,7 +51,7 @@ export class CommentService {
|
|||||||
|
|
||||||
async create(data: CreateCommentDto) {
|
async create(data: CreateCommentDto) {
|
||||||
const parent = await this.getParent(undefined, data.parent);
|
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!');
|
throw new ForbiddenException('Parent comment and child comment must belong same post!');
|
||||||
}
|
}
|
||||||
const item = await this.repository.save({
|
const item = await this.repository.save({
|
||||||
|
4
src/modules/content/services/index.ts
Normal file
4
src/modules/content/services/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export * from './category.service';
|
||||||
|
export * from './tag.service';
|
||||||
|
export * from './post.service';
|
||||||
|
export * from './comment.service';
|
@ -1,21 +1,35 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { isNil } from '@nestjs/common/utils/shared.utils';
|
import { isNil } from '@nestjs/common/utils/shared.utils';
|
||||||
|
|
||||||
import { isFunction, omit } from 'lodash';
|
import { isArray, isFunction, omit } from 'lodash';
|
||||||
import { EntityNotFoundError, IsNull, Not, SelectQueryBuilder } from 'typeorm';
|
import { EntityNotFoundError, In, IsNull, Not, SelectQueryBuilder } from 'typeorm';
|
||||||
|
|
||||||
import { PostOrder } from '@/modules/content/constants';
|
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 { PostEntity } from '@/modules/content/entities/post.entity';
|
||||||
import { PostRepository } from '@/modules/content/repositories/post.repository';
|
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 { 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, 'limit' | 'page'>]: QueryPostDto[key];
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PostService {
|
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<PostEntity>) {
|
async paginate(options: QueryPostDto, callback?: QueryHook<PostEntity>) {
|
||||||
const qb = await this.buildListQuery(this.repository.buildBaseQB(), options, callback);
|
const qb = await this.buildListQuery(this.repository.buildBaseQB(), options, callback);
|
||||||
return paginate(qb, options);
|
return paginate(qb, options);
|
||||||
}
|
}
|
||||||
@ -36,7 +50,15 @@ export class PostService {
|
|||||||
if (!isNil(data.publish)) {
|
if (!isNil(data.publish)) {
|
||||||
publishedAt = data.publish ? new Date() : null;
|
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);
|
return this.detail(item.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,8 +67,22 @@ export class PostService {
|
|||||||
if (!isNil(data.publish)) {
|
if (!isNil(data.publish)) {
|
||||||
publishedAt = data.publish ? new Date() : null;
|
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, {
|
await this.repository.update(data.id, {
|
||||||
...omit(data, ['id', 'publish']),
|
...omit(data, ['id', 'publish', 'tags', 'category']),
|
||||||
publishedAt,
|
publishedAt,
|
||||||
});
|
});
|
||||||
return this.detail(data.id);
|
return this.detail(data.id);
|
||||||
@ -59,16 +95,22 @@ export class PostService {
|
|||||||
|
|
||||||
protected async buildListQuery(
|
protected async buildListQuery(
|
||||||
qb: SelectQueryBuilder<PostEntity>,
|
qb: SelectQueryBuilder<PostEntity>,
|
||||||
options: RecordAny,
|
options: FindParams,
|
||||||
callback?: QueryHook<PostEntity>,
|
callback?: QueryHook<PostEntity>,
|
||||||
) {
|
) {
|
||||||
const { orderBy, isPublished } = options;
|
const { orderBy, isPublished, category, tag } = options;
|
||||||
if (typeof isPublished === 'boolean') {
|
if (typeof isPublished === 'boolean') {
|
||||||
isPublished
|
isPublished
|
||||||
? qb.where({ publishedAt: Not(IsNull()) })
|
? qb.where({ publishedAt: Not(IsNull()) })
|
||||||
: qb.where({ publishedAt: IsNull() });
|
: qb.where({ publishedAt: IsNull() });
|
||||||
}
|
}
|
||||||
this.queryOrderBy(qb, orderBy);
|
this.queryOrderBy(qb, orderBy);
|
||||||
|
if (category) {
|
||||||
|
await this.queryByCategory(category, qb);
|
||||||
|
}
|
||||||
|
if (tag) {
|
||||||
|
qb.where('tags.id = :id', { id: tag });
|
||||||
|
}
|
||||||
if (callback) {
|
if (callback) {
|
||||||
return callback(qb);
|
return callback(qb);
|
||||||
}
|
}
|
||||||
@ -85,6 +127,8 @@ export class PostService {
|
|||||||
return qb.orderBy('post.publishedAt', 'DESC');
|
return qb.orderBy('post.publishedAt', 'DESC');
|
||||||
case PostOrder.CUSTOM:
|
case PostOrder.CUSTOM:
|
||||||
return qb.orderBy('post.customOrder', 'DESC');
|
return qb.orderBy('post.customOrder', 'DESC');
|
||||||
|
case PostOrder.COMMENTCOUNT:
|
||||||
|
return qb.orderBy('post.commentCount', 'DESC');
|
||||||
default:
|
default:
|
||||||
return qb
|
return qb
|
||||||
.orderBy('post.createdAt', 'DESC')
|
.orderBy('post.createdAt', 'DESC')
|
||||||
@ -92,4 +136,12 @@ export class PostService {
|
|||||||
.addOrderBy('post.publishedAt', 'DESC');
|
.addOrderBy('post.publishedAt', 'DESC');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async queryByCategory(id: string, qb: SelectQueryBuilder<PostEntity>) {
|
||||||
|
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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { omit } from 'lodash';
|
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 { TagRepository } from '@/modules/content/repositories/tag.repository';
|
||||||
import { paginate } from '@/modules/database/utils';
|
import { paginate } from '@/modules/database/utils';
|
||||||
|
|
||||||
@ -31,7 +31,7 @@ export class TagService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string) {
|
async delete(id: string) {
|
||||||
const item = this.repository.findOneByOrFail({ id });
|
const item = await this.repository.findOneByOrFail({ id });
|
||||||
return this.repository.remove(item);
|
return this.repository.remove(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user