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 { CreatePostDto, QueryPostDto, UpdatePostDto } from '@/modules/content/dtos/post.dto';
|
||||||
import { PostService } from '@/modules/content/services/post.service';
|
import { PostService } from '@/modules/content/services/post.service';
|
||||||
|
|
||||||
|
import { DeleteWithTrashDto, RestoreDto } from '../dtos/delete.with.trash.dto';
|
||||||
|
|
||||||
@Controller('posts')
|
@Controller('posts')
|
||||||
export class PostController {
|
export class PostController {
|
||||||
constructor(private postService: PostService) {}
|
constructor(private postService: PostService) {}
|
||||||
@ -51,9 +53,18 @@ export class PostController {
|
|||||||
return this.postService.update(data);
|
return this.postService.update(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete(':id')
|
@Delete()
|
||||||
@SerializeOptions({ groups: ['post-detail'] })
|
@SerializeOptions({ groups: ['post-detail'] })
|
||||||
async delete(@Param('id', new ParseUUIDPipe()) id: string) {
|
async delete(@Body() data: DeleteWithTrashDto) {
|
||||||
return this.postService.delete(id);
|
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 { PostOrder } from '@/modules/content/constants';
|
||||||
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||||
import { toBoolean } from '@/modules/core/helpers';
|
import { toBoolean } from '@/modules/core/helpers';
|
||||||
|
import { SelectTrashMode } from '@/modules/database/constants';
|
||||||
import { IsDataExist } from '@/modules/database/constraints/data.exist.constraint';
|
import { IsDataExist } from '@/modules/database/constraints/data.exist.constraint';
|
||||||
import { PaginateOptions } from '@/modules/database/types';
|
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(',')}`,
|
message: `The sorting rule must be one of ${Object.values(PostOrder).join(',')}`,
|
||||||
})
|
})
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
orderBy: PostOrder;
|
orderBy?: PostOrder;
|
||||||
|
|
||||||
@Transform(({ value }) => toNumber(value))
|
@Transform(({ value }) => toNumber(value))
|
||||||
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
@Min(1, { always: true, message: 'The current page must be greater than 1.' })
|
||||||
@ -52,6 +53,10 @@ export class QueryPostDto implements PaginateOptions {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
limit = 10;
|
limit = 10;
|
||||||
|
|
||||||
|
@IsEnum(SelectTrashMode)
|
||||||
|
@IsOptional()
|
||||||
|
trashed?: SelectTrashMode;
|
||||||
|
|
||||||
@IsDataExist(CategoryEntity, { always: true, message: 'The category does not exist' })
|
@IsDataExist(CategoryEntity, { always: true, message: 'The category does not exist' })
|
||||||
@IsUUID(undefined, { message: 'The ID format is incorrect' })
|
@IsUUID(undefined, { message: 'The ID format is incorrect' })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
BaseEntity,
|
BaseEntity,
|
||||||
Column,
|
Column,
|
||||||
CreateDateColumn,
|
CreateDateColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
Entity,
|
Entity,
|
||||||
JoinTable,
|
JoinTable,
|
||||||
ManyToMany,
|
ManyToMany,
|
||||||
@ -64,6 +65,11 @@ export class PostEntity extends BaseEntity {
|
|||||||
@UpdateDateColumn({ comment: '更新时间', nullable: true })
|
@UpdateDateColumn({ comment: '更新时间', nullable: true })
|
||||||
updatedAt?: Date;
|
updatedAt?: Date;
|
||||||
|
|
||||||
|
@Expose()
|
||||||
|
@Type(() => Date)
|
||||||
|
@DeleteDateColumn({ comment: '删除时间' })
|
||||||
|
deleteAt: Date;
|
||||||
|
|
||||||
@Expose()
|
@Expose()
|
||||||
commentCount: number;
|
commentCount: number;
|
||||||
|
|
||||||
@ -76,7 +82,7 @@ export class PostEntity extends BaseEntity {
|
|||||||
|
|
||||||
@Expose()
|
@Expose()
|
||||||
@Type(() => TagEntity)
|
@Type(() => TagEntity)
|
||||||
@ManyToMany(() => TagEntity, (tag) => tag.posts, { cascade: true })
|
@ManyToMany(() => TagEntity, (tag) => tag.posts, { cascade: ['insert', 'update', 'remove'] })
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
tags: Relation<TagEntity>[];
|
tags: Relation<TagEntity>[];
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import { CreatePostDto, QueryPostDto, UpdatePostDto } from '@/modules/content/dt
|
|||||||
import { PostEntity } from '@/modules/content/entities/post.entity';
|
import { PostEntity } from '@/modules/content/entities/post.entity';
|
||||||
import { CategoryRepository } from '@/modules/content/repositories';
|
import { CategoryRepository } from '@/modules/content/repositories';
|
||||||
import { PostRepository } from '@/modules/content/repositories/post.repository';
|
import { PostRepository } from '@/modules/content/repositories/post.repository';
|
||||||
|
import { SelectTrashMode } from '@/modules/database/constants';
|
||||||
import { QueryHook } from '@/modules/database/types';
|
import { QueryHook } from '@/modules/database/types';
|
||||||
import { paginate } from '@/modules/database/utils';
|
import { paginate } from '@/modules/database/utils';
|
||||||
|
|
||||||
@ -88,9 +89,42 @@ export class PostService {
|
|||||||
return this.detail(data.id);
|
return this.detail(data.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string) {
|
async delete(ids: string[], trash?: boolean) {
|
||||||
const item = await this.repository.findOneByOrFail({ id });
|
const items = await this.repository
|
||||||
return this.repository.remove(item);
|
.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(
|
protected async buildListQuery(
|
||||||
@ -98,12 +132,18 @@ export class PostService {
|
|||||||
options: FindParams,
|
options: FindParams,
|
||||||
callback?: QueryHook<PostEntity>,
|
callback?: QueryHook<PostEntity>,
|
||||||
) {
|
) {
|
||||||
const { orderBy, isPublished, category, tag } = options;
|
const { orderBy, isPublished, category, tag, trashed } = 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() });
|
||||||
}
|
}
|
||||||
|
if (trashed === SelectTrashMode.ALL || trashed === SelectTrashMode.ONLY) {
|
||||||
|
qb.withDeleted();
|
||||||
|
if (trashed === SelectTrashMode.ONLY) {
|
||||||
|
qb.where('post.deletedAt is not null');
|
||||||
|
}
|
||||||
|
}
|
||||||
this.queryOrderBy(qb, orderBy);
|
this.queryOrderBy(qb, orderBy);
|
||||||
if (category) {
|
if (category) {
|
||||||
await this.queryByCategory(category, qb);
|
await this.queryByCategory(category, qb);
|
||||||
|
@ -1 +1,10 @@
|
|||||||
export const CUSTOM_REPOSITORY_METADATA = 'CUSTOM_REPOSITORY_METADATA';
|
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