Repository: naukmagymdb/gym
Pull request #46: Products
Author: login: MaksymSemk
Created: 2025-04-28T15:49:40Z | Merged: 2025-04-28T19:54:36Z

created module to work with products(sportTovar)


Diff:
diff --git a/packages/backend/package.json b/packages/backend/package.json
index fe3d008..dee3b73 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -23,6 +23,7 @@
     "@nestjs/common": "^10.0.0",
     "@nestjs/config": "^4.0.2",
     "@nestjs/core": "^10.0.0",
+    "@nestjs/mapped-types": "^2.1.0",
     "@nestjs/passport": "^11.0.5",
     "@nestjs/platform-express": "^10.0.0",
     "bcrypt": "^5.1.1",
@@ -47,6 +48,7 @@
     "@types/jest": "^29.5.2",
     "@types/node": "^20.3.1",
     "@types/passport-local": "^1.0.38",
+    "@types/pg-promise": "^5.4.3",
     "@types/supertest": "^6.0.0",
     "@typescript-eslint/eslint-plugin": "^8.0.0",
     "@typescript-eslint/parser": "^8.0.0",
diff --git a/packages/backend/src/repositories/products/dto/create-product.dto.ts b/packages/backend/src/repositories/products/dto/create-product.dto.ts
new file mode 100644
index 0000000..8a077c3
--- /dev/null
+++ b/packages/backend/src/repositories/products/dto/create-product.dto.ts
@@ -0,0 +1,11 @@
+import { IsNotEmpty, IsString } from "class-validator";
+
+export class CreateProductDto {
+    constructor(partial: Partial<CreateProductDto>) {
+        Object.assign(this, partial);
+    }
+
+    @IsString()
+    @IsNotEmpty()
+    good_name: string;
+}
diff --git a/packages/backend/src/repositories/products/dto/update-product.dto.ts b/packages/backend/src/repositories/products/dto/update-product.dto.ts
new file mode 100644
index 0000000..e2d43fc
--- /dev/null
+++ b/packages/backend/src/repositories/products/dto/update-product.dto.ts
@@ -0,0 +1,4 @@
+import { PartialType } from '@nestjs/mapped-types';
+import { CreateProductDto } from './create-product.dto';
+
+export class UpdateProductDto extends PartialType(CreateProductDto) {}
diff --git a/packages/backend/src/repositories/products/products.controller.ts b/packages/backend/src/repositories/products/products.controller.ts
new file mode 100644
index 0000000..d891954
--- /dev/null
+++ b/packages/backend/src/repositories/products/products.controller.ts
@@ -0,0 +1,34 @@
+import { Body, Controller, Delete, Get, Param, Patch, Post } from '@nestjs/common';
+import { CreateProductDto } from './dto/create-product.dto';
+import { UpdateProductDto } from './dto/update-product.dto';
+import { ProductsService } from './products.service';
+
+@Controller('products')
+export class ProductsController {
+  constructor(private readonly productsService: ProductsService) {}
+
+  @Post()
+  create(@Body() createProductDto: CreateProductDto) {
+    return this.productsService.create(createProductDto);
+  }
+
+  @Get()
+  findAll() {
+    return this.productsService.findAll();
+  }
+
+  @Get(':id')
+  findOne(@Param('id') id: string) {
+    return this.productsService.findOne(+id);
+  }
+
+  @Patch(':id')
+  update(@Param('id') id: string, @Body() updateProductDto: UpdateProductDto) {
+    return this.productsService.update(+id, updateProductDto);
+  }
+
+  @Delete(':id')
+  remove(@Param('id') id: string) {
+    return this.productsService.remove(+id);
+  }
+}
diff --git a/packages/backend/src/repositories/products/products.module.ts b/packages/backend/src/repositories/products/products.module.ts
new file mode 100644
index 0000000..1697bce
--- /dev/null
+++ b/packages/backend/src/repositories/products/products.module.ts
@@ -0,0 +1,9 @@
+import { Module } from '@nestjs/common';
+import { ProductsService } from './products.service';
+import { ProductsController } from './products.controller';
+
+@Module({
+  controllers: [ProductsController],
+  providers: [ProductsService],
+})
+export class ProductsModule {}
diff --git a/packages/backend/src/repositories/products/products.service.ts b/packages/backend/src/repositories/products/products.service.ts
new file mode 100644
index 0000000..7415339
--- /dev/null
+++ b/packages/backend/src/repositories/products/products.service.ts
@@ -0,0 +1,105 @@
+import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateProductDto } from './dto/create-product.dto';
+import { UpdateProductDto } from './dto/update-product.dto';
+
+@Injectable()
+export class ProductsService {
+
+  constructor(
+      private readonly databaseService: DatabaseService
+    ) {}
+
+    async create(createProductDto: CreateProductDto) {
+      const db = this.databaseService.getDb();
+    
+      const productExists = await this.isPresentInDatabase(createProductDto.good_name);
+    
+      if (productExists) {
+        throw new ConflictException("Cannot create item as it already exists");
+      }
+    
+      const query = `
+        INSERT INTO products (good_name)
+        VALUES ($1)
+        RETURNING *;
+      `;
+
+      const result = await db.one(query, [createProductDto.good_name]);
+      return result;
+    }
+
+    async findAll() {
+      const db = this.databaseService.getDb();
+      return db.query("SELECT * FROM products");
+    }
+
+    async findOne(id: number) {
+      const db = this.databaseService.getDb();
+    
+      const productIdExists = await this.isValidProductId(id);
+      if (!productIdExists) {
+        throw new NotFoundException("Invalid ID provided. Please provide a valid ID.");
+      }
+
+      const query = `
+        SELECT *
+        FROM products
+        WHERE good_id = $1
+      `;
+
+      const result = await db.one(query, [id]);
+      return result;
+    }
+
+    async update(id: number, updateProductDto: UpdateProductDto) {
+      const db = this.databaseService.getDb();
+    
+      const productExists = await this.isPresentInDatabase(updateProductDto.good_name);
+      if (productExists) {
+        throw new ConflictException("Cannot update item as it already exists");
+      }
+
+      const productIdExists = await this.isValidProductId(id);
+      if (!productIdExists) {
+        throw new NotFoundException("Invalid ID provided. Please provide a valid ID.");
+      }
+
+      const query = `
+        UPDATE products
+        SET good_name = $1
+        WHERE good_id = $2
+        RETURNING *;
+      `;
+
+      const result = await db.one(query, [updateProductDto.good_name, id]);
+      return result;
+    }
+
+    async remove(id: number) {
+      const db = this.databaseService.getDb();
+    
+      const productIdExists = await this.isValidProductId(id);
+      if (!productIdExists) {
+        throw new NotFoundException("Invalid ID provided. Please provide a valid ID.");
+      }
+
+      const query = `
+        DELETE FROM products
+        WHERE good_id = $1
+        RETURNING *;
+      `;
+
+      return db.one(query, [id]);
+    }
+
+    private async isValidProductId(id: number): Promise<boolean> {
+      const products = await this.findAll();
+      return products.some(product => product.good_id == id);
+    }
+
+    private async isPresentInDatabase(name: string): Promise<boolean> {
+      const products = await this.findAll();
+      return products.some(product => product.good_name === name);
+    }
+}
diff --git a/packages/backend/src/repositories/repository.module.ts b/packages/backend/src/repositories/repository.module.ts
index 9481480..4bf1350 100644
--- a/packages/backend/src/repositories/repository.module.ts
+++ b/packages/backend/src/repositories/repository.module.ts
@@ -1,9 +1,10 @@
 import { Module } from '@nestjs/common';
 import { DepartmentModule } from './departments/department.module';
