Skip to content

Commit db4a19b

Browse files
committed
Added pagination
1 parent af4d4c7 commit db4a19b

15 files changed

+157
-10
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export enum Order {
2+
ASC = 'ASC',
3+
DESC = 'DESC',
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { applyDecorators, Type } from '@nestjs/common';
2+
import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger';
3+
import { PageDto } from '../pages/dto/page.dto';
4+
5+
export const ApiPaginatedResponse = <TModel extends Type<unknown>>(
6+
model: TModel
7+
) => {
8+
return applyDecorators(
9+
ApiExtraModels(PageDto, model),
10+
ApiOkResponse({
11+
description: 'Successful operation',
12+
schema: {
13+
allOf: [
14+
{ $ref: getSchemaPath(PageDto) },
15+
{
16+
properties: {
17+
results: {
18+
type: 'array',
19+
items: { $ref: getSchemaPath(model) },
20+
},
21+
},
22+
},
23+
],
24+
},
25+
})
26+
);
27+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { ApiProperty } from '@nestjs/swagger';
2+
import { PageMetaDtoParams } from '../interfaces';
3+
4+
export class PageMetaDto {
5+
@ApiProperty()
6+
readonly page: number;
7+
8+
@ApiProperty()
9+
readonly take: number;
10+
11+
@ApiProperty()
12+
readonly itemCount: number;
13+
14+
@ApiProperty()
15+
readonly pageCount: number;
16+
17+
@ApiProperty()
18+
readonly hasPreviousPage: boolean;
19+
20+
@ApiProperty()
21+
readonly hasNextPage: boolean;
22+
23+
constructor({ pageOptionsDto, itemCount }: PageMetaDtoParams) {
24+
this.page = pageOptionsDto.page;
25+
this.take = pageOptionsDto.take;
26+
this.itemCount = itemCount;
27+
this.pageCount = Math.ceil(this.itemCount / this.take);
28+
this.hasPreviousPage = this.page > 1;
29+
this.hasNextPage = this.page < this.pageCount;
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ApiPropertyOptional } from '@nestjs/swagger';
2+
import { Type } from 'class-transformer';
3+
import { Order } from '../../constants';
4+
import { IsEnum, IsInt, IsOptional, Max, Min } from 'class-validator';
5+
6+
export class PageOptionsDto {
7+
@ApiPropertyOptional({ enum: Order, default: Order.ASC })
8+
@IsEnum(Order)
9+
@IsOptional()
10+
readonly order?: Order = Order.ASC;
11+
@ApiPropertyOptional({ default: 1, minimum: 1 })
12+
@Type(() => Number)
13+
@IsInt()
14+
@Min(1)
15+
@IsOptional()
16+
readonly page?: number = 1;
17+
@ApiPropertyOptional({ default: 10, minimum: 1, maximum: 200 })
18+
@Type(() => Number)
19+
@IsInt()
20+
@Min(1)
21+
@Max(200)
22+
@IsOptional()
23+
readonly take?: number = 10;
24+
get skip(): number {
25+
return (this.page - 1) * this.take;
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { ApiProperty } from "@nestjs/swagger";
2+
import { IsArray } from "class-validator";
3+
import { PageMetaDto } from "./page-meta.dto";
4+
5+
export class PageDto<T> {
6+
@IsArray()
7+
@ApiProperty({ isArray: true })
8+
readonly data: T[];
9+
10+
@ApiProperty({ type: () => PageMetaDto })
11+
readonly meta: PageMetaDto;
12+
13+
constructor(data: T[], meta: PageMetaDto) {
14+
this.data = data;
15+
this.meta = meta;
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { PageOptionsDto } from "../dto/page-options.dto";
2+
3+
export interface PageMetaDtoParams {
4+
pageOptionsDto: PageOptionsDto;
5+
itemCount: number;
6+
}

apps/car-rental-backend/src/main.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ async function bootstrap() {
88
const config = new DocumentBuilder().setTitle('Car Rental API').build();
99
const documentFactory = () => SwaggerModule.createDocument(app, config);
1010
const globalPrefix = 'api';
11-
app.useGlobalPipes(new ValidationPipe());
11+
app.useGlobalPipes(new ValidationPipe({ transform: true }));
1212
app.setGlobalPrefix(globalPrefix);
1313
const port = process.env.PORT || 3000;
1414
SwaggerModule.setup('api', app, documentFactory);

apps/car-rental-backend/src/vehicle-brands/dto/create-vehicle-brand.dto.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ApiProperty } from '@nestjs/swagger';
22
import { IsString, Length } from 'class-validator';
3-
import { IsFirstLetterUppercase } from '../../validators/is-first-letter-uppercase.decorator';
3+
import { IsFirstLetterUppercase } from '../../common/validators/is-first-letter-uppercase.decorator';
44
export class CreateVehicleBrandDto {
55
@IsString()
66
@Length(1, 50)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { PickType } from '@nestjs/swagger';
2+
import { CreateVehicleBrandDto } from './create-vehicle-brand.dto';
3+
4+
export class VehicleBrandDto extends PickType(CreateVehicleBrandDto, [
5+
'name',
6+
] as const) {}

apps/car-rental-backend/src/vehicle-brands/entities/vehicle-brand.entity.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Column, Entity } from 'typeorm';
2-
import { AbstractEntity } from '../../abstract/abstract.entity';
2+
import { AbstractEntity } from '../../common/entity/abstract.entity';
33

44
@Entity()
55
export class VehicleBrand extends AbstractEntity {

apps/car-rental-backend/src/vehicle-brands/vehicle-brands.controller.ts

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
1-
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
1+
import {
2+
Controller,
3+
Get,
4+
Post,
5+
Body,
6+
Patch,
7+
Param,
8+
Delete,
9+
HttpCode,
10+
HttpStatus,
11+
Query,
12+
} from '@nestjs/common';
213
import { VehicleBrandsService } from './vehicle-brands.service';
314
import { CreateVehicleBrandDto } from './dto/create-vehicle-brand.dto';
415
import { UpdateVehicleBrandDto } from './dto/update-vehicle-brand.dto';
16+
import { PageOptionsDto } from '../common/pages/dto/page-options.dto';
17+
import { ApiPaginatedResponse } from '../common/decorators/api-paginated-response.decorator';
18+
import { VehicleBrandDto } from './dto/vehicle-brand.dto';
519

620
@Controller('vehicle-brands')
721
export class VehicleBrandsController {
@@ -13,8 +27,10 @@ export class VehicleBrandsController {
1327
}
1428

1529
@Get()
16-
findAll() {
17-
return this.vehicleBrandsService.findAll();
30+
@HttpCode(HttpStatus.OK)
31+
@ApiPaginatedResponse(VehicleBrandDto)
32+
findVehicleBrands(@Query() pageOptionsDto: PageOptionsDto) {
33+
return this.vehicleBrandsService.findVehicleBrands(pageOptionsDto);
1834
}
1935

2036
@Get(':id')
@@ -23,7 +39,10 @@ export class VehicleBrandsController {
2339
}
2440

2541
@Patch(':id')
26-
update(@Param('id') id: string, @Body() updateVehicleBrandDto: UpdateVehicleBrandDto) {
42+
update(
43+
@Param('id') id: string,
44+
@Body() updateVehicleBrandDto: UpdateVehicleBrandDto
45+
) {
2746
return this.vehicleBrandsService.update(+id, updateVehicleBrandDto);
2847
}
2948

apps/car-rental-backend/src/vehicle-brands/vehicle-brands.service.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { UpdateVehicleBrandDto } from './dto/update-vehicle-brand.dto';
44
import { InjectRepository } from '@nestjs/typeorm';
55
import { Repository } from 'typeorm';
66
import { VehicleBrand } from './entities/vehicle-brand.entity';
7+
import { PageOptionsDto } from '../common/pages/dto/page-options.dto';
8+
import { PageMetaDto } from '../common/pages/dto/page-meta.dto';
9+
import { PageDto } from '../common/pages/dto/page.dto';
710

811
@Injectable()
912
export class VehicleBrandsService {
@@ -24,8 +27,15 @@ export class VehicleBrandsService {
2427
return this.vehicleBrandsRepository.save(createVehicleBrandDto);
2528
}
2629

27-
findAll() {
28-
return this.vehicleBrandsRepository.find();
30+
async findVehicleBrands(pageOptionsDto: PageOptionsDto) {
31+
const { order, take, skip } = pageOptionsDto;
32+
const queryBuilder =
33+
this.vehicleBrandsRepository.createQueryBuilder('vehicleBrand');
34+
queryBuilder.orderBy('vehicleBrand.name', order).skip(skip).take(take);
35+
const itemCount = await queryBuilder.getCount();
36+
const { entities } = await queryBuilder.getRawAndEntities();
37+
const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto });
38+
return new PageDto(entities, pageMetaDto);
2939
}
3040

3141
findOne(id: number) {

apps/car-rental-backend/src/vehicles/entities/vehicle.entity.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
ManyToOne,
66
} from 'typeorm';
77
import { VehicleBrand } from '../../vehicle-brands/entities/vehicle-brand.entity';
8-
import { AbstractEntity } from '../../abstract/abstract.entity';
8+
import { AbstractEntity } from '../../common/entity/abstract.entity';
99

1010
@Entity()
1111
export class Vehicle extends AbstractEntity {

0 commit comments

Comments
 (0)