Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ PGADMIN_DEFAULT_EMAIL=
PGADMIN_DEFAULT_PASSWORD=
# JWT
JWT_SECRET=
JWT_EXPIRES_IN=7d
JWT_EXPIRES_IN=7d
# API
API_URL=
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 11 additions & 29 deletions src/products/dto/find-products.dto.ts
Original file line number Diff line number Diff line change
@@ -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()
Expand All @@ -24,7 +9,7 @@ export class ManufacturerDTO extends BaseDTO {
description: string;
}

export class ImagesDTO extends BaseDTO {
export class ImageDTO extends BaseDTO {
@ApiProperty()
url: string;
}
Expand All @@ -34,7 +19,7 @@ export class LotDTO extends BaseDTO {
expirationDate: Date;
}

export class CategorieDTO extends UUIDBaseDTO {
export class CategoryDTO extends UUIDBaseDTO {
@ApiProperty()
name: string;

Expand All @@ -56,15 +41,15 @@ export class PresentationDTO extends BaseDTO {
meansurementUnit: string;
}

export class PresentationsDTO extends BaseDTO {
export class ProductPresentationDTO extends BaseDTO {
@ApiProperty()
price: number;

@ApiProperty()
presentation: PresentationDTO;
}

export class ProductListDTO extends BaseDTO {
export class ProductDTO extends BaseDTO {
@ApiProperty()
name: string;

Expand All @@ -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[];
}
46 changes: 38 additions & 8 deletions src/products/products.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,60 @@ 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 {
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({
summary: 'List all available products',
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: [ProductListDTO],
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);
const baseUrl = this.configService.get<string>('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 };
}
}
28 changes: 7 additions & 21 deletions src/products/products.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,48 +6,34 @@ import { Repository } from 'typeorm';
@Injectable()
export class ProductsService {
Comment thread
andres15alvarez marked this conversation as resolved.
constructor(
@InjectRepository(Product) private productRepository: Repository<Product>,
@InjectRepository(Product)
private productRepository: Repository<Product>,
) {}

async getProducts(
page: number,
limit: number,
): Promise<{
totalItems: number;
totalPages: number;
currentPage: number;
products: Product[];
}> {
const totalItems = await this.productRepository
async countProducts(): Promise<number> {
return await this.productRepository
.createQueryBuilder('product')
.where('product.deletedAt IS NULL')
.getCount();
}

const totalPages = Math.ceil(totalItems / limit);

async getProducts(page: number, limit: number): Promise<Product[]> {
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')
.leftJoinAndSelect('productPresentation.presentation', 'presentation')
.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)
.take(limit)
.getMany();

return {
totalItems,
totalPages,
currentPage: page,
products,
};
return products;
}
}
17 changes: 17 additions & 0 deletions src/utils/dto/base.dto.ts
Original file line number Diff line number Diff line change
@@ -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;
}
15 changes: 15 additions & 0 deletions src/utils/dto/pagination.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ApiProperty } from '@nestjs/swagger';

export class PaginationDTO<T> {
@ApiProperty()
results: T[];

@ApiProperty()
count: number;

@ApiProperty()
next: string | null;

@ApiProperty()
previous: string | null;
}
22 changes: 22 additions & 0 deletions src/utils/pagination-urls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
type PaginationURL = {
next: string | null;
previous: string | null;
};

export function getPaginationUrl(
baseUrl: string,
page: number,
limit: number,
count: number,
): PaginationURL {
const startIndex = (page - 1) * limit;
const endIndex = page * limit;

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 };
}