+import { ProductsModule } from './products/products.module';
 import { StaffModule } from './staff/staff.module';
 import { VisitorModule } from './visitors/visitor.module';
 
 @Module({
-  imports: [DepartmentModule, StaffModule, VisitorModule],
+  imports: [DepartmentModule, StaffModule, VisitorModule, ProductsModule],
 })
 export class RepositoryModule {}
diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock
index 8b1e388..a4773b6 100644
--- a/packages/backend/yarn.lock
+++ b/packages/backend/yarn.lock
@@ -721,6 +721,11 @@
     path-to-regexp "3.3.0"
     tslib "2.8.1"
 
+"@nestjs/mapped-types@^2.1.0":
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.1.0.tgz#b9b536b7c3571567aa1d0223db8baa1a51505a19"
+  integrity sha512-W+n+rM69XsFdwORF11UqJahn4J3xi4g/ZEOlJNL6KoW5ygWSmBB2p0S2BZ4FQeS/NDH72e6xIcu35SfJnE8bXw==
+
 "@nestjs/passport@^11.0.5":
   version "11.0.5"
   resolved "https://registry.yarnpkg.com/@nestjs/passport/-/passport-11.0.5.tgz#dd3e506c2fb7ddc80fd1321c01cc1a0ca6d6b609"
