Ajout de la pagination sur l'API

This commit is contained in:
2024-12-07 16:50:26 +01:00
parent 86427bb41b
commit 138ff1df65
10 changed files with 186 additions and 23 deletions

View File

@ -0,0 +1,42 @@
import { ApiProperty } from "@nestjs/swagger"
import { IsNumber, IsOptional } from "class-validator"
export const DEFAULT_PAGE_NUMBER = 1
export const DEFAULT_PAGE_SIZE = 20
export class MetaPaginateOutputDto {
@IsNumber()
@ApiProperty()
total: number
@IsNumber()
@ApiProperty()
lastPage: number
@IsNumber()
@ApiProperty({ default: DEFAULT_PAGE_NUMBER })
currentPage: number = DEFAULT_PAGE_NUMBER
@IsNumber()
@ApiProperty({ default: DEFAULT_PAGE_SIZE })
totalPerPage: number = DEFAULT_PAGE_SIZE
@IsOptional()
@IsNumber()
@ApiProperty({ required: false, nullable: true, default: null })
prevPage?: number | null
@IsOptional()
@IsNumber()
@ApiProperty({ required: false, nullable: true, default: null })
nextPage?: number | null
}
export class PaginateOutputDto<T> {
@ApiProperty({ isArray: true })
data: T[]
@ApiProperty()
meta: MetaPaginateOutputDto
}

View File

@ -0,0 +1,17 @@
import { ApiProperty } from '@nestjs/swagger'
import { Type } from 'class-transformer'
import { IsNumber, IsOptional } from 'class-validator'
export class QueryPaginationDto {
@IsOptional()
@IsNumber()
@Type(() => Number)
@ApiProperty({default: 1, required: false, description: "Numéro de page à charger"})
page?: number = 1
@IsOptional()
@IsNumber()
@Type(() => Number)
@ApiProperty({default: 20, required: false, description: "Nombre d'éléments à charger par page"})
size?: number = 20
}

View File

@ -0,0 +1,86 @@
import { applyDecorators, NotFoundException, Type } from '@nestjs/common'
import { QueryPaginationDto } from '../dto/pagination-query.dto'
import { ApiExtraModels, ApiOkResponse, ApiResponseNoStatusOptions, getSchemaPath } from '@nestjs/swagger'
import { DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_SIZE, PaginateOutputDto } from '../dto/pagination-output.dto'
export interface PrismaPaginationParams {
skip: number
take: number
}
export const paginate = (
query: QueryPaginationDto,
): PrismaPaginationParams => {
const size = query.size || DEFAULT_PAGE_SIZE
const page = query.page || DEFAULT_PAGE_NUMBER
return {
skip: size * (page - 1),
take: size,
}
}
export const paginateOutput = <T>(
data: T[],
total: number,
query: QueryPaginationDto,
): PaginateOutputDto<T> => {
const page = query.page || DEFAULT_PAGE_NUMBER
const size = query.size || DEFAULT_PAGE_SIZE
const lastPage = Math.ceil(total / size)
// if data is empty, return empty array
if (!data.length) {
return {
data,
meta: {
total,
lastPage,
currentPage: page,
totalPerPage: size,
prevPage: null,
nextPage: null,
},
}
}
// if page is greater than last page, throw an error
if (page > lastPage) {
throw new NotFoundException(
`Page ${page} not found. Last page is ${lastPage}`,
)
}
return {
data,
meta: {
total,
lastPage,
currentPage: page,
totalPerPage: size,
prevPage: page > 1 ? page - 1 : null,
nextPage: page < lastPage ? page + 1 : null,
},
}
}
export const ApiOkResponsePaginated = <DataDto extends Type<unknown>>(dataDto: DataDto, options?: ApiResponseNoStatusOptions) =>
applyDecorators(
ApiExtraModels(PaginateOutputDto, dataDto),
ApiOkResponse({
...options,
schema: {
allOf: [
{ $ref: getSchemaPath(PaginateOutputDto) },
{
properties: {
data: {
type: 'array',
items: { $ref: getSchemaPath(dataDto) },
},
},
},
],
},
}),
)