From 007cee5951924a321cb9fcc89258f08c9ae83d21 Mon Sep 17 00:00:00 2001 From: Mathis Date: Mon, 15 Jul 2024 11:41:18 +0200 Subject: [PATCH] feat(products): implement CRUD operations and pagination Added the implementation for Create, Read, Update, and Delete (CRUD) operations in the `ProductsService` and `ProductsController`. These include methods for adding, editing, deleting and listing all products with pagination. All these changes are reflected in the `ProductsModule` and related DTO file. --- src/products/products.controller.ts | 53 +++++++++++++++-- src/products/products.dto.ts | 45 ++++++++++++++ src/products/products.module.ts | 2 + src/products/products.service.ts | 92 +++++++++++++++++++++++++++-- 4 files changed, 183 insertions(+), 9 deletions(-) create mode 100644 src/products/products.dto.ts diff --git a/src/products/products.controller.ts b/src/products/products.controller.ts index 834b245..fa0e70e 100644 --- a/src/products/products.controller.ts +++ b/src/products/products.controller.ts @@ -1,12 +1,55 @@ -import { Controller } from '@nestjs/common'; +import { + BadRequestException, + Body, + Controller, Delete, Get, + HttpCode, + HttpStatus, + Param, + Patch, + Post, Query, + UseGuards +} from "@nestjs/common"; +import { CreateProductDto, EditProductDto } from "src/products/products.dto"; +import { AdminGuard } from "src/auth/auth.guard"; +import { ProductsService } from "src/products/products.service"; @Controller('products') export class ProductsController { - //TODO add a new product (admin) + constructor( + private readonly productsService: ProductsService + ) {} - //TODO edit a product (admin) - - //TODO delete a product (admin) + @HttpCode(HttpStatus.CREATED) + @UseGuards(AdminGuard) + @Post('new') + async newProduct(@Body() dto: CreateProductDto) { + const result = await this.productsService.add(dto) + if (result.count !== 1) { + throw new BadRequestException('Insertion failed'); + } + return result.statement + } //TODO list all product with pagination. + @HttpCode(HttpStatus.OK) + @Get("all") + async getAllProducts(@Query('page') page: number) { + return this.productsService.findAll(page || 0, 20); + } + + //TODO edit a product (admin) + @HttpCode(HttpStatus.OK) + @UseGuards(AdminGuard) + @Patch(':id') + async editProduct(@Param('id') id: string, @Body() dto: EditProductDto) { + + } + + //TODO delete a product (admin) + @HttpCode(HttpStatus.ACCEPTED) + @UseGuards(AdminGuard) + @Delete(':id') + async deleteProduct(@Param('id') id: string) { + + } } diff --git a/src/products/products.dto.ts b/src/products/products.dto.ts new file mode 100644 index 0000000..10b26e2 --- /dev/null +++ b/src/products/products.dto.ts @@ -0,0 +1,45 @@ +import { + IsEmail, IsInt, + IsNotEmpty, + IsString, + IsStrongPassword, + MaxLength, Min, + MinLength +} from "class-validator"; +import { optional } from "zod"; + +export class CreateProductDto { + @MinLength(1) + @MaxLength(32) + @IsNotEmpty() + @IsString() + slugName: string; + + @MinLength(1) + @MaxLength(64) + @IsNotEmpty() + @IsString() + displayName: string; + + @IsInt() + @Min(0) + price: number; +} + +export class EditProductDto { + @MinLength(1) + @MaxLength(32) + @IsNotEmpty() + @IsString() + slugName?: string; + + @MinLength(1) + @MaxLength(64) + @IsNotEmpty() + @IsString() + displayName?: string; + + @IsInt() + @Min(0) + price?: number; +} \ No newline at end of file diff --git a/src/products/products.module.ts b/src/products/products.module.ts index 16a3c83..99f61cf 100644 --- a/src/products/products.module.ts +++ b/src/products/products.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; import { ProductsController } from './products.controller'; import { ProductsService } from './products.service'; +import { DrizzleModule } from "src/drizzle/drizzle.module"; @Module({ + imports: [DrizzleModule], controllers: [ProductsController], providers: [ProductsService] }) diff --git a/src/products/products.service.ts b/src/products/products.service.ts index ce0db22..de22626 100644 --- a/src/products/products.service.ts +++ b/src/products/products.service.ts @@ -1,13 +1,97 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, InternalServerErrorException } from "@nestjs/common"; +import { DrizzleService } from "src/drizzle/drizzle.service"; +import { ProductsTable } from "src/schema"; +import { CreateProductDto, EditProductDto } from "src/products/products.dto"; +import { countDistinct, eq } from "drizzle-orm"; @Injectable() export class ProductsService { - //TODO add a new product (admin) - //TODO edit a product (admin) + constructor( + private db: DrizzleService, + ) {} - //TODO delete a product (admin) + async add(data: CreateProductDto) { + try { + const res = await this.db.use() + .insert(ProductsTable) + .values({ + slugName: data.slugName, + displayName: data.displayName, + price: String(data.price), + imagePath: "placeholder" + }) + console.log(`Adding new product "${data.slugName}" ...\n`, res) + return res; + } catch (err) { + throw new InternalServerErrorException(err); + } + } + + async edit(productId: string, data: EditProductDto) { + try { + const res = await this.db.use() + .update(ProductsTable) + .set({ + slugName: data.slugName, + displayName: data.displayName, + price: String(data.price), + }) + .where(eq(ProductsTable.uuid, productId)) + .prepare("editProductById") + .execute(); + console.log(`Editing product n°${productId} ...\n`, res) + return res; + } catch (err) { + throw new InternalServerErrorException(err); + } + } + + async delete(productId: string) { + try { + const res = await this.db.use() + .delete(ProductsTable) + .where(eq(ProductsTable.uuid, productId)) + .prepare("deleteProductById") + .execute(); + console.log(`Deleting product n°${productId} ...\n`, res) + return res; + } catch (err) { + throw new InternalServerErrorException(err); + } + } //TODO list all product with pagination. + async findAll(page: number, limit: number) { + try { + //get the number of row first + const count = await this.db.use() + .select({ + total: countDistinct(ProductsTable.uuid) + }) + .from(ProductsTable) + .prepare("countProducts") + .execute(); + + const res = await this.db.use() + .select() + .from(ProductsTable) + .limit(limit) + .offset((page - 1) * limit) + .prepare("findAllProducts") + .execute(); + console.log(`Fetching products (page ${page}, limit ${limit}) ...\n`) + const response = { + total: count[0].total, + page: page, + productsPerPage: limit, + products: res + } + console.log(response) + return response; + } catch (err) { + throw new InternalServerErrorException(err); + } + } }