From 86ebe8f05a1bec54921dc82ced5da28119afa042 Mon Sep 17 00:00:00 2001 From: Abraham <1001.28021547.ucla@gmail.com> Date: Wed, 5 Mar 2025 18:21:53 -0400 Subject: [PATCH 1/6] add Pagination dto --- src/utils/dto.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/utils/dto.ts diff --git a/src/utils/dto.ts b/src/utils/dto.ts new file mode 100644 index 0000000..ba84d8c --- /dev/null +++ b/src/utils/dto.ts @@ -0,0 +1,6 @@ +export class Pagination { + results: object[]; + count: number; + next: string | null; + previous: string | null; +} From 484f95ac62df0667370459d32672d3a6abe505b6 Mon Sep 17 00:00:00 2001 From: Abraham <1001.28021547.ucla@gmail.com> Date: Wed, 5 Mar 2025 18:25:13 -0400 Subject: [PATCH 2/6] update pagination in products --- src/products/products.controller.ts | 5 +++- src/products/products.service.ts | 36 +++++++++++++++++++---------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/products/products.controller.ts b/src/products/products.controller.ts index 36cf8e7..d180d23 100644 --- a/src/products/products.controller.ts +++ b/src/products/products.controller.ts @@ -4,10 +4,12 @@ import { Get, ParseIntPipe, Query, + Req, } from '@nestjs/common'; import { ProductsService } from './products.service'; import { ApiOperation, ApiResponse } from '@nestjs/swagger'; import { ProductListDTO } from './dto/find-products.dto'; +import { Request } from 'express'; @Controller('product') export class ProductsController { @@ -27,7 +29,8 @@ export class ProductsController { getProducts( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number, + @Req() req: Request, ) { - return this.productsServices.getProducts(page, limit); + return this.productsServices.getProducts(page, limit, req); } } diff --git a/src/products/products.service.ts b/src/products/products.service.ts index 71aabb8..f9d16e6 100644 --- a/src/products/products.service.ts +++ b/src/products/products.service.ts @@ -2,6 +2,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Product } from './entities/product.entity'; import { Repository } from 'typeorm'; +import { Request } from 'express'; +import { Pagination } from 'src/utils/dto'; @Injectable() export class ProductsService { @@ -12,18 +14,28 @@ export class ProductsService { async getProducts( page: number, limit: number, - ): Promise<{ - totalItems: number; - totalPages: number; - currentPage: number; - products: Product[]; - }> { - const totalItems = await this.productRepository + req: Request, + ): Promise { + const count = await this.productRepository .createQueryBuilder('product') .where('product.deletedAt IS NULL') .getCount(); - const totalPages = Math.ceil(totalItems / limit); + const startIndex = (page - 1) * limit; + const endIndex = page * limit; + + const protocol = req.protocol; + const host = req.get('host'); + + const baseUrl = `${protocol}://${host}${req.path}`; + + const next = + endIndex < count ? `${baseUrl}?page=${page + 1}&pageSize=${limit}` : null; + + const previous = + startIndex > 0 ? `${baseUrl}?page=${page - 1}&pageSize=${limit}` : null; + + // const totalPages = Math.ceil(totalItems / limit); const products = await this.productRepository .createQueryBuilder('product') @@ -44,10 +56,10 @@ export class ProductsService { .getMany(); return { - totalItems, - totalPages, - currentPage: page, - products, + results: products, + count, + next, + previous, }; } } From c8c3b12a8be26746cb941201ed7aaea3c2632ee6 Mon Sep 17 00:00:00 2001 From: Abraham <1001.28021547.ucla@gmail.com> Date: Wed, 5 Mar 2025 19:52:56 -0400 Subject: [PATCH 3/6] add paginationUrls in utils --- src/products/products.service.ts | 17 ++--------------- src/utils/paginationUrls.ts | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 15 deletions(-) create mode 100644 src/utils/paginationUrls.ts diff --git a/src/products/products.service.ts b/src/products/products.service.ts index f9d16e6..892ae81 100644 --- a/src/products/products.service.ts +++ b/src/products/products.service.ts @@ -4,6 +4,7 @@ import { Product } from './entities/product.entity'; import { Repository } from 'typeorm'; import { Request } from 'express'; import { Pagination } from 'src/utils/dto'; +import { getPaginationUrls } from 'src/utils/paginationUrls'; @Injectable() export class ProductsService { @@ -21,21 +22,7 @@ export class ProductsService { .where('product.deletedAt IS NULL') .getCount(); - const startIndex = (page - 1) * limit; - const endIndex = page * limit; - - const protocol = req.protocol; - const host = req.get('host'); - - const baseUrl = `${protocol}://${host}${req.path}`; - - const next = - endIndex < count ? `${baseUrl}?page=${page + 1}&pageSize=${limit}` : null; - - const previous = - startIndex > 0 ? `${baseUrl}?page=${page - 1}&pageSize=${limit}` : null; - - // const totalPages = Math.ceil(totalItems / limit); + const { next, previous } = getPaginationUrls(req, page, limit, count); const products = await this.productRepository .createQueryBuilder('product') diff --git a/src/utils/paginationUrls.ts b/src/utils/paginationUrls.ts new file mode 100644 index 0000000..b6dcd7a --- /dev/null +++ b/src/utils/paginationUrls.ts @@ -0,0 +1,29 @@ +import { Request } from 'express'; + +interface PaginationUrls { + next: string | null; + previous: string | null; +} + +export function getPaginationUrls( + req: Request, + page: number, + limit: number, + count: number, +): PaginationUrls { + const startIndex = (page - 1) * limit; + const endIndex = page * limit; + + const protocol = req.protocol; + const host = req.get('host'); + + const baseUrl = `${protocol}://${host}${req.path}`; + + const next = + endIndex < count ? `${baseUrl}?page=${page + 1}&limit=${limit}` : null; + + const previous = + startIndex > 0 ? `${baseUrl}?page=${page - 1}&limit=${limit}` : null; + + return { next, previous }; +} From 8ea3cd9fda6a8afd04982ef7333766045a1023c2 Mon Sep 17 00:00:00 2001 From: Abraham <1001.28021547.ucla@gmail.com> Date: Sat, 8 Mar 2025 12:09:32 -0400 Subject: [PATCH 4/6] add API_URL to .env and rename files --- .env.example | 4 +++- package-lock.json | 4 ++-- src/products/products.service.ts | 4 ++-- src/utils/{paginationUrls.ts => pagination-urls.ts} | 12 +++++++----- src/utils/{dto.ts => pagination.dto.ts} | 0 5 files changed, 14 insertions(+), 10 deletions(-) rename src/utils/{paginationUrls.ts => pagination-urls.ts} (70%) rename src/utils/{dto.ts => pagination.dto.ts} (100%) diff --git a/.env.example b/.env.example index ff70528..2cc988b 100644 --- a/.env.example +++ b/.env.example @@ -10,4 +10,6 @@ PGADMIN_DEFAULT_EMAIL= PGADMIN_DEFAULT_PASSWORD= # JWT JWT_SECRET= -JWT_EXPIRES_IN=7d \ No newline at end of file +JWT_EXPIRES_IN=7d +# API +API_URL= \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index bd58b98..22e59cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "api", - "version": "0.0.1", + "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "api", - "version": "0.0.1", + "version": "0.1.0", "license": "UNLICENSED", "dependencies": { "@nestjs/common": "^11.0.1", diff --git a/src/products/products.service.ts b/src/products/products.service.ts index 892ae81..051a93e 100644 --- a/src/products/products.service.ts +++ b/src/products/products.service.ts @@ -3,8 +3,8 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Product } from './entities/product.entity'; import { Repository } from 'typeorm'; import { Request } from 'express'; -import { Pagination } from 'src/utils/dto'; -import { getPaginationUrls } from 'src/utils/paginationUrls'; +import { Pagination } from 'src/utils/pagination.dto'; +import { getPaginationUrls } from 'src/utils/pagination-urls'; @Injectable() export class ProductsService { diff --git a/src/utils/paginationUrls.ts b/src/utils/pagination-urls.ts similarity index 70% rename from src/utils/paginationUrls.ts rename to src/utils/pagination-urls.ts index b6dcd7a..d3f2c73 100644 --- a/src/utils/paginationUrls.ts +++ b/src/utils/pagination-urls.ts @@ -1,9 +1,12 @@ +import { ConfigService } from '@nestjs/config'; import { Request } from 'express'; -interface PaginationUrls { +type PaginationUrls = { next: string | null; previous: string | null; -} +}; + +const configService = new ConfigService(); export function getPaginationUrls( req: Request, @@ -14,10 +17,9 @@ export function getPaginationUrls( const startIndex = (page - 1) * limit; const endIndex = page * limit; - const protocol = req.protocol; - const host = req.get('host'); + const api = configService.get('API_URL'); - const baseUrl = `${protocol}://${host}${req.path}`; + const baseUrl = `${api}${req.path}`; const next = endIndex < count ? `${baseUrl}?page=${page + 1}&limit=${limit}` : null; diff --git a/src/utils/dto.ts b/src/utils/pagination.dto.ts similarity index 100% rename from src/utils/dto.ts rename to src/utils/pagination.dto.ts From 5d4ac35b7a3b7ea08f33377062b44460f49d1a28 Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Sat, 8 Mar 2025 17:28:58 -0400 Subject: [PATCH 5/6] Refactor for modularity, change class names and create new folders and files --- src/products/dto/find-products.dto.ts | 40 ++++++++------------------- src/products/products.controller.ts | 4 +-- src/products/products.service.ts | 17 ++++++------ src/utils/dto/base.dto.ts | 17 ++++++++++++ src/utils/{ => dto}/pagination.dto.ts | 2 +- src/utils/pagination-urls.ts | 17 +++--------- 6 files changed, 44 insertions(+), 53 deletions(-) create mode 100644 src/utils/dto/base.dto.ts rename src/utils/{ => dto}/pagination.dto.ts (75%) diff --git a/src/products/dto/find-products.dto.ts b/src/products/dto/find-products.dto.ts index 713105d..924a3dd 100644 --- a/src/products/dto/find-products.dto.ts +++ b/src/products/dto/find-products.dto.ts @@ -1,20 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; - -class UUIDBaseDTO { - @ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174006' }) - id: string; -} - -export class BaseDTO extends UUIDBaseDTO { - @ApiProperty() - createdAt: Date; - - @ApiProperty() - updatedAt: Date; - - @ApiProperty({ example: null }) - deletedAt: Date; -} +import { BaseDTO, UUIDBaseDTO } from 'src/utils/dto/base.dto'; export class ManufacturerDTO extends BaseDTO { @ApiProperty() @@ -24,7 +9,7 @@ export class ManufacturerDTO extends BaseDTO { description: string; } -export class ImagesDTO extends BaseDTO { +export class ImageDTO extends BaseDTO { @ApiProperty() url: string; } @@ -34,7 +19,7 @@ export class LotDTO extends BaseDTO { expirationDate: Date; } -export class CategorieDTO extends UUIDBaseDTO { +export class CategoryDTO extends UUIDBaseDTO { @ApiProperty() name: string; @@ -56,7 +41,7 @@ export class PresentationDTO extends BaseDTO { meansurementUnit: string; } -export class PresentationsDTO extends BaseDTO { +export class ProductPresentationDTO extends BaseDTO { @ApiProperty() price: number; @@ -64,7 +49,7 @@ export class PresentationsDTO extends BaseDTO { presentation: PresentationDTO; } -export class ProductListDTO extends BaseDTO { +export class ProductDTO extends BaseDTO { @ApiProperty() name: string; @@ -80,15 +65,12 @@ export class ProductListDTO extends BaseDTO { @ApiProperty({ type: ManufacturerDTO }) manufacturer: ManufacturerDTO; - @ApiProperty({ type: ImagesDTO }) - images: ImagesDTO[]; - - @ApiProperty({ type: [LotDTO] }) - lot: LotDTO[]; + @ApiProperty({ type: ImageDTO }) + images: ImageDTO[]; - @ApiProperty({ type: [CategorieDTO] }) - categories: CategorieDTO[]; + @ApiProperty({ type: [CategoryDTO] }) + categories: CategoryDTO[]; - @ApiProperty({ type: [PresentationsDTO] }) - presentations: PresentationsDTO[]; + @ApiProperty({ type: [ProductPresentationDTO] }) + presentations: ProductPresentationDTO[]; } diff --git a/src/products/products.controller.ts b/src/products/products.controller.ts index d180d23..e476eb7 100644 --- a/src/products/products.controller.ts +++ b/src/products/products.controller.ts @@ -8,7 +8,7 @@ import { } from '@nestjs/common'; import { ProductsService } from './products.service'; import { ApiOperation, ApiResponse } from '@nestjs/swagger'; -import { ProductListDTO } from './dto/find-products.dto'; +import { ProductDTO } from './dto/find-products.dto'; import { Request } from 'express'; @Controller('product') @@ -24,7 +24,7 @@ export class ProductsController { @ApiResponse({ status: 200, description: 'Products obtained correctly.', - type: [ProductListDTO], + type: [ProductDTO], }) getProducts( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, diff --git a/src/products/products.service.ts b/src/products/products.service.ts index 051a93e..d07ea0b 100644 --- a/src/products/products.service.ts +++ b/src/products/products.service.ts @@ -3,31 +3,33 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Product } from './entities/product.entity'; import { Repository } from 'typeorm'; import { Request } from 'express'; -import { Pagination } from 'src/utils/pagination.dto'; -import { getPaginationUrls } from 'src/utils/pagination-urls'; +import { PaginationDTO } from 'src/utils/dto/pagination.dto'; +import { getPaginationUrl } from 'src/utils/pagination-urls'; +import { ConfigService } from '@nestjs/config'; @Injectable() export class ProductsService { constructor( - @InjectRepository(Product) private productRepository: Repository, + @InjectRepository(Product) + private productRepository: Repository, + private configService: ConfigService, ) {} async getProducts( page: number, limit: number, req: Request, - ): Promise { + ): Promise { const count = await this.productRepository .createQueryBuilder('product') .where('product.deletedAt IS NULL') .getCount(); - - const { next, previous } = getPaginationUrls(req, page, limit, count); + const baseUrl = this.configService.get('API_URL') + `${req.path}`; + const { next, previous } = getPaginationUrl(baseUrl, page, limit, count); const products = await this.productRepository .createQueryBuilder('product') .leftJoinAndSelect('product.images', 'images') - .leftJoinAndSelect('product.lot', 'lot') .leftJoinAndSelect('product.manufacturer', 'manufacturer') .leftJoinAndSelect('product.categories', 'categories') .leftJoinAndSelect('product.presentations', 'productPresentation') @@ -35,7 +37,6 @@ export class ProductsService { .where('product.deletedAt IS NULL') .andWhere('manufacturer.deletedAt IS NULL') .andWhere('images.deletedAt IS NULL') - .andWhere('lot.deletedAt IS NULL') .andWhere('productPresentation.deletedAt IS NULL') .andWhere('presentation.deletedAt IS NULL') .skip((page - 1) * limit) diff --git a/src/utils/dto/base.dto.ts b/src/utils/dto/base.dto.ts new file mode 100644 index 0000000..291b37a --- /dev/null +++ b/src/utils/dto/base.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UUIDBaseDTO { + @ApiProperty({ example: '123e4567-e89b-12d3-a456-426614174006' }) + id: string; +} + +export class BaseDTO extends UUIDBaseDTO { + @ApiProperty() + createdAt: Date; + + @ApiProperty() + updatedAt: Date; + + @ApiProperty({ example: null }) + deletedAt: Date; +} diff --git a/src/utils/pagination.dto.ts b/src/utils/dto/pagination.dto.ts similarity index 75% rename from src/utils/pagination.dto.ts rename to src/utils/dto/pagination.dto.ts index ba84d8c..8a100b5 100644 --- a/src/utils/pagination.dto.ts +++ b/src/utils/dto/pagination.dto.ts @@ -1,4 +1,4 @@ -export class Pagination { +export class PaginationDTO { results: object[]; count: number; next: string | null; diff --git a/src/utils/pagination-urls.ts b/src/utils/pagination-urls.ts index d3f2c73..e9d7386 100644 --- a/src/utils/pagination-urls.ts +++ b/src/utils/pagination-urls.ts @@ -1,26 +1,17 @@ -import { ConfigService } from '@nestjs/config'; -import { Request } from 'express'; - -type PaginationUrls = { +type PaginationURL = { next: string | null; previous: string | null; }; -const configService = new ConfigService(); - -export function getPaginationUrls( - req: Request, +export function getPaginationUrl( + baseUrl: string, page: number, limit: number, count: number, -): PaginationUrls { +): PaginationURL { const startIndex = (page - 1) * limit; const endIndex = page * limit; - const api = configService.get('API_URL'); - - const baseUrl = `${api}${req.path}`; - const next = endIndex < count ? `${baseUrl}?page=${page + 1}&limit=${limit}` : null; From e1a47fb1792243f7dcd258d8b9a510a64adfe5cf Mon Sep 17 00:00:00 2001 From: Andres Alvarez Date: Sat, 8 Mar 2025 18:38:10 -0400 Subject: [PATCH 6/6] Refactor product service --- src/products/products.controller.ts | 41 ++++++++++++++++++++++++----- src/products/products.service.ts | 24 ++++------------- src/utils/dto/pagination.dto.ts | 13 +++++++-- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/src/products/products.controller.ts b/src/products/products.controller.ts index e476eb7..9118e63 100644 --- a/src/products/products.controller.ts +++ b/src/products/products.controller.ts @@ -7,13 +7,25 @@ import { Req, } from '@nestjs/common'; import { ProductsService } from './products.service'; -import { ApiOperation, ApiResponse } from '@nestjs/swagger'; +import { + ApiExtraModels, + ApiOkResponse, + ApiOperation, + getSchemaPath, +} from '@nestjs/swagger'; import { ProductDTO } from './dto/find-products.dto'; import { Request } from 'express'; +import { ConfigService } from '@nestjs/config'; +import { getPaginationUrl } from 'src/utils/pagination-urls'; +import { PaginationDTO } from 'src/utils/dto/pagination.dto'; @Controller('product') +@ApiExtraModels(PaginationDTO, ProductDTO) export class ProductsController { - constructor(private productsServices: ProductsService) {} + constructor( + private productsServices: ProductsService, + private configService: ConfigService, + ) {} @Get() @ApiOperation({ @@ -21,16 +33,31 @@ export class ProductsController { description: 'returns all available products (deletedAt is NULL). It will include their images, lots, presentations, manufacturers and categories.', }) - @ApiResponse({ - status: 200, + @ApiOkResponse({ description: 'Products obtained correctly.', - type: [ProductDTO], + schema: { + allOf: [ + { $ref: getSchemaPath(PaginationDTO) }, + { + properties: { + results: { + type: 'array', + items: { $ref: getSchemaPath(ProductDTO) }, + }, + }, + }, + ], + }, }) - getProducts( + async getProducts( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number, @Req() req: Request, ) { - return this.productsServices.getProducts(page, limit, req); + const baseUrl = this.configService.get('API_URL') + `${req.path}`; + const count = await this.productsServices.countProducts(); + const { next, previous } = getPaginationUrl(baseUrl, page, limit, count); + const products = await this.productsServices.getProducts(page, limit); + return { products, count, next, previous }; } } diff --git a/src/products/products.service.ts b/src/products/products.service.ts index d07ea0b..77846f5 100644 --- a/src/products/products.service.ts +++ b/src/products/products.service.ts @@ -2,31 +2,22 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Product } from './entities/product.entity'; import { Repository } from 'typeorm'; -import { Request } from 'express'; -import { PaginationDTO } from 'src/utils/dto/pagination.dto'; -import { getPaginationUrl } from 'src/utils/pagination-urls'; -import { ConfigService } from '@nestjs/config'; @Injectable() export class ProductsService { constructor( @InjectRepository(Product) private productRepository: Repository, - private configService: ConfigService, ) {} - async getProducts( - page: number, - limit: number, - req: Request, - ): Promise { - const count = await this.productRepository + async countProducts(): Promise { + return await this.productRepository .createQueryBuilder('product') .where('product.deletedAt IS NULL') .getCount(); - const baseUrl = this.configService.get('API_URL') + `${req.path}`; - const { next, previous } = getPaginationUrl(baseUrl, page, limit, count); + } + async getProducts(page: number, limit: number): Promise { const products = await this.productRepository .createQueryBuilder('product') .leftJoinAndSelect('product.images', 'images') @@ -43,11 +34,6 @@ export class ProductsService { .take(limit) .getMany(); - return { - results: products, - count, - next, - previous, - }; + return products; } } diff --git a/src/utils/dto/pagination.dto.ts b/src/utils/dto/pagination.dto.ts index 8a100b5..37a1432 100644 --- a/src/utils/dto/pagination.dto.ts +++ b/src/utils/dto/pagination.dto.ts @@ -1,6 +1,15 @@ -export class PaginationDTO { - results: object[]; +import { ApiProperty } from '@nestjs/swagger'; + +export class PaginationDTO { + @ApiProperty() + results: T[]; + + @ApiProperty() count: number; + + @ApiProperty() next: string | null; + + @ApiProperty() previous: string | null; }