@@ -1053,6 +1058,13 @@
   dependencies:
     "@types/express" "*"
 
+"@types/pg-promise@^5.4.3":
+  version "5.4.3"
+  resolved "https://registry.yarnpkg.com/@types/pg-promise/-/pg-promise-5.4.3.tgz#e9628ff43fe7738ee57ac00e11982139b2fb5c5f"
+  integrity sha512-ABgy5hzmcyRZxLNG6HUafpxbpmXGeVJ5dopKwssEsFeA7rpRakdoodt75HJNIjQB4He+s3PTfG1AoWQWm7OuCg==
+  dependencies:
+    pg-promise "*"
+
 "@types/qs@*":
   version "6.9.18"
   resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.18.tgz#877292caa91f7c1b213032b34626505b746624c2"
@@ -4236,7 +4248,7 @@ pg-pool@^3.8.0:
   resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.8.0.tgz#e6bce7fc4506a8d6106551363fc5283e5445b776"
   integrity sha512-VBw3jiVm6ZOdLBTIcXLNdSotb6Iy3uOCwDGFAksZCXmi10nyRvnP2v3jl4d+IsLYRyXf6o9hIm/ZtUzlByNUdw==
 
-pg-promise@^11.13.0:
+pg-promise@*, pg-promise@^11.13.0:
   version "11.13.0"
   resolved "https://registry.yarnpkg.com/pg-promise/-/pg-promise-11.13.0.tgz#90c2c7c5cd638a3ca6e8cde9f18d29dae333297d"
   integrity sha512-NWCsh1gnELfYRF5hNhfXPcSxuCk9C3FyM9MhmGkVTmepczAC2aXuBkyXhipVlHzp0V/IVzyCZOrlH48Ma3i7YQ==


Reviews:
user: login: myroslavve
body: Один невеличкий коментар а так все добре помоєму.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T16:02:11Z
user: login: EWOKWICKET
body: і перенеси products/ в repositories
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T16:08:19Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T16:14:30Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T16:15:05Z
user: login: MaksymSemk
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T16:16:47Z
user: login: myroslavve
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T16:24:12Z
user: login: EWOKWICKET
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-28T19:51:45Z

