add rbac module
This commit is contained in:
@@ -5,7 +5,16 @@ import { ContentFactory } from '@/modules/database/factories/content.factory';
|
||||
import ContentSeeder from '@/modules/database/seeders/content.seeder';
|
||||
|
||||
export const database = createDBConfig((configure) => ({
|
||||
common: { synchronize: true },
|
||||
common: {
|
||||
synchronize: true,
|
||||
// 启用详细日志以便调试 SQL 错误
|
||||
logging:
|
||||
configure.env.get('NODE_ENV') === 'development'
|
||||
? ['query', 'error', 'schema', 'warn', 'info', 'log']
|
||||
: ['error'],
|
||||
// 启用最大日志记录
|
||||
maxQueryExecutionTime: 1000,
|
||||
},
|
||||
connections: [
|
||||
{
|
||||
type: 'mysql',
|
||||
|
||||
@@ -27,7 +27,7 @@ import { PaginateDto } from '@/modules/restful/dtos/paginate.dto';
|
||||
|
||||
const permission: PermissionChecker = async (ab) => ab.can(PermissionAction.MANAGE, TagEntity.name);
|
||||
|
||||
@ApiTags('标签查询')
|
||||
@ApiTags('标签管理')
|
||||
@Depends(ContentModule)
|
||||
@Controller('tag')
|
||||
export class TagController {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import {
|
||||
ArgumentsHost,
|
||||
Catch,
|
||||
ExceptionFilter,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
|
||||
@Catch()
|
||||
export class GlobalExceptionFilter implements ExceptionFilter {
|
||||
private readonly logger = new Logger(GlobalExceptionFilter.name);
|
||||
|
||||
catch(exception: unknown, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const request = ctx.getRequest<FastifyRequest>();
|
||||
const response = ctx.getResponse<FastifyReply>();
|
||||
|
||||
let status = HttpStatus.INTERNAL_SERVER_ERROR;
|
||||
let message = 'Internal server error';
|
||||
let details: any = null;
|
||||
|
||||
// 记录完整的错误信息
|
||||
this.logError(exception, request);
|
||||
|
||||
if (exception instanceof HttpException) {
|
||||
status = exception.getStatus();
|
||||
const exceptionResponse = exception.getResponse();
|
||||
message =
|
||||
typeof exceptionResponse === 'string'
|
||||
? exceptionResponse
|
||||
: (exceptionResponse as any).message || exception.message;
|
||||
} else if (exception instanceof QueryFailedError) {
|
||||
// 专门处理 TypeORM 查询错误
|
||||
status = HttpStatus.BAD_REQUEST;
|
||||
message = 'Database query failed';
|
||||
details = {
|
||||
query: exception.query,
|
||||
parameters: exception.parameters,
|
||||
driverError: exception.driverError?.message,
|
||||
sqlMessage: (exception as any).sqlMessage,
|
||||
code: (exception as any).code,
|
||||
errno: (exception as any).errno,
|
||||
};
|
||||
} else if (exception instanceof Error) {
|
||||
message = exception.message;
|
||||
}
|
||||
|
||||
const errorResponse = {
|
||||
statusCode: status,
|
||||
timestamp: new Date().toISOString(),
|
||||
path: request.url,
|
||||
method: request.method,
|
||||
message,
|
||||
...(details && { details }),
|
||||
};
|
||||
|
||||
response.status(status).send(errorResponse);
|
||||
}
|
||||
|
||||
private logError(exception: unknown, request: FastifyRequest) {
|
||||
const errorInfo = {
|
||||
timestamp: new Date().toISOString(),
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
headers: request.headers,
|
||||
body: request.body,
|
||||
query: request.query,
|
||||
params: request.params,
|
||||
};
|
||||
|
||||
if (exception instanceof QueryFailedError) {
|
||||
this.logger.error(`Database Query Failed: ${exception.message}`, {
|
||||
...errorInfo,
|
||||
query: exception.query,
|
||||
parameters: exception.parameters,
|
||||
driverError: exception.driverError,
|
||||
stack: exception.stack,
|
||||
sqlMessage: (exception as any).sqlMessage,
|
||||
code: (exception as any).code,
|
||||
errno: (exception as any).errno,
|
||||
});
|
||||
} else if (exception instanceof Error) {
|
||||
this.logger.error(`Unhandled Exception: ${exception.message}`, {
|
||||
...errorInfo,
|
||||
stack: exception.stack,
|
||||
name: exception.name,
|
||||
});
|
||||
} else {
|
||||
this.logger.error('Unknown Exception', {
|
||||
...errorInfo,
|
||||
exception,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import { PaginateWithTrashedDto } from '@/modules/restful/dtos/paginate-width-tr
|
||||
const permission: PermissionChecker = async (ab) =>
|
||||
ab.can(PermissionAction.MANAGE, PermissionEntity.name);
|
||||
|
||||
@ApiTags('权限查询')
|
||||
@ApiTags('权限管理')
|
||||
@ApiBearerAuth()
|
||||
@Depends(RbacModule)
|
||||
@Controller('permissions')
|
||||
|
||||
@@ -243,7 +243,7 @@ export class RbacResolver<P extends AbilityTuple = AbilityTuple, T extends Mongo
|
||||
const superUsers = await manager
|
||||
.createQueryBuilder(UserEntity, 'user')
|
||||
.leftJoinAndSelect('user.roles', 'roles')
|
||||
.where('roles.id IN (:...ids', { ids: [superRole.id] })
|
||||
.where('roles.id IN (:...ids)', { ids: [superRole.id] })
|
||||
.getMany();
|
||||
|
||||
if (superUsers.length < 1) {
|
||||
|
||||
@@ -24,7 +24,7 @@ export const createRbacApi = () => {
|
||||
app: [{ name: '角色查询', description: '查询角色信息' }],
|
||||
manager: [
|
||||
{ name: '角色管理', description: '管理角色信息' },
|
||||
{ name: '权限信息', description: '查询权限信息' },
|
||||
{ name: '权限管理', description: '管理权限信息' },
|
||||
],
|
||||
};
|
||||
return { routes, tags };
|
||||
|
||||
@@ -34,7 +34,7 @@ export class PermissionService extends BaseService<
|
||||
) {
|
||||
const qb = await super.buildListQB(queryBuilder, options, callback);
|
||||
if (!isNil(options.role)) {
|
||||
qb.andWhere('role.id IN (:...roles', { roles: [options.role] });
|
||||
qb.andWhere('role.id IN (:...roles)', { roles: [options.role] });
|
||||
}
|
||||
return qb;
|
||||
}
|
||||
|
||||
@@ -22,10 +22,7 @@ export function createUserApi() {
|
||||
};
|
||||
|
||||
const tags: Record<'app' | 'manager', (string | TagOption)[]> = {
|
||||
app: [
|
||||
{ name: '用户管理', description: '对用户进行CRUD操作' },
|
||||
{ name: '账户操作', description: '注册登录、查看修改账户信息、修改密码等' },
|
||||
],
|
||||
app: [{ name: '账户操作', description: '注册登录、查看修改账户信息、修改密码等' }],
|
||||
manager: [{ name: '用户管理', description: '管理用户信息' }],
|
||||
};
|
||||
|
||||
|
||||
@@ -132,10 +132,10 @@ export class UserService extends BaseService<UserEntity, UserRepository> {
|
||||
const { orderBy } = options;
|
||||
const qb = await super.buildListQB(queryBuilder, options, callback);
|
||||
if (!isNil(options.role)) {
|
||||
qb.andWhere('roles.id IN (:...roles', { roles: [options.role] });
|
||||
qb.andWhere('roles.id IN (:...roles)', { roles: [options.role] });
|
||||
}
|
||||
if (!isNil(options.permission)) {
|
||||
qb.andWhere('permissions.id IN (:...permissions', {
|
||||
qb.andWhere('permissions.id IN (:...permissions)', {
|
||||
permissions: [options.permission],
|
||||
});
|
||||
}
|
||||
|
||||
+5
-1
@@ -12,6 +12,7 @@ import { UserModule } from '@/modules/user/user.module';
|
||||
|
||||
import * as configs from './config';
|
||||
import { ContentModule } from './modules/content/content.module';
|
||||
import { GlobalExceptionFilter } from './modules/core/filters/global-exception.filter';
|
||||
import { CreateOptions } from './modules/core/types';
|
||||
import * as dbCommands from './modules/database/commands';
|
||||
import { DatabaseModule } from './modules/database/database.module';
|
||||
@@ -32,7 +33,10 @@ export const createOptions: CreateOptions = {
|
||||
await UserModule.forRoot(configure),
|
||||
await RbacModule.forRoot(configure),
|
||||
],
|
||||
globals: { guard: RbacGuard },
|
||||
globals: {
|
||||
guard: RbacGuard,
|
||||
filter: GlobalExceptionFilter,
|
||||
},
|
||||
builder: async ({ configure, BootModule }) => {
|
||||
const container = await NestFactory.create<NestFastifyApplication>(
|
||||
BootModule,
|
||||
|
||||
Reference in New Issue
Block a user