LoginSignup
0
0

More than 1 year has passed since last update.

【NestJS】権限付加と見積取得の実装(Guard, QueryBuilder)

Last updated at Posted at 2021-10-20

はじめに

リレーション(一対多)の実装に引き続き、権限付加とQueryBuilderの実装を行いました。

実装したコードを確認したい方は以下よりご確認ください。

実装

管理者のみに特定の操作の権限を与える処理とQueryBuilderで見積額を取得する処理を実装しました。

管理者によるReportの承認

ユーザが作成したReportを管理者(Administrator)が承認する処理を行うルートハンドラー(approveReport)をReportsControllerに追加します。
管理者のみに承認の認可を与えるために、リクエストを行うユーザが管理者であるかどうかを判断するGuardをルートハンドラーの前に置きます。

reports.controller.ts
@Controller('reports')
export class ReportsController {
  constructor(private reportsService: ReportsService) {}

  ...

  @Patch('/:id')
  @UseGuards(AdminGuard)
  approveReport(@Param('id') id: string, @Body() body: ApproveReportDto) {
    return this.reportsService.changeApproval(id, body.approved);
  }
}

Guard(AdminGuard)の中身は以下のようになります。
currentUser.admintrueのときだけリクエストを通します。

admin.guard.ts
import { CanActivate, ExecutionContext } from '@nestjs/common';

export class AdminGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    const request = context.switchToHttp().getRequest();

    if (!request.currentUser) {
      return false;
    }

    return request.currentUser.admin;
  }
}

ただ、AdminGuardを加えただけだとうまく動作しません。
なぜかというと、currentUserを返すInterceptorはGuardの後に実行されるためです。
スクリーンショット 2021-10-21 7.58.30.png

そのため、currentUserを返す処理をInterceptorからMiddlewareにうつしてあげる必要があります。

current-user.middleware.ts
declare global {
  namespace Express {
    interface Request {
      currentUser?: User;
    }
  }
}

@Injectable()
export class CurrentUserMiddleware implements NestMiddleware {
  constructor(private usersService: UsersService) {}

  async use(req: Request, res: Response, next: NextFunction) {
    const { userId } = req.session || {};

    if (userId) {
      const user = await this.usersService.findOne(userId);
      // eslint-disable-next-line @typescript-eslint/ban-ts-comment
      // @ts-ignore
      req.currentUser = user;
    }

    next();
  }
}

作成したMiddlewareをUsersModuleに付加します。

users.module.ts
export class UsersModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(CurrentUserMiddleware).forRoutes('*');
  }
}

QueryBuilderで見積額を取得

入力した中古車情報から見積額priceを取得する処理を実装します。
priceを推測するにあたり、以下の条件に当てはまるReportをDBから3件取得します。
そして、3件のレポートのpriceの平均値を見積額として返します。

  • make: 同じメーカ
  • model: 同じ車種
  • lng: +/- 5°
  • lat: +/- 5°
  • year: 3年以内
  • mileage: 近い順

DBからpriceの平均値を取得するにあたって、SQLを構築する必要があります。
そこでTypeORMのQueryBuilderを使用します。

reports.service.ts
@Injectable()
export class ReportsService {
  constructor(@InjectRepository(Report) private repo: Repository<Report>) {}

  createEstimate({ make, model, lng, lat, year, mileage }: GetEstimateDto) {
    return this.repo
      .createQueryBuilder()
      .select('AVG(price)', 'price')
      .where('make = :make', { make })
      .andWhere('model = :model', { model })
      .andWhere('lng - :lng BETWEEN -5 AND 5', { lng })
      .andWhere('lat - :lat BETWEEN -5 AND 5', { lat })
      .andWhere('year - :year BETWEEN -3 AND 3', { year })
      .andWhere('approved IS TRUE')
      .orderBy('ABS(mileage - :mileage)', 'DESC')
      .setParameters({ mileage })
      .limit(3)
      .getRawOne();
  }
  ...
}

クエリに必要なデータについては、クエリパラメータとしてURLに渡すようにします。

reports.controller.ts
@Controller('reports')
export class ReportsController {
  constructor(private reportsService: ReportsService) {}

  @Get()
  getEstimate(@Query() query: GetEstimateDto) {
    return this.reportsService.createEstimate(query);
  }
  ...
}

ただ、クエリパラメータはstring型で入ってくるので、yearmileageなどについては、GetEstimateDto内で@Transform()デコレータを使用して、型を適切に変換してあげる必要があります。

get-estimate.dto.ts
export class GetEstimateDto {
  @IsString()
  make: string;

  @IsString()
  model: string;

  @Transform(({ value }) => parseInt(value))
  @IsNumber()
  @Min(1930)
  @Max(2050)
  year: number;

  @Transform(({ value }) => parseFloat(value))
  @IsLongitude()
  lng: number;

  @Transform(({ value }) => parseFloat(value))
  @IsLatitude()
  lat: number;

  @Transform(({ value }) => parseInt(value))
  @IsNumber()
  @Min(0)
  @Max(1000000)
  mileage: number;
}

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0