Review comments:
diff_hunk: @@ -0,0 +1,105 @@
+import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateProductDto } from './dto/create-product.dto';
+import { UpdateProductDto } from './dto/update-product.dto';
+
+@Injectable()
+export class ProductsService {
+
+  constructor(
+      private readonly databaseService: DatabaseService
+    ) {}
+
+    async create(createProductDto: CreateProductDto) {
+      const db = this.databaseService.getDb();
+    
+      const productExists = await this.isPresentInDatabase(createProductDto.good_name);
+    
+      if (productExists) {
+        throw new ConflictException("Cannot create item as it already exists");
+      }
+    
+      const query = `
+        INSERT INTO products (good_name)
path: packages/backend/src/products/products.service.ts
user: login: myroslavve
body: Може краще product_name i product_id щоб збігалось з назвою таблиці
created_at: 2025-04-28T16:01:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,6 +21,7 @@ import { RepositoryModule } from './repositories/repository.module';
     AuthModule,
     AccountsModule,
     RepositoryModule,
+    ProductsModule
path: packages/backend/src/app.module.ts
user: login: EWOKWICKET
body: тут ProductsModule імпортуй не в AppModule, а в RepositoryModule. Там усі репозиторії забрані
created_at: 2025-04-28T16:04:27Z
author_association: CONTRIBUTOR
path: packages/backend/src/products/products.service.ts
user: login: EWOKWICKET
body: і тут, я думаю, перейменуй з service на repository, як в інших файлах
created_at: 2025-04-28T16:07:39Z
author_association: CONTRIBUTOR
path: packages/backend/src/products/products.service.ts
user: login: EWOKWICKET
body: щоб кожен раз не отримувати пул, краще створи поле і в конструкторі заповни його
created_at: 2025-04-28T16:13:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateProductDto } from './dto/create-product.dto';
+import { UpdateProductDto } from './dto/update-product.dto';
+
+@Injectable()
+export class ProductsService {
+
+  constructor(
+      private readonly databaseService: DatabaseService
+    ) {}
+
+    async create(createProductDto: CreateProductDto) {
+      const db = this.databaseService.getDb();
+    
+      const productExists = await this.isPresentInDatabase(createProductDto.good_name);
+    
+      if (productExists) {
+        throw new ConflictException("Cannot create item as it already exists");
+      }
+    
+      const query = `
+        INSERT INTO products (good_name)
path: packages/backend/src/products/products.service.ts
user: login: MaksymSemk
body: Я робив по документу з rm моделлю і там такі назви атрибутів. В нас не буде проблем, якщо назви атрибутів будуть не ті, як в документі?
created_at: 2025-04-28T16:14:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -20,6 +21,7 @@ import { RepositoryModule } from './repositories/repository.module';
     AuthModule,
     AccountsModule,
     RepositoryModule,
+    ProductsModule
path: packages/backend/src/app.module.ts
user: login: MaksymSemk
body: Окей👍
created_at: 2025-04-28T16:15:05Z
author_association: CONTRIBUTOR
path: packages/backend/src/products/products.service.ts
user: login: MaksymSemk
body: Без питань. Але чому треба перейменовувати, це якась домовленість?

created_at: 2025-04-28T16:16:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,105 @@
+import { ConflictException, Injectable, NotFoundException } from '@nestjs/common';
+import { DatabaseService } from 'src/database/database.service';
+import { CreateProductDto } from './dto/create-product.dto';
+import { UpdateProductDto } from './dto/update-product.dto';
+
+@Injectable()
+export class ProductsService {
+
+  constructor(
+      private readonly databaseService: DatabaseService
+    ) {}
+
+    async create(createProductDto: CreateProductDto) {
+      const db = this.databaseService.getDb();
+    
+      const productExists = await this.isPresentInDatabase(createProductDto.good_name);
+    
+      if (productExists) {
+        throw new ConflictException("Cannot create item as it already exists");
+      }
+    
+      const query = `
+        INSERT INTO products (good_name)
path: packages/backend/src/products/products.service.ts
user: login: myroslavve
body: a сорі, я забув що у нас такі є вимоги, так тоді хай буде
created_at: 2025-04-28T16:24:12Z
author_association: CONTRIBUTOR
path: packages/backend/src/products/products.service.ts
user: login: EWOKWICKET
body: ну я не знаю про .repository, але зазвичай під ціль фалйу називають. У нас там саме запити до бд по певній табличці, тому краще всі їх назвати repository. А от .service можемо створити, щоб якісь допоміжні штуки робити для .repository
created_at: 2025-04-28T16:26:15Z
author_association: CONTRIBUTOR

PR comments:
user: login: EWOKWICKET
created_at: 2025-04-28T16:21:20Z
body: в принципі норм, але можна оптимізувати, сильно скоротити методи в сервісі:
деякі перевірки, я думаю, зайві. Краще зробити точечний запит за айді, а не шукати всі і фільтрувати
і я думаю не робити перевірку і кидати помилку якщо щось не те, а робити запит методом oneOrNull(чи щось схоже), щоб це робити одним запитом. А якщо null, то вже якось обробляти і кидат помилку
author_association: CONTRIBUTOR
user: login: myroslavve
created_at: 2025-04-28T17:17:13Z
body: Fixes #40
Пишіть от так щоб автоматично issue потім закривали які ми зробили. (гітхаб автоматично закриває коли пулл реквест з таким текстом мерджать)
https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue
author_association: CONTRIBUTOR