modify bugs
This commit is contained in:
parent
0f8abaec7f
commit
d2692e6a11
@ -8,10 +8,7 @@ export const database = createDBConfig((configure) => ({
|
|||||||
common: {
|
common: {
|
||||||
synchronize: true,
|
synchronize: true,
|
||||||
// 启用详细日志以便调试 SQL 错误
|
// 启用详细日志以便调试 SQL 错误
|
||||||
logging:
|
logging: configure.env.get('NODE_ENV') === 'development' ? ['error', 'query'] : ['error'],
|
||||||
configure.env.get('NODE_ENV') === 'development'
|
|
||||||
? ['query', 'error', 'schema', 'warn', 'info', 'log']
|
|
||||||
: ['error'],
|
|
||||||
// 启用最大日志记录
|
// 启用最大日志记录
|
||||||
maxQueryExecutionTime: 1000,
|
maxQueryExecutionTime: 1000,
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createMeiliConfig } from '../modules/meilisearch/config';
|
import { createMeiliConfig } from '@/modules/meilisearch/config';
|
||||||
|
|
||||||
export const meili = createMeiliConfig((configure) => [
|
export const meili = createMeiliConfig((configure) => [
|
||||||
{
|
{
|
||||||
|
@ -44,8 +44,8 @@ export class ContentModule {
|
|||||||
categoryRepository: repositories.CategoryRepository,
|
categoryRepository: repositories.CategoryRepository,
|
||||||
tagRepository: repositories.TagRepository,
|
tagRepository: repositories.TagRepository,
|
||||||
categoryService: services.CategoryService,
|
categoryService: services.CategoryService,
|
||||||
searchService: SearchService,
|
|
||||||
userRepository: UserRepository,
|
userRepository: UserRepository,
|
||||||
|
searchService: SearchService,
|
||||||
) {
|
) {
|
||||||
return new PostService(
|
return new PostService(
|
||||||
postRepository,
|
postRepository,
|
||||||
|
@ -104,7 +104,7 @@ export class PostController {
|
|||||||
@SerializeOptions({ groups: ['post-detail'] })
|
@SerializeOptions({ groups: ['post-detail'] })
|
||||||
async show(@Param('id', new ParseUUIDPipe()) id: string) {
|
async show(@Param('id', new ParseUUIDPipe()) id: string) {
|
||||||
return this.postService.detail(id, async (qb) =>
|
return this.postService.detail(id, async (qb) =>
|
||||||
qb.andWhere({ publishedAt: Not(IsNull()), deletedAt: Not(IsNull()) }),
|
qb.andWhere({ publishedAt: Not(IsNull()), deletedAt: IsNull() }),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IsDefined, IsUUID } from 'class-validator';
|
import { ArrayMinSize, IsArray, IsDefined, IsUUID } from 'class-validator';
|
||||||
|
|
||||||
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator';
|
||||||
|
|
||||||
@ -6,5 +6,7 @@ import { DtoValidation } from '@/modules/core/decorator/dto.validation.decorator
|
|||||||
export class DeleteDto {
|
export class DeleteDto {
|
||||||
@IsUUID(undefined, { each: true, always: true, message: 'The ID format is incorrect' })
|
@IsUUID(undefined, { each: true, always: true, message: 'The ID format is incorrect' })
|
||||||
@IsDefined({ each: true, message: 'The ID must be specified' })
|
@IsDefined({ each: true, message: 'The ID must be specified' })
|
||||||
|
@IsArray()
|
||||||
|
@ArrayMinSize(1)
|
||||||
ids: string[];
|
ids: string[];
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,6 @@ export class PostEntity extends BaseEntity {
|
|||||||
@Column({ comment: '文章描述', nullable: true })
|
@Column({ comment: '文章描述', nullable: true })
|
||||||
summary?: string;
|
summary?: string;
|
||||||
|
|
||||||
@Expose()
|
|
||||||
@Expose()
|
@Expose()
|
||||||
@Column({ comment: '关键字', type: 'simple-array', nullable: true })
|
@Column({ comment: '关键字', type: 'simple-array', nullable: true })
|
||||||
keywords?: string[];
|
keywords?: string[];
|
||||||
@ -69,7 +68,7 @@ export class PostEntity extends BaseEntity {
|
|||||||
@Expose()
|
@Expose()
|
||||||
@Type(() => Date)
|
@Type(() => Date)
|
||||||
@DeleteDateColumn({ comment: '删除时间' })
|
@DeleteDateColumn({ comment: '删除时间' })
|
||||||
deleteAt: Date;
|
deletedAt: Date;
|
||||||
|
|
||||||
@Expose()
|
@Expose()
|
||||||
commentCount: number;
|
commentCount: number;
|
||||||
|
@ -3,7 +3,9 @@ import { ModuleRef } from '@nestjs/core';
|
|||||||
|
|
||||||
import { CategoryEntity, CommentEntity, PostEntity, TagEntity } from '@/modules/content/entities';
|
import { CategoryEntity, CommentEntity, PostEntity, TagEntity } from '@/modules/content/entities';
|
||||||
import { PermissionAction, SystemRoles } from '@/modules/rbac/constants';
|
import { PermissionAction, SystemRoles } from '@/modules/rbac/constants';
|
||||||
|
import { PermissionEntity, RoleEntity } from '@/modules/rbac/entities';
|
||||||
import { RbacResolver } from '@/modules/rbac/rbac.resolver';
|
import { RbacResolver } from '@/modules/rbac/rbac.resolver';
|
||||||
|
import { UserEntity } from '@/modules/user/entities';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ContentRbac implements OnModuleInit {
|
export class ContentRbac implements OnModuleInit {
|
||||||
@ -73,6 +75,27 @@ export class ContentRbac implements OnModuleInit {
|
|||||||
subject: CommentEntity,
|
subject: CommentEntity,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'permission.manage',
|
||||||
|
rule: {
|
||||||
|
action: PermissionAction.MANAGE,
|
||||||
|
subject: PermissionEntity,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'role.manage',
|
||||||
|
rule: {
|
||||||
|
action: PermissionAction.MANAGE,
|
||||||
|
subject: RoleEntity,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'user.manage',
|
||||||
|
rule: {
|
||||||
|
action: PermissionAction.MANAGE,
|
||||||
|
subject: UserEntity,
|
||||||
|
},
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
resolver.addRoles([
|
resolver.addRoles([
|
||||||
|
@ -11,12 +11,13 @@ export class PostRepository extends BaseRepository<PostEntity> {
|
|||||||
return this.createQueryBuilder(this.qbName)
|
return this.createQueryBuilder(this.qbName)
|
||||||
.leftJoinAndSelect(`${this.qbName}.category`, 'category')
|
.leftJoinAndSelect(`${this.qbName}.category`, 'category')
|
||||||
.leftJoinAndSelect(`${this.qbName}.tags`, 'tags')
|
.leftJoinAndSelect(`${this.qbName}.tags`, 'tags')
|
||||||
|
.leftJoinAndSelect(`${this.qbName}.author`, 'author')
|
||||||
.addSelect((query) => {
|
.addSelect((query) => {
|
||||||
return query
|
return query
|
||||||
.select('COUNT(c.id)', 'count')
|
.select('COUNT(c.id)', 'count')
|
||||||
.from(CommentEntity, 'c')
|
.from(CommentEntity, 'c')
|
||||||
.where(`c.post.id = ${this.qbName}.id`);
|
.where(`c.post.id = ${this.qbName}.id`);
|
||||||
}, 'commentCount')
|
}, 'commentCount')
|
||||||
.loadRelationCountAndMap(`${this.qbName}.commentCOunt`, `${this.qbName}.comments`);
|
.loadRelationCountAndMap(`${this.qbName}.commentCount`, `${this.qbName}.comments`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -147,8 +147,8 @@ export class PostService extends BaseService<PostEntity, PostRepository, FindPar
|
|||||||
.getMany();
|
.getMany();
|
||||||
let result: PostEntity[];
|
let result: PostEntity[];
|
||||||
if (trash) {
|
if (trash) {
|
||||||
const directs = items.filter((item) => !isNil(item.deleteAt));
|
const directs = items.filter((item) => !isNil(item.deletedAt));
|
||||||
const softs = items.filter((item) => isNil(item.deleteAt));
|
const softs = items.filter((item) => isNil(item.deletedAt));
|
||||||
result = [
|
result = [
|
||||||
...(await this.repository.remove(directs)),
|
...(await this.repository.remove(directs)),
|
||||||
...(await this.repository.softRemove(softs)),
|
...(await this.repository.softRemove(softs)),
|
||||||
@ -172,7 +172,7 @@ export class PostService extends BaseService<PostEntity, PostRepository, FindPar
|
|||||||
.where('post.id IN (:...ids)', { ids })
|
.where('post.id IN (:...ids)', { ids })
|
||||||
.withDeleted()
|
.withDeleted()
|
||||||
.getMany();
|
.getMany();
|
||||||
const trashes = items.filter((item) => !isNil(item.deleteAt));
|
const trashes = items.filter((item) => !isNil(item.deletedAt));
|
||||||
await this.searchService.update(trashes);
|
await this.searchService.update(trashes);
|
||||||
const trashedIds = trashes.map((item) => item.id);
|
const trashedIds = trashes.map((item) => item.id);
|
||||||
if (trashedIds.length < 1) {
|
if (trashedIds.length < 1) {
|
||||||
@ -190,7 +190,7 @@ export class PostService extends BaseService<PostEntity, PostRepository, FindPar
|
|||||||
options: FindParams,
|
options: FindParams,
|
||||||
callback?: QueryHook<PostEntity>,
|
callback?: QueryHook<PostEntity>,
|
||||||
) {
|
) {
|
||||||
const { orderBy, isPublished, category, tag, trashed, search } = options;
|
const { orderBy, isPublished, category, tag, trashed, search, author } = options;
|
||||||
if (typeof isPublished === 'boolean') {
|
if (typeof isPublished === 'boolean') {
|
||||||
isPublished
|
isPublished
|
||||||
? qb.where({ publishedAt: Not(IsNull()) })
|
? qb.where({ publishedAt: Not(IsNull()) })
|
||||||
@ -212,6 +212,9 @@ export class PostService extends BaseService<PostEntity, PostRepository, FindPar
|
|||||||
if (tag) {
|
if (tag) {
|
||||||
qb.where('tags.id = :id', { id: tag });
|
qb.where('tags.id = :id', { id: tag });
|
||||||
}
|
}
|
||||||
|
if (author) {
|
||||||
|
qb.where('author.id = :id', { id: author });
|
||||||
|
}
|
||||||
if (callback) {
|
if (callback) {
|
||||||
return callback(qb);
|
return callback(qb);
|
||||||
}
|
}
|
||||||
@ -252,6 +255,6 @@ export class PostService extends BaseService<PostEntity, PostRepository, FindPar
|
|||||||
const tree = await this.categoryRepository.findDescendantsTree(root);
|
const tree = await this.categoryRepository.findDescendantsTree(root);
|
||||||
const flatDes = await this.categoryRepository.toFlatTrees(tree.children);
|
const flatDes = await this.categoryRepository.toFlatTrees(tree.children);
|
||||||
const ids = [tree.id, ...flatDes.map((item) => item.id)];
|
const ids = [tree.id, ...flatDes.map((item) => item.id)];
|
||||||
return qb.where('categoryRepository.id IN (:...ids)', { ids });
|
return qb.where('category.id IN (:...ids)', { ids });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ export class SearchService implements OnModuleInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async search(text: string, param: SearchOption = {}): Promise<any> {
|
async search(text: string, param: SearchOption = {}): Promise<any> {
|
||||||
const option = { page: 1, limit: 10, trashed: SelectTrashMode.ONLY, ...param };
|
const option = { page: 1, limit: 10, trashed: SelectTrashMode.NONE, ...param };
|
||||||
const limit = isNil(option.limit) || option.limit < 1 ? 1 : option.limit;
|
const limit = isNil(option.limit) || option.limit < 1 ? 1 : option.limit;
|
||||||
const page = isNil(option.page) || option.page < 1 ? 1 : option.page;
|
const page = isNil(option.page) || option.page < 1 ? 1 : option.page;
|
||||||
let filter = ['deletedAt IS NULL'];
|
let filter = ['deletedAt IS NULL'];
|
||||||
@ -64,7 +64,7 @@ export class SearchService implements OnModuleInit {
|
|||||||
.index(this.index)
|
.index(this.index)
|
||||||
.search(text, { page, limit, sort: ['updatedAt:desc', 'commentCount:desc'], filter });
|
.search(text, { page, limit, sort: ['updatedAt:desc', 'commentCount:desc'], filter });
|
||||||
return {
|
return {
|
||||||
item: result.hits,
|
items: result.hits,
|
||||||
currentPage: result.page,
|
currentPage: result.page,
|
||||||
perPage: result.hitsPerPage,
|
perPage: result.hitsPerPage,
|
||||||
totalItems: result.estimatedTotalHits,
|
totalItems: result.estimatedTotalHits,
|
||||||
|
@ -29,7 +29,7 @@ export async function getSearchItem(
|
|||||||
'body',
|
'body',
|
||||||
'summary',
|
'summary',
|
||||||
'commentCount',
|
'commentCount',
|
||||||
'deleteAt',
|
'deletedAt',
|
||||||
'publishedAt',
|
'publishedAt',
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
|
@ -83,7 +83,7 @@ export abstract class BaseService<
|
|||||||
|
|
||||||
async detail(id: string, callback?: QueryHook<T>) {
|
async detail(id: string, callback?: QueryHook<T>) {
|
||||||
const qb = await this.buildItemQB(id, this.repository.buildBaseQB(), callback);
|
const qb = await this.buildItemQB(id, this.repository.buildBaseQB(), callback);
|
||||||
const item = qb.getOne();
|
const item = await qb.getOne();
|
||||||
if (!item) {
|
if (!item) {
|
||||||
throw new NotFoundException(`${this.repository.qbName} ${id} NOT FOUND`);
|
throw new NotFoundException(`${this.repository.qbName} ${id} NOT FOUND`);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,9 @@ export class UniqueConstraint implements ValidatorConstraintInterface {
|
|||||||
constructor(private dataSource: DataSource) {}
|
constructor(private dataSource: DataSource) {}
|
||||||
|
|
||||||
async validate(value: any, validationArguments?: ValidationArguments): Promise<boolean> {
|
async validate(value: any, validationArguments?: ValidationArguments): Promise<boolean> {
|
||||||
|
if (isNil(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const config: Omit<Condition, 'entity'> = { property: validationArguments.property };
|
const config: Omit<Condition, 'entity'> = { property: validationArguments.property };
|
||||||
const condition = ('entity' in validationArguments.constraints[0]
|
const condition = ('entity' in validationArguments.constraints[0]
|
||||||
? merge(config, validationArguments.constraints[0])
|
? merge(config, validationArguments.constraints[0])
|
||||||
|
@ -31,7 +31,7 @@ export const ContentFactory = defineFactory(
|
|||||||
post.publishedAt = (await getTime(configure)).toDate();
|
post.publishedAt = (await getTime(configure)).toDate();
|
||||||
}
|
}
|
||||||
if (Math.random() > 0.5) {
|
if (Math.random() > 0.5) {
|
||||||
post.deleteAt = (await getTime(configure)).toDate();
|
post.deletedAt = (await getTime(configure)).toDate();
|
||||||
}
|
}
|
||||||
if (category) {
|
if (category) {
|
||||||
post.category = category;
|
post.category = category;
|
||||||
|
@ -20,9 +20,6 @@ const permission: PermissionChecker = async (ab) =>
|
|||||||
export class PermissionController {
|
export class PermissionController {
|
||||||
constructor(private service: PermissionService) {}
|
constructor(private service: PermissionService) {}
|
||||||
|
|
||||||
permission: PermissionChecker = async (ab) =>
|
|
||||||
ab.can(PermissionAction.MANAGE, PermissionEntity.name);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页列表查询
|
* 分页列表查询
|
||||||
* @param options
|
* @param options
|
||||||
|
@ -30,18 +30,21 @@ export class RoleEntity extends BaseEntity {
|
|||||||
/**
|
/**
|
||||||
* 角色名称
|
* 角色名称
|
||||||
*/
|
*/
|
||||||
|
@Expose()
|
||||||
@Column({ comment: '角色名称' })
|
@Column({ comment: '角色名称' })
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 显示名称
|
* 显示名称
|
||||||
*/
|
*/
|
||||||
|
@Expose()
|
||||||
@Column({ comment: '显示名称', nullable: true })
|
@Column({ comment: '显示名称', nullable: true })
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 角色描述
|
* 角色描述
|
||||||
*/
|
*/
|
||||||
|
@Expose({ groups: ['role-detail'] })
|
||||||
@Column({ comment: '角色描述', nullable: true, type: 'text' })
|
@Column({ comment: '角色描述', nullable: true, type: 'text' })
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
import { createMongoAbility } from '@casl/ability';
|
import { createMongoAbility } from '@casl/ability';
|
||||||
import { ExecutionContext, ForbiddenException } from '@nestjs/common';
|
import { ExecutionContext, ForbiddenException, Injectable } from '@nestjs/common';
|
||||||
import { ModuleRef, Reflector } from '@nestjs/core';
|
import { ModuleRef, Reflector } from '@nestjs/core';
|
||||||
|
|
||||||
import { isNil } from 'lodash';
|
import { isNil } from 'lodash';
|
||||||
|
|
||||||
|
import { PermissionEntity } from '@/modules/rbac/entities';
|
||||||
import { JwtAuthGuard } from '@/modules/user/guards';
|
import { JwtAuthGuard } from '@/modules/user/guards';
|
||||||
|
|
||||||
import { UserRepository } from '@/modules/user/repositories';
|
import { UserRepository } from '@/modules/user/repositories';
|
||||||
import { TokenService } from '@/modules/user/services';
|
import { TokenService } from '@/modules/user/services';
|
||||||
|
|
||||||
import { PERMISSION_CHECKERS } from '../constants';
|
import { PERMISSION_CHECKERS } from '../constants';
|
||||||
import { PermissionEntity } from '../entities/permission.entity';
|
|
||||||
import { RbacResolver } from '../rbac.resolver';
|
import { RbacResolver } from '../rbac.resolver';
|
||||||
|
|
||||||
import { CheckerParams, PermissionChecker } from '../types';
|
import { CheckerParams, PermissionChecker } from '../types';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class RbacGuard extends JwtAuthGuard {
|
export class RbacGuard extends JwtAuthGuard {
|
||||||
constructor(
|
constructor(
|
||||||
protected reflector: Reflector,
|
protected reflector: Reflector,
|
||||||
protected resolver: RbacResolver,
|
protected resolver: RbacResolver,
|
||||||
protected tokenService: TokenService,
|
protected tokenService: TokenService,
|
||||||
protected userRepository: UserRepository,
|
protected userRepository: UserRepository,
|
||||||
protected modeleRef: ModuleRef,
|
protected moduleRef: ModuleRef,
|
||||||
) {
|
) {
|
||||||
super(reflector, tokenService);
|
super(reflector, tokenService);
|
||||||
}
|
}
|
||||||
@ -45,7 +46,7 @@ export class RbacGuard extends JwtAuthGuard {
|
|||||||
resolver: this.resolver,
|
resolver: this.resolver,
|
||||||
repository: this.userRepository,
|
repository: this.userRepository,
|
||||||
checkers,
|
checkers,
|
||||||
moduleRef: this.modeleRef,
|
moduleRef: this.moduleRef,
|
||||||
request: context.switchToHttp().getRequest(),
|
request: context.switchToHttp().getRequest(),
|
||||||
});
|
});
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
@ -205,8 +205,11 @@ export class RbacResolver<P extends AbilityTuple = AbilityTuple, T extends Mongo
|
|||||||
|
|
||||||
// 同步普通角色
|
// 同步普通角色
|
||||||
for (const role of roles) {
|
for (const role of roles) {
|
||||||
const rolePermissions = await manager.findBy(PermissionEntity, {
|
const rolePermissions =
|
||||||
name: In(this.roles.find(({ name }) => name === role.name).permissions),
|
isNil(role.permissions) || role.permissions.length <= 0
|
||||||
|
? []
|
||||||
|
: await manager.findBy(PermissionEntity, {
|
||||||
|
name: In(role.permissions.map(({ name }) => name)),
|
||||||
});
|
});
|
||||||
await roleRepo
|
await roleRepo
|
||||||
.createQueryBuilder('role')
|
.createQueryBuilder('role')
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
Body,
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
Delete,
|
Delete,
|
||||||
|
ForbiddenException,
|
||||||
Get,
|
Get,
|
||||||
Param,
|
Param,
|
||||||
ParseUUIDPipe,
|
ParseUUIDPipe,
|
||||||
@ -18,6 +19,7 @@ import { PermissionAction } from '@/modules/rbac/constants';
|
|||||||
import { Permission } from '@/modules/rbac/decorators/permission.decorator';
|
import { Permission } from '@/modules/rbac/decorators/permission.decorator';
|
||||||
import { PermissionChecker } from '@/modules/rbac/types';
|
import { PermissionChecker } from '@/modules/rbac/types';
|
||||||
import { Depends } from '@/modules/restful/decorators/depend.decorator';
|
import { Depends } from '@/modules/restful/decorators/depend.decorator';
|
||||||
|
import { RequestUser } from '@/modules/user/decorators/user.request.decorator';
|
||||||
import { CreateUserDto, FrontedQueryUserDto, UpdateUserDto } from '@/modules/user/dtos/user.dto';
|
import { CreateUserDto, FrontedQueryUserDto, UpdateUserDto } from '@/modules/user/dtos/user.dto';
|
||||||
import { UserEntity } from '@/modules/user/entities';
|
import { UserEntity } from '@/modules/user/entities';
|
||||||
import { UserService } from '@/modules/user/services';
|
import { UserService } from '@/modules/user/services';
|
||||||
@ -40,7 +42,7 @@ export class UserController {
|
|||||||
@Permission(permission)
|
@Permission(permission)
|
||||||
@SerializeOptions({ groups: ['user-list'] })
|
@SerializeOptions({ groups: ['user-list'] })
|
||||||
async list(@Query() options: FrontedQueryUserDto) {
|
async list(@Query() options: FrontedQueryUserDto) {
|
||||||
return this.service.list(options);
|
return this.service.paginate(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,13 +80,17 @@ export class UserController {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量删除用户
|
* 批量删除用户
|
||||||
|
* @param user
|
||||||
* @param data
|
* @param data
|
||||||
*/
|
*/
|
||||||
@Delete()
|
@Delete()
|
||||||
@Permission(permission)
|
@Permission(permission)
|
||||||
@SerializeOptions({ groups: ['user-list'] })
|
@SerializeOptions({ groups: ['user-list'] })
|
||||||
async delete(@Body() data: DeleteWithTrashDto) {
|
async delete(@RequestUser() user: UserEntity, @Body() data: DeleteWithTrashDto) {
|
||||||
const { ids, trash } = data;
|
const { ids, trash } = data;
|
||||||
|
if (ids.includes(user.id)) {
|
||||||
|
throw new ForbiddenException();
|
||||||
|
}
|
||||||
return this.service.delete(ids, trash);
|
return this.service.delete(ids, trash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { Controller, Get, Param, ParseUUIDPipe, Query, SerializeOptions } from '
|
|||||||
|
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { IsNull, Not } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
|
|
||||||
import { SelectTrashMode } from '@/modules/database/constants';
|
import { SelectTrashMode } from '@/modules/database/constants';
|
||||||
import { Depends } from '@/modules/restful/decorators/depend.decorator';
|
import { Depends } from '@/modules/restful/decorators/depend.decorator';
|
||||||
@ -25,7 +25,7 @@ export class UserController {
|
|||||||
@Guest()
|
@Guest()
|
||||||
@SerializeOptions({ groups: ['user-list'] })
|
@SerializeOptions({ groups: ['user-list'] })
|
||||||
async list(@Query() options: FrontedQueryUserDto) {
|
async list(@Query() options: FrontedQueryUserDto) {
|
||||||
return this.service.list({ ...options, trashed: SelectTrashMode.NONE });
|
return this.service.paginate({ ...options, trashed: SelectTrashMode.NONE });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,6 +36,6 @@ export class UserController {
|
|||||||
@Guest()
|
@Guest()
|
||||||
@SerializeOptions({ groups: ['user-detail'] })
|
@SerializeOptions({ groups: ['user-detail'] })
|
||||||
async detail(@Param('id', new ParseUUIDPipe()) id: string) {
|
async detail(@Param('id', new ParseUUIDPipe()) id: string) {
|
||||||
return this.service.detail(id, async (qb) => qb.andWhere({ deletedAt: Not(IsNull()) }));
|
return this.service.detail(id, async (qb) => qb.andWhere({ deletedAt: IsNull() }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ export class UpdateAccountDto extends PickType(UserCommonDto, ['username', 'nick
|
|||||||
*/
|
*/
|
||||||
@IsUUID(undefined, { message: '用户ID格式不正确', groups: [UserValidateGroup.USER_UPDATE] })
|
@IsUUID(undefined, { message: '用户ID格式不正确', groups: [UserValidateGroup.USER_UPDATE] })
|
||||||
@IsDefined({ groups: ['update'], message: '用户ID必须指定' })
|
@IsDefined({ groups: ['update'], message: '用户ID必须指定' })
|
||||||
id: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,7 +38,7 @@ export class UserCommonDto {
|
|||||||
{ entity: UserEntity, ignore: 'id', ignoreKey: 'userId' },
|
{ entity: UserEntity, ignore: 'id', ignoreKey: 'userId' },
|
||||||
{ groups: [UserValidateGroup.ACCOUNT_UPDATE], message: '该用户名已被注册' },
|
{ groups: [UserValidateGroup.ACCOUNT_UPDATE], message: '该用户名已被注册' },
|
||||||
)
|
)
|
||||||
@Length(4, 50, { always: true, message: '用户名长度必须为$constraint1到$constraint2' })
|
@Length(4, 30, { always: true, message: '用户名长度必须为$constraint1到$constraint2' })
|
||||||
@IsOptional({ groups: [UserValidateGroup.USER_UPDATE, UserValidateGroup.ACCOUNT_UPDATE] })
|
@IsOptional({ groups: [UserValidateGroup.USER_UPDATE, UserValidateGroup.ACCOUNT_UPDATE] })
|
||||||
username: string;
|
username: string;
|
||||||
|
|
||||||
|
@ -94,6 +94,7 @@ export class QueryUserDto extends PaginateWithTrashedDto {
|
|||||||
* 排序规则:可指定用户列表的排序规则,默认为按创建时间降序排序
|
* 排序规则:可指定用户列表的排序规则,默认为按创建时间降序排序
|
||||||
*/
|
*/
|
||||||
@IsEnum(UserOrderType)
|
@IsEnum(UserOrderType)
|
||||||
|
@IsOptional()
|
||||||
orderBy?: UserOrderType;
|
orderBy?: UserOrderType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,13 +109,16 @@ export class UserEntity {
|
|||||||
* 用户权限
|
* 用户权限
|
||||||
*/
|
*/
|
||||||
@Expose()
|
@Expose()
|
||||||
@ManyToMany(() => PermissionEntity, (permission) => permission.users, { cascade: true })
|
@ManyToMany(() => PermissionEntity, (permission) => permission.users, {
|
||||||
|
cascade: true,
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
permissions: Relation<PermissionEntity>[];
|
permissions: Relation<PermissionEntity>[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户角色
|
* 用户角色
|
||||||
*/
|
*/
|
||||||
@Expose()
|
@Expose()
|
||||||
@ManyToMany(() => RoleEntity, (role) => role.users, { cascade: true })
|
@ManyToMany(() => RoleEntity, (role) => role.users, { cascade: true, onDelete: 'CASCADE' })
|
||||||
roles: Relation<RoleEntity>[];
|
roles: Relation<RoleEntity>[];
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user