Commit fcdb6611 authored by Kulpybaev Ilyas's avatar Kulpybaev Ilyas

lesson 87

parent 6628fe08
......@@ -10,6 +10,7 @@
"license": "ISC",
"dependencies": {
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cors": "^2.8.5",
"express": "^4.18.2",
"multer": "^1.4.5-lts.1",
......@@ -506,6 +507,11 @@
"@types/node": "*"
}
},
"node_modules/@types/validator": {
"version": "13.11.9",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.9.tgz",
"integrity": "sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz",
......@@ -1064,6 +1070,16 @@
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
},
"node_modules/class-validator": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz",
"integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==",
"dependencies": {
"@types/validator": "^13.11.8",
"libphonenumber-js": "^1.10.53",
"validator": "^13.9.0"
}
},
"node_modules/cli-highlight": {
"version": "2.1.11",
"resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz",
......@@ -2354,6 +2370,11 @@
"node": ">= 0.8.0"
}
},
"node_modules/libphonenumber-js": {
"version": "1.10.60",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.60.tgz",
"integrity": "sha512-Ctgq2lXUpEJo5j1762NOzl2xo7z7pqmVWYai0p07LvAkQ32tbPv3wb+tcUeHEiXhKU5buM4H9MXsXo6OlM6C2g=="
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
......@@ -3845,6 +3866,14 @@
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
},
"node_modules/validator": {
"version": "13.11.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz",
"integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==",
"engines": {
"node": ">= 0.10"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
......
......@@ -14,6 +14,7 @@
"license": "ISC",
"dependencies": {
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"cors": "^2.8.5",
"express": "^4.18.2",
"multer": "^1.4.5-lts.1",
......
import { DataSource } from 'typeorm';
import { Product } from '@/entities/product.entity';
import { Category } from '@/entities/category.entity';
export const appDataSource = new DataSource({
type: 'mysql',
......@@ -10,5 +11,5 @@ export const appDataSource = new DataSource({
database: 'test',
synchronize: true,
logging: true,
entities: [Product],
entities: [Product, Category],
});
import { CategoryService } from '@/services/category.service';
import { RequestHandler } from 'express';
import { plainToInstance } from 'class-transformer';
import { CategoryDto } from '@/dto/category.dto';
export class CategoryController {
private service: CategoryService;
constructor() {
this.service = new CategoryService();
}
getCategories: RequestHandler = async (req, res): Promise<void> => {
const categories = await this.service.getCategories();
res.send(categories);
};
createCategory: RequestHandler = async (req, res): Promise<void> => {
const categoryDto = plainToInstance(CategoryDto, req.body);
const category = await this.service.createCategory(categoryDto);
res.send(category);
};
}
import { RequestHandler } from 'express';
import { ProductService } from '@/services/product.service';
import { plainToInstance } from 'class-transformer';
import { ProductDto } from '@/dto/ProductDto';
import { ProductDto } from '@/dto/product.dto';
export class ProductController {
private service: ProductService;
......@@ -25,9 +25,18 @@ export class ProductController {
};
createProduct: RequestHandler = async (req, res): Promise<void> => {
const productDto = plainToInstance(ProductDto, req.body);
if (req.file) productDto.image = req.file.filename;
const product = await this.service.createProduct(productDto);
res.send(product);
try {
const productDto = plainToInstance(ProductDto, req.body);
if (req.file) productDto.image = req.file.filename;
const product = await this.service.createProduct(productDto);
res.send(product);
} catch (e) {
if (Array.isArray(e)) {
console.log(e);
res.status(400).send(e);
} else {
res.status(500).send(e);
}
}
};
}
import { Expose } from 'class-transformer';
@Expose()
export class ProductDto {
export class CategoryDto {
@Expose()
title!: string;
@Expose()
description!: string;
price!: number;
image!: string;
}
import { Expose } from 'class-transformer';
import { IsNotEmpty, IsNumberString, IsOptional, IsString } from 'class-validator';
@Expose()
export class ProductDto {
@IsNotEmpty({ message: 'Продукт не может быть создан без названия!' })
@IsString({ message: 'Название должно быть строкой' })
title!: string;
@IsOptional()
description!: string;
@IsNotEmpty({ message: 'Укажите цену продукта' })
@IsNumberString({}, { message: 'Укажите корректную цену' })
price!: number;
@IsOptional()
image!: string;
}
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity('categories')
export class Category {
@PrimaryGeneratedColumn()
id!: number;
@Column()
title!: string;
@Column({ nullable: true })
description?: string;
}
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn } from 'typeorm';
import { Category } from '@/entities/category.entity';
@Entity({ name: 'products' })
export class Product {
......@@ -16,4 +17,11 @@ export class Product {
@Column()
description!: string;
@ManyToOne(() => Category)
@JoinColumn({ name: 'categoryId' })
category!: Category;
@Column({ nullable: true })
categoryId?: number;
}
import { ValidationError } from 'class-validator';
interface IError {
type: string;
messages: string[];
}
export const formatErrors = (errors: ValidationError[]) => {
const updatedErrors: IError[] = [];
errors.forEach((e) => {
if (e.constraints) {
const error: IError = {
type: e.property,
messages: Object.values(e.constraints),
};
updatedErrors.push(error);
}
});
return updatedErrors;
};
......@@ -3,11 +3,12 @@ import logger from './middlewares/logger';
import { ArticleRoute } from './routes/article.route';
import { ProductRoute } from '@/routes/product.route';
import cors from 'cors';
import { CategoryRoute } from '@/routes/category.route';
const app = new App({
port: 8000,
middlewares: [logger(), cors()],
controllers: [new ArticleRoute(), new ProductRoute()],
controllers: [new ArticleRoute(), new ProductRoute(), new CategoryRoute()],
});
app.listen();
export interface ICategory {
id: number;
title: string;
description?: string;
}
import { Repository } from 'typeorm';
import { Category } from '@/entities/category.entity';
import { appDataSource } from '@/config/dataSource';
import { CategoryDto } from '@/dto/category.dto';
import { ICategory } from '@/interfaces/ICategory.interface';
export class CategoryRepository extends Repository<Category> {
constructor() {
super(Category, appDataSource.createEntityManager());
}
async getCategories(): Promise<ICategory[]> {
return await this.find();
}
async createCategory(categoryDto: CategoryDto): Promise<ICategory> {
return await this.save(categoryDto);
}
}
import { IProduct } from '@/interfaces/IProduct.interface';
import { ProductDto } from '@/dto/ProductDto';
import { ProductDto } from '@/dto/product.dto';
import { Repository } from 'typeorm';
import { appDataSource } from '@/config/dataSource';
import { Product } from '@/entities/product.entity';
......@@ -9,7 +9,7 @@ export class ProductRepository extends Repository<Product> {
super(Product, appDataSource.createEntityManager());
}
async getProducts(): Promise<IProduct[]> {
return await this.find();
return await this.find({ relations: { category: true } });
}
async getProduct(id: number): Promise<IProduct> {
......@@ -24,14 +24,6 @@ export class ProductRepository extends Repository<Product> {
}
async createProduct(productDto: ProductDto): Promise<IProduct> {
const { title, description, price, image } = productDto;
const product = new Product();
product.image = image;
product.title = title;
product.price = price;
product.description = description;
await this.save(product);
return product;
return await this.save(productDto);
}
}
import { IRoute } from '@/interfaces/IRoute.interface';
import { Router } from 'express';
import { CategoryController } from '@/controllers/category.controller';
export class CategoryRoute implements IRoute {
public path = '/categories';
public router = Router();
private controller: CategoryController;
constructor() {
this.controller = new CategoryController();
this.init();
}
private init() {
this.router.get('/', this.controller.getCategories);
this.router.post('/', this.controller.createCategory);
}
}
import { CategoryRepository } from '@/repositories/category.repository';
import { ICategory } from '@/interfaces/ICategory.interface';
import { CategoryDto } from '@/dto/category.dto';
export class CategoryService {
private repository: CategoryRepository;
constructor() {
this.repository = new CategoryRepository();
}
getCategories = async (): Promise<ICategory[]> => {
return await this.repository.getCategories();
};
createCategory = async (categoryDto: CategoryDto): Promise<ICategory> => {
return await this.repository.createCategory(categoryDto);
};
}
import { IProduct } from '@/interfaces/IProduct.interface';
import { ProductDto } from '@/dto/ProductDto';
import { ProductDto } from '@/dto/product.dto';
import { ProductRepository } from '@/repositories/product.repository';
import { validate } from 'class-validator';
import { formatErrors } from '@/helpers/formatErrors';
export class ProductService {
private repository: ProductRepository;
......@@ -20,6 +22,8 @@ export class ProductService {
}
public async createProduct(productDto: ProductDto): Promise<IProduct> {
const errors = await validate(productDto, { whitelist: true, validationError: { target: false, value: false } });
if (errors.length) throw formatErrors(errors);
return await this.repository.createProduct(productDto);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment