有没有一种漂亮的方法可以在 NestJS 应用程序中创建重载路由?我有一些想法,但也许我正在发明一个轮子。但我找不到任何现成的方法......
我说的是这样的 (让我们以 https://github.com/nestjs/nest/blob/master/sample/01-cats-app/src/cats/cats.controller.ts 作为起点):
@Get()
async findAll(): Promise<Cat[]> {
return this.catsService.findAll();
}
@Get()
@Roles('admin')
async findAllAdmin(): Promise<Cat[]> {
return this.catsService.findAllAdmin();
}
换句话说,我希望有两条具有相同 URL 的路由,但通过一些其他值进行区分(例如此处的角色)。
我的想法是创建我自己的装饰器,而不是Get,它将填充一些权重映射,分配给每个重载方法唯一的路径。然后,添加中间件,该中间件将从请求中获取参数,将它们与地图进行比较,并进行内部重定向(使用
next('route')
或 req.app.handle(req, res)
)到适当的新路径。
但是在这种方法中,如果他们应该在其中一种方法上使用 AuthGuard 进行身份验证,我无法从请求中获取用户......
看起来不太好看,但我们来试试吧。
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
export const IsAdmin = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
// Here add the logic to know if is an admin from the request
return request.role === "admin";
}
);
@Get()
async findAll(@IsAdmin() isAdmin: boolean): Promise<Cat[]> {
if (isAdmin) {
return this.catsService.findAllAdmin();
}
return this.catsService.findAll();
}
Nest 有两种方法,一种是@David-Morales 已经提出的,但应该有一些改变。
示例:
------file------: is-admin.decorator.ts
import { applyDecorators, UseGuards } from '@nestjs/common';
import { ApiBearerAuth, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { JwtAuthGuard } from '.jwt-auth.guard'; // this is class extending default AuthGuard('jwt') from '@nestjs/passport', but you could use your own there (it must validate user and provide `request.user` variable with user data/role anything you wish to store about user in request).
import { AdminGuard } from './admin.guard';
export function IsAdmin() {
return applyDecorators(
UseGuards(JwtAuthGuard, AdminGuard),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized' }),
);
}
-----file------: admin.guard.ts
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AdminGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
return context.switchToHttp().getRequest().user?.accountType === 'admin';
}
}
------file------: example controller fully accessible only by admins
/* dont forget proper imports here */
@IsAdmin()
@Controller('/admin/account') // exmaple controller path
export class AccountController {
// any endpoint here can be only accessed by logged in admin
}
------file------: example controller with some routes restricted to admin
/* dont forget proper imports here */
@Controller('/account')
export class AccountController {
@IsAdmin()
@Get('/admin')
async adminRoute() {} // this will be callable only when requesting user is admin
@Get('/any-user')
async anyUserRouter() {} // this can be called by anyone
}
------file------: invoker.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const Invoker = createParamDecorator(
(data: unknown, ctx: ExecutionContext): UserAuthorization | undefined => {
const request = ctx.switchToHttp().getRequest();
if (request.user) {
return { /*data*/ };
// here you return class, object - anything that contains use context you want to use. example: return new InvokerClass(request.user); where `InvokerClass`, is class that can tell you user account type, maybe its id etc etc.
}
return undefined;
},
);
------file-----: controller that uses both approaches
/* dont forget proper imports here */
@Controller('/account')
export class AccountController {
@IsAdmin()
@Get('/admin')
async adminRoute(@Invoker() invoker: InvokerClass) {} // this will be callable only when requesting user is admin, but you know details of that person in `invoker` variable
@Get('/any-user')
async anyUserRouter(@Invoker() invoker?: InvokerClass) {} // this can be called by anyone but you can learn who executed it with `invoker` variable (also get details of that person)
}