post and soft delete
This commit is contained in:
parent
427997f1cb
commit
b7bb509b70
@ -14,6 +14,8 @@ import {
|
||||
import { CreatePostDto, QueryPostDto, UpdatePostDto } from '@/modules/content/dtos/post.dto';
|
||||
import { PostService } from '@/modules/content/services/post.service';
|
||||
|
||||
import { DeleteWithTrashDto, RestoreDto } from '../dtos/delete.with.trash.dto';
|
||||
|
||||
@Controller('posts')
|
||||
export class PostController {
|
||||
constructor(private postService: PostService) {}
|
||||
@ -51,9 +53,18 @@ export class PostController {
|
||||
return this.postService.update(data);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Delete()
|
||||
@SerializeOptions({ groups: ['post-detail'] })
|
||||
async delete(@Param('id', new ParseUUIDPipe()) id: string) {
|
||||
return this.postService.delete(id);
|
||||
async delete(@Body() data: DeleteWithTrashDto) {
|
||||
return this.postService.delete(data.ids, data.trash);
|
||||
}
|
||||
|
||||
@Patch('restore')
|
||||
@SerializeOptions({ groups: ['post-detail'] })
|
||||
async restore(
|
||||
@Body()
|
||||
data: RestoreDto,
|
||||
) {
|
||||
return this.postService.restore(data.ids);
|
||||
}
|
||||
}
|
||||
|
10
src/modules/content/dtos/delete.dto.ts
Normal file
10
src/modules/content/dtos/delete.dto.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { IsUUID, IsDefined } from 'class-validator';
|
||||
|
||||
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||
|
||||
@DtoValidation()
|
||||
export class DeleteDto {
|
||||
@IsUUID(undefined, { each: true, always: true, message: 'The ID format is incorrect' })
|
||||
@IsDefined({ each: true, message: 'The ID must be specified' })
|
||||
ids: string[];
|
||||
}
|
19
src/modules/content/dtos/delete.with.trash.dto.ts
Normal file
19
src/modules/content/dtos/delete.with.trash.dto.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Transform } from 'class-transformer';
|
||||
|
||||
import { IsBoolean, IsDefined, IsOptional, IsUUID } from 'class-validator';
|
||||
import { toBoolean } from 'validator';
|
||||
|
||||
import { DeleteDto } from './delete.dto';
|
||||
|
||||
export class DeleteWithTrashDto extends DeleteDto {
|
||||
@Transform(({ value }) => toBoolean(value))
|
||||
@IsBoolean()
|
||||
@IsOptional()
|
||||
trash?: boolean;
|
||||
}
|
||||
|
||||
export class RestoreDto {
|
||||
@IsUUID(undefined, { each: true, always: true, message: 'The ID format is incorrect' })
|
||||
@IsDefined({ each: true, message: 'The ID must be specified' })
|
||||
ids: string[];
|
||||
}
|
@ -19,6 +19,7 @@ import { isNil, toNumber } from 'lodash';
|
||||
import { PostOrder } from '@/modules/content/constants';
|
||||
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||
import { toBoolean } from '@/modules/core/helpers';
|
||||
import { SelectTrashMode } from '@/modules/database/constants';
|
||||
import { IsDataExist } from '@/modules/database/constraints/data.exist.constraint';
|
||||
import { PaginateOptions } from '@/modules/database/types';
|
||||
|
||||
@ -35,7 +36,7 @@ export class QueryPostDto implements PaginateOptions {
|
||||
message: `The sorting rule must be one of ${Object.values(PostOrder).join(',')}`,
|
||||
})
|
||||
@IsOptional()
|
||||
orderBy: PostOrder;
|
||||
orderBy?: PostOrder;
|
||||
|
||||
@Transform(({ value }) => toNumber(value))
|
||||
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||
@ -52,6 +53,10 @@ export class QueryPostDto implements PaginateOptions {
|
||||
@IsOptional()
|
||||
limit = 10;
|
||||
|
||||
@IsEnum(SelectTrashMode)
|
||||
@IsOptional()
|
||||
trashed?: SelectTrashMode;
|
||||
|
||||
@IsDataExist(CategoryEntity, { always: true, message: 'The category does not exist' })
|
||||
@IsUUID(undefined, { message: 'The ID format is incorrect' })
|
||||
@IsOptional()
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
BaseEntity,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
DeleteDateColumn,
|
||||
Entity,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
@ -64,6 +65,11 @@ export class PostEntity extends BaseEntity {
|
||||
@UpdateDateColumn({ comment: '更新时间', nullable: true })
|
||||
updatedAt?: Date;
|
||||
|
||||
@Expose()
|
||||
@Type(() => Date)
|
||||
@DeleteDateColumn({ comment: '删除时间' })
|
||||
deleteAt: Date;
|
||||
|
||||
@Expose()
|
||||
commentCount: number;
|
||||
|
||||
@ -76,7 +82,7 @@ export class PostEntity extends BaseEntity {
|
||||
|
||||
@Expose()
|
||||
@Type(() => TagEntity)
|
||||
@ManyToMany(() => TagEntity, (tag) => tag.posts, { cascade: true })
|
||||
@ManyToMany(() => TagEntity, (tag) => tag.posts, { cascade: ['insert', 'update', 'remove'] })
|
||||
@JoinTable()
|
||||
tags: Relation<TagEntity>[];
|
||||
|
||||
|
@ -9,6 +9,7 @@ import { CreatePostDto, QueryPostDto, UpdatePostDto } from '@/modules/content/dt
|
||||
import { PostEntity } from '@/modules/content/entities/post.entity';
|
||||
import { CategoryRepository } from '@/modules/content/repositories';
|
||||
import { PostRepository } from '@/modules/content/repositories/post.repository';
|
||||
import { SelectTrashMode } from '@/modules/database/constants';
|
||||
import { QueryHook } from '@/modules/database/types';
|
||||
import { paginate } from '@/modules/database/utils';
|
||||
|
||||
@ -88,9 +89,42 @@ export class PostService {
|
||||
return this.detail(data.id);
|
||||
}
|
||||
|
||||
async delete(id: string) {
|
||||
const item = await this.repository.findOneByOrFail({ id });
|
||||
return this.repository.remove(item);
|
||||
async delete(ids: string[], trash?: boolean) {
|
||||
const items = await this.repository
|
||||
.buildBaseQB()
|
||||
.where('post.id IN (:...ids)', { ids })
|
||||
.withDeleted()
|
||||
.getMany();
|
||||
let result: PostEntity[] = [];
|
||||
if (trash) {
|
||||
const directs = items.filter((item) => !isNil(item.deleteAt));
|
||||
const softs = items.filter((item) => isNil(item.deleteAt));
|
||||
result = [
|
||||
...(await this.repository.remove(directs)),
|
||||
...(await this.repository.softRemove(softs)),
|
||||
];
|
||||
} else {
|
||||
result = await this.repository.remove(items);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
async restore(ids: string[]) {
|
||||
const items = await this.repository
|
||||
.buildBaseQB()
|
||||
.where('post.id IN (:...ids)', { ids })
|
||||
.withDeleted()
|
||||
.getMany();
|
||||
const trasheds = items.filter((item) => !isNil(item.deleteAt));
|
||||
const trashedIds = trasheds.map((item) => item.id);
|
||||
if (trashedIds.length < 1) {
|
||||
return [];
|
||||
}
|
||||
await this.repository.restore(trashedIds);
|
||||
const qb = await this.buildListQuery(this.repository.buildBaseQB(), {}, async (qbuilder) =>
|
||||
qbuilder.andWhereInIds(trashedIds),
|
||||
);
|
||||
return qb.getMany();
|
||||
}
|
||||
|
||||
protected async buildListQuery(
|
||||
@ -98,12 +132,18 @@ export class PostService {
|
||||
options: FindParams,
|
||||
callback?: QueryHook<PostEntity>,
|
||||
) {
|
||||
const { orderBy, isPublished, category, tag } = options;
|
||||
const { orderBy, isPublished, category, tag, trashed } = options;
|
||||
if (typeof isPublished === 'boolean') {
|
||||
isPublished
|
||||
? qb.where({ publishedAt: Not(IsNull()) })
|
||||
: qb.where({ publishedAt: IsNull() });
|
||||
}
|
||||
if (trashed === SelectTrashMode.ALL || trashed === SelectTrashMode.ONLY) {
|
||||
qb.withDeleted();
|
||||
if (trashed === SelectTrashMode.ONLY) {
|
||||
qb.where('post.deletedAt is not null');
|
||||
}
|
||||
}
|
||||
this.queryOrderBy(qb, orderBy);
|
||||
if (category) {
|
||||
await this.queryByCategory(category, qb);
|
||||
|
@ -1 +1,10 @@
|
||||
export const CUSTOM_REPOSITORY_METADATA = 'CUSTOM_REPOSITORY_METADATA';
|
||||
|
||||
export enum SelectTrashMode {
|
||||
// ALL: 包含已软删除和未软删除的数据(同时查询正常数据和回收站中的数据)
|
||||
ALL = 'all',
|
||||
// ONLY: 只包含软删除的数据 (只查询回收站中的数据)
|
||||
ONLY = 'only',
|
||||
// NONE: 只包含未软删除的数据 (只查询正常数据)
|
||||
NONE = 'none',
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user