├── mysql └── init.sql ├── .dockerignore ├── .gitignore ├── .env ├── src ├── auth │ ├── interfaces │ │ └── auth.interface.ts │ ├── utils │ │ └── passport.use.ts │ ├── auth.router.ts │ ├── strategies │ │ ├── jwt.strategy.ts │ │ └── login.strategy.ts │ ├── controllers │ │ └── auth.controller.ts │ └── services │ │ └── auth.service.ts ├── config │ ├── base.dto.ts │ ├── base.entity.ts │ ├── base.service.ts │ ├── data.source.ts │ └── config.ts ├── shared │ ├── router │ │ └── router.ts │ ├── middlewares │ │ └── shared.middleware.ts │ └── response │ │ └── http.response.ts ├── customer │ ├── dto │ │ └── customer.dto.ts │ ├── entitites │ │ └── customer.entity.ts │ ├── middlewares │ │ └── customer.middleware.ts │ ├── customer.router.ts │ ├── services │ │ └── customer.service.ts │ └── controllers │ │ └── customer.controller.ts ├── product │ ├── dto │ │ └── product.dto.ts │ ├── middlewares │ │ └── product.middleware.ts │ ├── entities │ │ └── product.entity.ts │ ├── services │ │ └── product.service.ts │ ├── product.router.ts │ └── controllers │ │ └── product.controller.ts ├── category │ ├── dto │ │ └── category.dto.ts │ ├── entities │ │ └── category.entity.ts │ ├── middlewares │ │ └── category.middleware.ts │ ├── category.router.ts │ ├── services │ │ └── category.service.ts │ └── controllers │ │ └── category.controller.ts ├── purchase │ ├── dto │ │ ├── purchase.dto.ts │ │ └── purchase-product.dto.ts │ ├── entitites │ │ ├── purchases-products.entity.ts │ │ └── purchase.entity.ts │ ├── middlewares │ │ ├── purchase.middleware.ts │ │ └── purchase-product.middleware.ts │ ├── services │ │ ├── purchase.service.ts │ │ └── purchase-product.service.ts │ ├── purchase.router.ts │ ├── purchase-product.router.ts │ └── controllers │ │ ├── purchase.controller.ts │ │ └── purchase-product.controller.ts ├── user │ ├── dto │ │ └── user.dto.ts │ ├── entities │ │ └── user.entity.ts │ ├── middlewares │ │ └── user.middleware.ts │ ├── user.router.ts │ ├── services │ │ └── user.service.ts │ └── controllers │ │ └── user.controller.ts └── server.ts ├── .production.env ├── Dockerfile ├── docker-compose.yml ├── package.json ├── README.md └── tsconfig.json /mysql/init.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS codrr_db; -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src/migrations/* 3 | dist -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | .vscode/* 4 | anotaciones.txt 5 | src/migrations -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PORT=8000 2 | ENV=develop 3 | DB_PORT=3312 4 | DB_HOST=192.168.0.104 5 | DB_DATABASE=codrr_db 6 | DB_USER=ucodrr 7 | DB_PASSWORD=secret 8 | 9 | JWT_SECRET=codrr@2022 -------------------------------------------------------------------------------- /src/auth/interfaces/auth.interface.ts: -------------------------------------------------------------------------------- 1 | import { RoleType } from "../../user/dto/user.dto"; 2 | 3 | export interface PayloadToken { 4 | role: RoleType; 5 | sub: string; 6 | } 7 | -------------------------------------------------------------------------------- /.production.env: -------------------------------------------------------------------------------- 1 | PORT=8000 2 | ENV=production 3 | DB_PORT=3312 4 | DB_HOST=192.168.0.104 5 | DB_DATABASE=codrr_db 6 | DB_USER=ucodrr 7 | DB_PASSWORD=secret 8 | 9 | JWT_SECRET=codrr@2022 -------------------------------------------------------------------------------- /src/config/base.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsDate, IsOptional, IsUUID } from "class-validator"; 2 | 3 | export class BaseDTO { 4 | @IsUUID() 5 | @IsOptional() 6 | id!: string; 7 | 8 | @IsDate() 9 | @IsOptional() 10 | createdAt!: Date; 11 | 12 | @IsDate() 13 | @IsOptional() 14 | updatedAt!: Date; 15 | } 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | 3 | RUN npm install -g ts-node 4 | 5 | WORKDIR /usr/src/app 6 | 7 | COPY package*.json ./ 8 | 9 | COPY . . 10 | 11 | RUN npm install 12 | 13 | ENV NODE_ENV=production 14 | 15 | RUN npm run m:gen -- src/migrations/InitDB 16 | 17 | RUN npm run m:run 18 | 19 | EXPOSE 8000 20 | 21 | CMD ["npm","start"] -------------------------------------------------------------------------------- /src/auth/utils/passport.use.ts: -------------------------------------------------------------------------------- 1 | import passport, { Strategy } from "passport"; 2 | 3 | type TypeStrategy = { new (params: U, callback: X): T }; 4 | 5 | export function PassportUse( 6 | name: string, 7 | Strategy: TypeStrategy, 8 | params: U, 9 | callback: X 10 | ) { 11 | passport.use(name, new Strategy(params, callback)); 12 | } 13 | -------------------------------------------------------------------------------- /src/shared/router/router.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | 3 | export class BaseRouter { 4 | public router: Router; 5 | public controller: T; 6 | public middleware: U; 7 | constructor(TController: { new (): T }, UMiddleware: { new (): U }) { 8 | this.router = Router(); 9 | this.controller = new TController(); 10 | this.middleware = new UMiddleware(); 11 | this.routes(); 12 | } 13 | 14 | routes() {} 15 | } 16 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.1" 2 | 3 | services: 4 | codrr_db: 5 | image: mysql:5.7 6 | volumes: 7 | - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql 8 | command: --init-file /docker-entrypoint-initdb.d/init.sql 9 | ports: 10 | - "3312:3306" 11 | environment: 12 | MYSQL_DATABASE: codrr_db 13 | MYSQL_ROOT_USER: ucodrr 14 | MYSQL_USER: ucodrr 15 | MYSQL_ROOT_PASSWORD: secret 16 | MYSQL_PASSWORD: secret 17 | -------------------------------------------------------------------------------- /src/customer/dto/customer.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from "class-validator"; 2 | import { BaseDTO } from "../../config/base.dto"; 3 | import { CategoryEntity } from "../../category/entities/category.entity"; 4 | import { UserEntity } from "../../user/entities/user.entity"; 5 | 6 | export class CustomerDTO extends BaseDTO { 7 | @IsNotEmpty() 8 | address!: string; 9 | 10 | @IsNotEmpty() 11 | dni!: number; 12 | 13 | @IsNotEmpty() 14 | user!: UserEntity; 15 | } 16 | -------------------------------------------------------------------------------- /src/product/dto/product.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from "class-validator"; 2 | import { BaseDTO } from "../../config/base.dto"; 3 | import { CategoryEntity } from "../../category/entities/category.entity"; 4 | 5 | export class ProductDTO extends BaseDTO { 6 | @IsNotEmpty() 7 | productName!: string; 8 | 9 | @IsNotEmpty() 10 | description!: string; 11 | 12 | @IsNotEmpty() 13 | price!: number; 14 | 15 | @IsNotEmpty() 16 | category!: CategoryEntity; 17 | } 18 | -------------------------------------------------------------------------------- /src/category/dto/category.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsOptional } from "class-validator"; 2 | import { BaseDTO } from "../../config/base.dto"; 3 | 4 | export class CategoryDTO extends BaseDTO { 5 | @IsNotEmpty() 6 | categoryName!: string; 7 | 8 | @IsOptional() 9 | colorBadge?: ColorBadge; 10 | } 11 | 12 | export enum ColorBadge { 13 | DEFAULT = "default", 14 | PRIMARY = "primary", 15 | SECONDARY = "secondary", 16 | ERROR = "error", 17 | INFO = "info", 18 | SUCCESS = "success", 19 | WARNING = "warning", 20 | } 21 | -------------------------------------------------------------------------------- /src/config/base.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CreateDateColumn, 3 | PrimaryGeneratedColumn, 4 | UpdateDateColumn, 5 | } from "typeorm"; 6 | 7 | export abstract class BaseEntity { 8 | @PrimaryGeneratedColumn("uuid") 9 | id!: string; 10 | 11 | @CreateDateColumn({ 12 | name: "created_at", 13 | type: "timestamp", 14 | }) 15 | createdAt!: Date; 16 | 17 | @UpdateDateColumn({ 18 | name: "updated_at", 19 | type: "timestamp", 20 | }) 21 | updatedAt!: Date; 22 | } 23 | 24 | //id 25 | //created_at 26 | //updated_at 27 | -------------------------------------------------------------------------------- /src/auth/auth.router.ts: -------------------------------------------------------------------------------- 1 | import { SharedMiddleware } from "../shared/middlewares/shared.middleware"; 2 | import { BaseRouter } from "../shared/router/router"; 3 | import { AuthController } from "./controllers/auth.controller"; 4 | 5 | export class AuthRouter extends BaseRouter { 6 | constructor() { 7 | super(AuthController, SharedMiddleware); 8 | } 9 | 10 | routes(): void { 11 | this.router.post("/login", this.middleware.passAuth("login"), (req, res) => 12 | this.controller.login(req, res) 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/config/base.service.ts: -------------------------------------------------------------------------------- 1 | import { EntityTarget, Repository } from "typeorm"; 2 | import { BaseEntity } from "./base.entity"; 3 | import { ConfigServer } from "./config"; 4 | 5 | export class BaseService extends ConfigServer { 6 | public execRepository: Promise>; 7 | constructor(private getEntity: EntityTarget) { 8 | super(); 9 | this.execRepository = this.initRepository(getEntity); 10 | } 11 | 12 | async initRepository(e: EntityTarget): Promise> { 13 | const getConn = await this.initConnect; 14 | return getConn.getRepository(e); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/category/entities/category.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, OneToMany } from "typeorm"; 2 | import { BaseEntity } from "../../config/base.entity"; 3 | import { ProductEntity } from "../../product/entities/product.entity"; 4 | import { ColorBadge } from "../dto/category.dto"; 5 | 6 | @Entity({ name: "category" }) 7 | export class CategoryEntity extends BaseEntity { 8 | @Column() 9 | categoryName!: string; 10 | 11 | @Column({ type: "enum", enum: ColorBadge, default: ColorBadge.PRIMARY }) 12 | colorBadge?: ColorBadge; 13 | 14 | @OneToMany(() => ProductEntity, (product) => product.category) 15 | products!: ProductEntity[]; 16 | } 17 | -------------------------------------------------------------------------------- /src/purchase/dto/purchase.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from "class-validator"; 2 | import { BaseDTO } from "../../config/base.dto"; 3 | import { CustomerEntity } from "../../customer/entitites/customer.entity"; 4 | 5 | export class PurchaseDTO extends BaseDTO { 6 | @IsNotEmpty() 7 | status!: StatusPurchare; 8 | 9 | @IsNotEmpty() 10 | paymentMethod!: string; 11 | 12 | @IsNotEmpty() 13 | customer!: CustomerEntity; 14 | } 15 | 16 | export enum StatusPurchare { 17 | IN_CART = "IN_CART", 18 | PENDING_PAYMENT = "PENDING_PAYMENT", 19 | PENDDING_APPROVED = "PENDING_APPROVED", 20 | APPROVED = "APPROVED", 21 | ERROR = "ERROR", 22 | } 23 | -------------------------------------------------------------------------------- /src/purchase/dto/purchase-product.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty, IsOptional } from "class-validator"; 2 | import { BaseDTO } from "../../config/base.dto"; 3 | import { CustomerEntity } from "../../customer/entitites/customer.entity"; 4 | import { ProductEntity } from "../../product/entities/product.entity"; 5 | import { PurchaseEntity } from "../entitites/purchase.entity"; 6 | 7 | export class PurchaseProductDTO extends BaseDTO { 8 | @IsNotEmpty() 9 | quantityProduct!: number; 10 | 11 | @IsOptional() 12 | totalPrice?: number; 13 | 14 | @IsOptional() 15 | purchase?: PurchaseEntity; 16 | 17 | @IsOptional() 18 | product?: ProductEntity; 19 | } 20 | -------------------------------------------------------------------------------- /src/user/dto/user.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsNotEmpty } from "class-validator"; 2 | import { BaseDTO } from "../../config/base.dto"; 3 | 4 | export class UserDTO extends BaseDTO { 5 | @IsNotEmpty() 6 | name!: string; 7 | 8 | @IsNotEmpty() 9 | lastname!: string; 10 | 11 | @IsNotEmpty() 12 | username!: string; 13 | 14 | @IsNotEmpty() 15 | email!: string; 16 | 17 | @IsNotEmpty() 18 | password!: string; 19 | 20 | @IsNotEmpty() 21 | city!: string; 22 | 23 | @IsNotEmpty() 24 | province!: string; 25 | 26 | @IsNotEmpty() 27 | role!: RoleType; 28 | } 29 | 30 | export enum RoleType { 31 | USER = "USER", 32 | CUSTOMER = "CUSTOMER", 33 | ADMIN = "ADMIN", 34 | } 35 | -------------------------------------------------------------------------------- /src/customer/entitites/customer.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, JoinColumn, OneToMany, OneToOne } from "typeorm"; 2 | import { BaseEntity } from "../../config/base.entity"; 3 | import { PurchaseEntity } from "../../purchase/entitites/purchase.entity"; 4 | import { UserEntity } from "../../user/entities/user.entity"; 5 | 6 | @Entity({ name: "customer" }) 7 | export class CustomerEntity extends BaseEntity { 8 | @Column() 9 | address!: string; 10 | 11 | @Column() 12 | dni!: number; 13 | 14 | @OneToOne(() => UserEntity, (user) => user.customer) 15 | @JoinColumn({ name: "user_id" }) 16 | user!: UserEntity; 17 | 18 | @OneToMany(() => PurchaseEntity, (purchase) => purchase.customer) 19 | purchases!: PurchaseEntity[]; 20 | } 21 | -------------------------------------------------------------------------------- /src/purchase/entitites/purchases-products.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, JoinColumn } from "typeorm"; 2 | import { BaseEntity } from "../../config/base.entity"; 3 | import { ProductEntity } from "../../product/entities/product.entity"; 4 | import { PurchaseEntity } from "./purchase.entity"; 5 | 6 | @Entity({ name: "purchases_products" }) 7 | export class PurchaseProductEntity extends BaseEntity { 8 | @Column() 9 | quantityProduct!: number; 10 | 11 | @Column() 12 | totalPrice!: number; 13 | 14 | @ManyToOne(() => PurchaseEntity, (purchase) => purchase.purchaseProduct) 15 | @JoinColumn({ name: "purchase_id" }) 16 | purchase!: PurchaseEntity; 17 | 18 | @ManyToOne(() => ProductEntity, (product) => product.purchaseProduct) 19 | @JoinColumn({ name: "product_id" }) 20 | product!: ProductEntity; 21 | } 22 | -------------------------------------------------------------------------------- /src/category/middlewares/category.middleware.ts: -------------------------------------------------------------------------------- 1 | import { validate } from "class-validator"; 2 | import { NextFunction, Request, Response } from "express"; 3 | 4 | import { HttpResponse } from "../../shared/response/http.response"; 5 | import { CategoryDTO } from "../dto/category.dto"; 6 | 7 | export class CategoryMiddleware { 8 | constructor( 9 | private readonly httpResponse: HttpResponse = new HttpResponse() 10 | ) {} 11 | categoryValidator(req: Request, res: Response, next: NextFunction) { 12 | const { categoryName } = req.body; 13 | 14 | const valid = new CategoryDTO(); 15 | 16 | valid.categoryName = categoryName; 17 | validate(valid).then((err) => { 18 | if (err.length > 0) { 19 | return this.httpResponse.Error(res, err); 20 | } else { 21 | next(); 22 | } 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/customer/middlewares/customer.middleware.ts: -------------------------------------------------------------------------------- 1 | import { validate } from "class-validator"; 2 | import { NextFunction, Request, Response } from "express"; 3 | import { SharedMiddleware } from "../../shared/middlewares/shared.middleware"; 4 | import { CustomerDTO } from "../dto/customer.dto"; 5 | 6 | export class CustomerMiddleware extends SharedMiddleware { 7 | constructor() { 8 | super(); 9 | } 10 | customerValidator(req: Request, res: Response, next: NextFunction) { 11 | const { address, dni, user } = req.body; 12 | 13 | const valid = new CustomerDTO(); 14 | 15 | valid.address = address; 16 | valid.dni = dni; 17 | valid.user = user; 18 | 19 | validate(valid).then((err) => { 20 | if (err.length > 0) { 21 | return this.httpResponse.Error(res, err); 22 | } else { 23 | next(); 24 | } 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/user/entities/user.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, OneToOne } from "typeorm"; 2 | import { BaseEntity } from "../../config/base.entity"; 3 | import { CustomerEntity } from "../../customer/entitites/customer.entity"; 4 | import { RoleType } from "../dto/user.dto"; 5 | 6 | @Entity({ name: "users" }) 7 | export class UserEntity extends BaseEntity { 8 | @Column() 9 | name!: string; 10 | 11 | @Column() 12 | lastname!: string; 13 | 14 | @Column() 15 | username!: string; 16 | 17 | @Column() 18 | email!: string; 19 | 20 | @Column({ select: false }) 21 | password!: string; 22 | 23 | @Column() 24 | city!: string; 25 | 26 | @Column() 27 | province!: string; 28 | 29 | @Column({ type: "enum", enum: RoleType, nullable: false }) 30 | role!: RoleType; 31 | 32 | @OneToOne(() => CustomerEntity, (customer) => customer.user) 33 | customer!: CustomerEntity; 34 | } 35 | -------------------------------------------------------------------------------- /src/config/data.source.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, DataSourceOptions } from "typeorm"; 2 | import * as dotenv from "dotenv"; 3 | import { SnakeNamingStrategy } from "typeorm-naming-strategies"; 4 | 5 | dotenv.config({ 6 | path: 7 | process.env.NODE_ENV !== undefined 8 | ? `.${process.env.NODE_ENV.trim()}.env` 9 | : ".env", 10 | }); 11 | 12 | const Config: DataSourceOptions = { 13 | type: "mysql", 14 | host: process.env.DB_HOST, 15 | port: Number(process.env.DB_PORT), 16 | username: process.env.DB_USER, 17 | password: process.env.DB_PASSWORD, 18 | database: process.env.DB_DATABASE, 19 | entities: [__dirname + "/../**/*.entity{.ts,.js}"], 20 | migrations: [__dirname + "/../migrations/*{.ts,.js}"], 21 | synchronize: false, 22 | migrationsRun: true, 23 | logging: false, 24 | namingStrategy: new SnakeNamingStrategy(), 25 | }; 26 | 27 | export const AppDataSource: DataSource = new DataSource(Config); 28 | -------------------------------------------------------------------------------- /src/purchase/entitites/purchase.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, JoinColumn, OneToMany } from "typeorm"; 2 | import { BaseEntity } from "../../config/base.entity"; 3 | import { PurchaseProductEntity } from "./purchases-products.entity"; 4 | import { CustomerEntity } from "../../customer/entitites/customer.entity"; 5 | import { StatusPurchare } from "../dto/purchase.dto"; 6 | 7 | @Entity({ name: "purchase" }) 8 | export class PurchaseEntity extends BaseEntity { 9 | @Column({ type: "enum", enum: StatusPurchare }) 10 | status!: StatusPurchare; 11 | 12 | @Column() 13 | paymentMethod!: string; 14 | 15 | @ManyToOne(() => CustomerEntity, (customer) => customer.purchases) 16 | @JoinColumn({ name: "customer_id" }) 17 | customer!: CustomerEntity; 18 | 19 | @OneToMany( 20 | () => PurchaseProductEntity, 21 | (purchaseProduct) => purchaseProduct.purchase 22 | ) 23 | purchaseProduct!: PurchaseProductEntity[]; 24 | } 25 | -------------------------------------------------------------------------------- /src/product/middlewares/product.middleware.ts: -------------------------------------------------------------------------------- 1 | import { validate } from "class-validator"; 2 | import { NextFunction, Request, Response } from "express"; 3 | import { SharedMiddleware } from "../../shared/middlewares/shared.middleware"; 4 | 5 | import { ProductDTO } from "../dto/product.dto"; 6 | 7 | export class ProductMiddleware extends SharedMiddleware { 8 | constructor() { 9 | super(); 10 | } 11 | productValidator(req: Request, res: Response, next: NextFunction) { 12 | const { productName, description, category, price } = req.body; 13 | 14 | const valid = new ProductDTO(); 15 | valid.productName = productName; 16 | valid.description = description; 17 | valid.category = category; 18 | valid.price = price; 19 | 20 | validate(valid).then((err) => { 21 | if (err.length > 0) { 22 | return this.httpResponse.Error(res, err); 23 | } else { 24 | next(); 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/auth/strategies/jwt.strategy.ts: -------------------------------------------------------------------------------- 1 | import { AuthService } from "../services/auth.service"; 2 | import { Strategy as JwtStr, StrategyOptions, ExtractJwt } from "passport-jwt"; 3 | import { PayloadToken } from "../interfaces/auth.interface"; 4 | import { PassportUse } from "../utils/passport.use"; 5 | 6 | export class JwtStrategy extends AuthService { 7 | constructor() { 8 | super(); 9 | } 10 | 11 | async validate(payload: PayloadToken, done: any) { 12 | return done(null, payload); 13 | } 14 | 15 | get use() { 16 | return PassportUse< 17 | JwtStr, 18 | StrategyOptions, 19 | (payload: PayloadToken, done: any) => Promise 20 | >( 21 | "jwt", 22 | JwtStr, 23 | { 24 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 25 | secretOrKey: this.getEnvironment("JWT_SECRET"), 26 | ignoreExpiration: false, 27 | }, 28 | this.validate 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/product/entities/product.entity.ts: -------------------------------------------------------------------------------- 1 | import { Column, Entity, ManyToOne, JoinColumn, OneToMany } from "typeorm"; 2 | import { BaseEntity } from "../../config/base.entity"; 3 | import { CustomerEntity } from "../../customer/entitites/customer.entity"; 4 | import { CategoryEntity } from "../../category/entities/category.entity"; 5 | import { PurchaseProductEntity } from "../../purchase/entitites/purchases-products.entity"; 6 | 7 | @Entity({ name: "product" }) 8 | export class ProductEntity extends BaseEntity { 9 | @Column() 10 | productName!: string; 11 | 12 | @Column() 13 | description!: string; 14 | 15 | @Column() 16 | price!: number; 17 | 18 | @ManyToOne(() => CategoryEntity, (category) => category.products) 19 | @JoinColumn({ name: "category_id" }) 20 | category!: CategoryEntity; 21 | 22 | @OneToMany( 23 | () => PurchaseProductEntity, 24 | (purchaseProduct) => purchaseProduct.product 25 | ) 26 | purchaseProduct!: PurchaseProductEntity[]; 27 | } 28 | -------------------------------------------------------------------------------- /src/purchase/middlewares/purchase.middleware.ts: -------------------------------------------------------------------------------- 1 | import { validate } from "class-validator"; 2 | import { NextFunction, Request, Response } from "express"; 3 | import { SharedMiddleware } from "../../shared/middlewares/shared.middleware"; 4 | 5 | import { HttpResponse } from "../../shared/response/http.response"; 6 | import { PurchaseDTO } from "../dto/purchase.dto"; 7 | 8 | export class PurchaseMiddleware extends SharedMiddleware { 9 | constructor() { 10 | super(); 11 | } 12 | purchaseValidator(req: Request, res: Response, next: NextFunction) { 13 | const { status, paymentMethod, customer } = req.body; 14 | 15 | const valid = new PurchaseDTO(); 16 | valid.status = status; 17 | valid.paymentMethod = paymentMethod; 18 | valid.customer = customer; 19 | 20 | validate(valid).then((err) => { 21 | if (err.length > 0) { 22 | return this.httpResponse.Error(res, err); 23 | } else { 24 | next(); 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/purchase/middlewares/purchase-product.middleware.ts: -------------------------------------------------------------------------------- 1 | import { validate } from "class-validator"; 2 | import { NextFunction, Request, Response } from "express"; 3 | import { SharedMiddleware } from "../../shared/middlewares/shared.middleware"; 4 | 5 | import { PurchaseProductDTO } from "../dto/purchase-product.dto"; 6 | 7 | export class PurchaseProductMiddleware extends SharedMiddleware { 8 | constructor() { 9 | super(); 10 | } 11 | purchaseProductValidator(req: Request, res: Response, next: NextFunction) { 12 | const { quantityProduct, totalPrice, purchase, product } = req.body; 13 | 14 | const valid = new PurchaseProductDTO(); 15 | valid.quantityProduct = quantityProduct; 16 | valid.totalPrice = totalPrice; 17 | valid.purchase = purchase; 18 | valid.product = product; 19 | 20 | validate(valid).then((err) => { 21 | if (err.length > 0) { 22 | return this.httpResponse.Error(res, err); 23 | } else { 24 | next(); 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/auth/strategies/login.strategy.ts: -------------------------------------------------------------------------------- 1 | import { AuthService } from "../services/auth.service"; 2 | import { Strategy as LocalStrategy, VerifyFunction } from "passport-local"; 3 | import { UserEntity } from "../../user/entities/user.entity"; 4 | import { PassportUse } from "../utils/passport.use"; 5 | 6 | const authService: AuthService = new AuthService(); 7 | 8 | export class LoginStrategy { 9 | async validate( 10 | username: string, 11 | password: string, 12 | done: any 13 | ): Promise { 14 | const user = await authService.validateUser(username, password); 15 | if (!user) { 16 | return done(null, false, { message: "Invalid username or password" }); 17 | } 18 | 19 | return done(null, user); 20 | } 21 | 22 | get use() { 23 | return PassportUse( 24 | "login", 25 | LocalStrategy, 26 | { 27 | usernameField: "username", 28 | passwordField: "password", 29 | }, 30 | this.validate 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/auth/controllers/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { HttpResponse } from "../../shared/response/http.response"; 3 | import { UserEntity } from "../../user/entities/user.entity"; 4 | import { AuthService } from "../services/auth.service"; 5 | 6 | export class AuthController extends AuthService { 7 | constructor( 8 | private readonly httpResponse: HttpResponse = new HttpResponse() 9 | ) { 10 | super(); 11 | } 12 | 13 | async login(req: Request, res: Response) { 14 | try { 15 | const userEncode = req.user as UserEntity; 16 | 17 | const encode = await this.generateJWT(userEncode); 18 | if (!encode) { 19 | return this.httpResponse.Unauthorized(res, "No tienes permisos"); 20 | } 21 | 22 | res.header("Content-Type", "application/json"); 23 | res.cookie("accessToken", encode.accessToken, { maxAge: 60000 * 60 }); 24 | res.write(JSON.stringify(encode)); 25 | res.end(); 26 | } catch (err) { 27 | console.error(err); 28 | return this.httpResponse.Error(res, err); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/shared/middlewares/shared.middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import passport from "passport"; 3 | import { RoleType } from "../../user/dto/user.dto"; 4 | import { UserEntity } from "../../user/entities/user.entity"; 5 | import { HttpResponse } from "../response/http.response"; 6 | 7 | export class SharedMiddleware { 8 | constructor(public httpResponse: HttpResponse = new HttpResponse()) {} 9 | passAuth(type: string) { 10 | return passport.authenticate(type, { session: false }); 11 | } 12 | 13 | checkCustomerRole(req: Request, res: Response, next: NextFunction) { 14 | const user = req.user as UserEntity; 15 | if (user.role !== RoleType.CUSTOMER) { 16 | return this.httpResponse.Unauthorized(res, "No tienes permiso"); 17 | } 18 | return next(); 19 | } 20 | checkAdminRole(req: Request, res: Response, next: NextFunction) { 21 | const user = req.user as UserEntity; 22 | if (user.role !== RoleType.ADMIN) { 23 | return this.httpResponse.Unauthorized(res, "No tienes permiso"); 24 | } 25 | return next(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/purchase/services/purchase.service.ts: -------------------------------------------------------------------------------- 1 | import { DeleteResult, UpdateResult } from "typeorm"; 2 | import { BaseService } from "../../config/base.service"; 3 | import { PurchaseDTO } from "../dto/purchase.dto"; 4 | import { PurchaseEntity } from "../entitites/purchase.entity"; 5 | export class PurchaseService extends BaseService { 6 | constructor() { 7 | super(PurchaseEntity); 8 | } 9 | 10 | async findAllPurchases(): Promise { 11 | return (await this.execRepository).find(); 12 | } 13 | async findPurchaseById(id: string): Promise { 14 | return (await this.execRepository).findOneBy({ id }); 15 | } 16 | async createPurchase(body: PurchaseDTO): Promise { 17 | return (await this.execRepository).save(body); 18 | } 19 | async deletePurchase(id: string): Promise { 20 | return (await this.execRepository).delete({ id }); 21 | } 22 | async updatePurchase( 23 | id: string, 24 | infoUpdate: PurchaseDTO 25 | ): Promise { 26 | return (await this.execRepository).update(id, infoUpdate); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/user/middlewares/user.middleware.ts: -------------------------------------------------------------------------------- 1 | import { validate } from "class-validator"; 2 | import { NextFunction, Request, Response } from "express"; 3 | import { UserDTO } from "../dto/user.dto"; 4 | import { HttpResponse } from "../../shared/response/http.response"; 5 | import { SharedMiddleware } from "../../shared/middlewares/shared.middleware"; 6 | 7 | export class UserMiddleware extends SharedMiddleware { 8 | constructor() { 9 | super(); 10 | } 11 | userValidator(req: Request, res: Response, next: NextFunction) { 12 | const { name, lastname, username, email, password, city, province, role } = 13 | req.body; 14 | 15 | const valid = new UserDTO(); 16 | 17 | valid.name = name; 18 | valid.lastname = lastname; 19 | valid.username = username; 20 | valid.email = email; 21 | valid.password = password; 22 | valid.city = city; 23 | valid.province = province; 24 | valid.role = role; 25 | 26 | validate(valid).then((err) => { 27 | if (err.length > 0) { 28 | return this.httpResponse.Error(res, err); 29 | } else { 30 | next(); 31 | } 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/category/category.router.ts: -------------------------------------------------------------------------------- 1 | import { BaseRouter } from "../shared/router/router"; 2 | import { CategoryController } from "./controllers/category.controller"; 3 | import { CategoryMiddleware } from "./middlewares/category.middleware"; 4 | export class CategoryRouter extends BaseRouter< 5 | CategoryController, 6 | CategoryMiddleware 7 | > { 8 | constructor() { 9 | super(CategoryController, CategoryMiddleware); 10 | } 11 | 12 | routes(): void { 13 | this.router.get("/categories", (req, res) => 14 | this.controller.getCategories(req, res) 15 | ); 16 | this.router.get("/categories/category/:id", (req, res) => 17 | this.controller.getCategoryById(req, res) 18 | ); 19 | this.router.post( 20 | "/categories/create", 21 | (req, res, next) => [this.middleware.categoryValidator(req, res, next)], 22 | (req, res) => this.controller.createCategory(req, res) 23 | ); 24 | this.router.put("/categories/update/:id", (req, res) => 25 | this.controller.updateCategory(req, res) 26 | ); 27 | this.router.delete("/categories/delete/:id", (req, res) => 28 | this.controller.deleteCategory(req, res) 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/config/config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | import { 3 | Connection, 4 | ConnectionOptions, 5 | createConnection, 6 | DataSource, 7 | } from "typeorm"; 8 | import { SnakeNamingStrategy } from "typeorm-naming-strategies"; 9 | import { AppDataSource } from "./data.source"; 10 | 11 | export abstract class ConfigServer { 12 | constructor() { 13 | const nodeNameEnv = this.createPathEnv(this.nodeEnv); 14 | dotenv.config({ 15 | path: nodeNameEnv, 16 | }); 17 | } 18 | 19 | public getEnvironment(k: string): string | undefined { 20 | return process.env[k]; 21 | } 22 | 23 | public getNumberEnv(k: string): number { 24 | return Number(this.getEnvironment(k)); 25 | } 26 | 27 | public get nodeEnv(): string { 28 | return this.getEnvironment("NODE_ENV")?.trim() || ""; 29 | } 30 | 31 | public createPathEnv(path: string): string { 32 | const arrEnv: Array = ["env"]; //['hola', 'mundo'] => 'hola.mundo' 33 | 34 | if (path.length > 0) { 35 | const stringToArray = path.split("."); 36 | arrEnv.unshift(...stringToArray); 37 | } 38 | return "." + arrEnv.join("."); 39 | } 40 | 41 | get initConnect(): Promise { 42 | return AppDataSource.initialize(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/customer/customer.router.ts: -------------------------------------------------------------------------------- 1 | import { BaseRouter } from "../shared/router/router"; 2 | import { CustomerController } from "./controllers/customer.controller"; 3 | import { CustomerMiddleware } from "./middlewares/customer.middleware"; 4 | export class CustomerRouter extends BaseRouter< 5 | CustomerController, 6 | CustomerMiddleware 7 | > { 8 | constructor() { 9 | super(CustomerController, CustomerMiddleware); 10 | } 11 | 12 | routes(): void { 13 | this.router.get("/customers", this.middleware.passAuth("jwt"), (req, res) => 14 | this.controller.getCustomers(req, res) 15 | ); 16 | this.router.get( 17 | "/customers/customer/:id", 18 | this.middleware.passAuth("jwt"), 19 | (req, res) => this.controller.getCustomerById(req, res) 20 | ); 21 | this.router.post( 22 | "/customers/create", 23 | this.middleware.passAuth("jwt"), 24 | (req, res, next) => [this.middleware.customerValidator(req, res, next)], 25 | (req, res) => this.controller.createCustomer(req, res) 26 | ); 27 | this.router.put( 28 | "/customers/update/:id", 29 | this.middleware.passAuth("jwt"), 30 | (req, res) => this.controller.updateCustomer(req, res) 31 | ); 32 | this.router.delete( 33 | "/customers/delete/:id", 34 | this.middleware.passAuth("jwt"), 35 | (req, res) => this.controller.deleteCustomer(req, res) 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/purchase/purchase.router.ts: -------------------------------------------------------------------------------- 1 | import { BaseRouter } from "../shared/router/router"; 2 | import { PurchaseController } from "./controllers/purchase.controller"; 3 | import { PurchaseMiddleware } from "./middlewares/purchase.middleware"; 4 | export class PurchaseRouter extends BaseRouter< 5 | PurchaseController, 6 | PurchaseMiddleware 7 | > { 8 | constructor() { 9 | super(PurchaseController, PurchaseMiddleware); 10 | } 11 | 12 | routes(): void { 13 | this.router.get("/purchases", this.middleware.passAuth("jwt"), (req, res) => 14 | this.controller.getPurchases(req, res) 15 | ); 16 | this.router.get( 17 | "/purchases/purchase/:id", 18 | this.middleware.passAuth("jwt"), 19 | (req, res) => this.controller.getPurchaseById(req, res) 20 | ); 21 | this.router.post( 22 | "/purchases/create", 23 | this.middleware.passAuth("jwt"), 24 | (req, res, next) => [this.middleware.purchaseValidator(req, res, next)], 25 | (req, res) => this.controller.createPurchase(req, res) 26 | ); 27 | this.router.put( 28 | "/purchases/update/:id", 29 | this.middleware.passAuth("jwt"), 30 | (req, res) => this.controller.updatePurchase(req, res) 31 | ); 32 | this.router.delete( 33 | "/purchases/delete/:id", 34 | this.middleware.passAuth("jwt"), 35 | (req, res) => this.controller.deletePurchase(req, res) 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/shared/response/http.response.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "express"; 2 | 3 | export enum HttpStatus { 4 | OK = 200, 5 | NOT_FOUND = 404, 6 | UNAUTHORIZED = 401, 7 | FORBIDDEN = 403, 8 | INTERNAL_SERVER_ERROR = 500, 9 | } 10 | 11 | export class HttpResponse { 12 | Ok(res: Response, data?: any): Response { 13 | return res.status(HttpStatus.OK).json({ 14 | status: HttpStatus.OK, 15 | statusMsg: "Success", 16 | data: data, 17 | }); 18 | } 19 | 20 | NotFound(res: Response, data?: any): Response { 21 | return res.status(HttpStatus.NOT_FOUND).json({ 22 | status: HttpStatus.NOT_FOUND, 23 | statusMsg: "Not Found", 24 | error: data, 25 | }); 26 | } 27 | 28 | Unauthorized(res: Response, data?: any): Response { 29 | return res.status(HttpStatus.UNAUTHORIZED).json({ 30 | status: HttpStatus.UNAUTHORIZED, 31 | statusMsg: "Unauthorized", 32 | error: data, 33 | }); 34 | } 35 | 36 | Forbidden(res: Response, data?: any): Response { 37 | return res.status(HttpStatus.FORBIDDEN).json({ 38 | status: HttpStatus.FORBIDDEN, 39 | statusMsg: "Forbidden", 40 | error: data, 41 | }); 42 | } 43 | 44 | Error(res: Response, data?: any): Response { 45 | return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ 46 | status: HttpStatus.INTERNAL_SERVER_ERROR, 47 | statusMsg: "Internal server error", 48 | error: data, 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/category/services/category.service.ts: -------------------------------------------------------------------------------- 1 | import { DeleteResult, UpdateResult } from "typeorm"; 2 | import { BaseService } from "../../config/base.service"; 3 | import { CategoryEntity } from "../entities/category.entity"; 4 | import { CategoryDTO } from "../dto/category.dto"; 5 | 6 | export class CategoryService extends BaseService { 7 | constructor() { 8 | super(CategoryEntity); 9 | } 10 | 11 | async findAllCategoties(): Promise { 12 | return (await this.execRepository).find(); 13 | } 14 | async findCategoryById(id: string): Promise { 15 | return (await this.execRepository).findOneBy({ id }); 16 | } 17 | 18 | async findCategoryWithProduct( 19 | categorytId: string 20 | ): Promise { 21 | return (await this.execRepository) 22 | .createQueryBuilder("category") 23 | .leftJoinAndSelect("category.products", "products") 24 | .where({ id: categorytId }) 25 | .getOne(); 26 | } 27 | async createCategory(body: CategoryDTO): Promise { 28 | return (await this.execRepository).save(body); 29 | } 30 | async deleteCategory(id: string): Promise { 31 | return (await this.execRepository).delete({ id }); 32 | } 33 | async updateCategory( 34 | id: string, 35 | infoUpdate: CategoryDTO 36 | ): Promise { 37 | return (await this.execRepository).update(id, infoUpdate); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/user/user.router.ts: -------------------------------------------------------------------------------- 1 | import { BaseRouter } from "../shared/router/router"; 2 | import { UserController } from "./controllers/user.controller"; 3 | import { UserMiddleware } from "./middlewares/user.middleware"; 4 | export class UserRouter extends BaseRouter { 5 | constructor() { 6 | super(UserController, UserMiddleware); 7 | } 8 | 9 | routes(): void { 10 | this.router.get("/users", this.middleware.passAuth("jwt"), (req, res) => 11 | this.controller.getUsers(req, res) 12 | ); 13 | this.router.get( 14 | "/users/user/:id", 15 | this.middleware.passAuth("jwt"), 16 | (req, res) => this.controller.getUserById(req, res) 17 | ); 18 | this.router.get( 19 | "/users/user-customer/:id", 20 | this.middleware.passAuth("jwt"), 21 | (req, res) => this.controller.getUserWithRelationById(req, res) 22 | ); 23 | this.router.post( 24 | "/users/register", 25 | (req, res, next) => [this.middleware.userValidator(req, res, next)], 26 | (req, res) => this.controller.createUser(req, res) 27 | ); 28 | this.router.put( 29 | "/users/update/:id", 30 | this.middleware.passAuth("jwt"), 31 | (req, res, next) => [this.middleware.checkAdminRole(req, res, next)], 32 | (req, res) => this.controller.updateUser(req, res) 33 | ); 34 | this.router.delete( 35 | "/users/delete/:id", 36 | this.middleware.passAuth("jwt"), 37 | (req, res, next) => [this.middleware.checkAdminRole(req, res, next)], 38 | (req, res) => this.controller.deleteUser(req, res) 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/product/services/product.service.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "express"; 2 | import QueryString from "qs"; 3 | import { DeleteResult, UpdateResult } from "typeorm"; 4 | import { BaseService } from "../../config/base.service"; 5 | import { ProductDTO } from "../dto/product.dto"; 6 | 7 | import { ProductEntity } from "../entities/product.entity"; 8 | export class ProductService extends BaseService { 9 | constructor() { 10 | super(ProductEntity); 11 | } 12 | 13 | async findAllProducts(): Promise { 14 | return (await this.execRepository).find(); 15 | } 16 | async findProductById(id: string): Promise { 17 | return (await this.execRepository).findOneBy({ id }); 18 | } 19 | 20 | async findProductsByName( 21 | productName: 22 | | string 23 | | string[] 24 | | QueryString.ParsedQs 25 | | QueryString.ParsedQs[] 26 | ): Promise { 27 | return (await this.execRepository) 28 | .createQueryBuilder("products") 29 | .where("products.productName like :productName", { 30 | productName: `%${productName}%`, 31 | }) 32 | .getMany(); 33 | } 34 | 35 | async createProduct(body: ProductDTO): Promise { 36 | return (await this.execRepository).save(body); 37 | } 38 | async deleteProduct(id: string): Promise { 39 | return (await this.execRepository).delete({ id }); 40 | } 41 | async updateProduct( 42 | id: string, 43 | infoUpdate: ProductDTO 44 | ): Promise { 45 | return (await this.execRepository).update(id, infoUpdate); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/purchase/purchase-product.router.ts: -------------------------------------------------------------------------------- 1 | import { BaseRouter } from "../shared/router/router"; 2 | import { PurchaseController } from "./controllers/purchase.controller"; 3 | import { PurchaseProductController } from "./controllers/purchase-product.controller"; 4 | import { PurchaseProductMiddleware } from "./middlewares/purchase-product.middleware"; 5 | export class PurchaseProductRouter extends BaseRouter< 6 | PurchaseProductController, 7 | PurchaseProductMiddleware 8 | > { 9 | constructor() { 10 | super(PurchaseProductController, PurchaseProductMiddleware); 11 | } 12 | 13 | routes(): void { 14 | this.router.get("/purchaseProducts", (req, res) => 15 | this.controller.getPurchaseProducts(req, res) 16 | ); 17 | this.router.get("/purchaseProducts/purchaseProduct/:id", (req, res) => 18 | this.controller.getPurchaseProductById(req, res) 19 | ); 20 | this.router.post( 21 | "/purchaseProducts/create", 22 | this.middleware.passAuth("jwt"), 23 | (req, res, next) => [ 24 | this.middleware.checkCustomerRole(req, res, next), 25 | this.middleware.purchaseProductValidator(req, res, next), 26 | ], 27 | (req, res) => this.controller.createPurchaseProduct(req, res) 28 | ); 29 | this.router.put( 30 | "/purchaseProducts/update/:id", 31 | this.middleware.passAuth("jwt"), 32 | (req, res) => this.controller.updatePurchaseProduct(req, res) 33 | ); 34 | this.router.delete( 35 | "/purchaseProducts/delete/:id", 36 | this.middleware.passAuth("jwt"), 37 | (req, res) => this.controller.deletePurchaseProduct(req, res) 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/product/product.router.ts: -------------------------------------------------------------------------------- 1 | import { BaseRouter } from "../shared/router/router"; 2 | import { ProductController } from "./controllers/product.controller"; 3 | import { ProductMiddleware } from "./middlewares/product.middleware"; 4 | export class ProductRouter extends BaseRouter< 5 | ProductController, 6 | ProductMiddleware 7 | > { 8 | constructor() { 9 | super(ProductController, ProductMiddleware); 10 | } 11 | 12 | routes(): void { 13 | this.router.get("/products", (req, res) => 14 | this.controller.getProducts(req, res) 15 | ); 16 | this.router.get("/products/product/:id", (req, res) => 17 | this.controller.getProductById(req, res) 18 | ); 19 | this.router.get("/products/search", (req, res) => 20 | this.controller.findProductsByName(req, res) 21 | ); 22 | this.router.post( 23 | "/products/create", 24 | this.middleware.passAuth("jwt"), 25 | (req, res, next) => [ 26 | this.middleware.checkAdminRole(req, res, next), 27 | this.middleware.productValidator(req, res, next), 28 | ], 29 | (req, res) => this.controller.createProduct(req, res) 30 | ); 31 | this.router.put( 32 | "/products/update/:id", 33 | this.middleware.passAuth("jwt"), 34 | (req, res, next) => [this.middleware.checkAdminRole(req, res, next)], 35 | (req, res) => this.controller.updateProduct(req, res) 36 | ); 37 | this.router.delete( 38 | "products/delete/:id", 39 | this.middleware.passAuth("jwt"), 40 | (req, res, next) => [this.middleware.checkAdminRole(req, res, next)], 41 | (req, res) => this.controller.deleteProduct(req, res) 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/purchase/services/purchase-product.service.ts: -------------------------------------------------------------------------------- 1 | import { DeleteResult, UpdateResult } from "typeorm"; 2 | import { BaseService } from "../../config/base.service"; 3 | import { PurchaseProductDTO } from "../dto/purchase-product.dto"; 4 | import { PurchaseProductEntity } from "../entitites/purchases-products.entity"; 5 | import { ProductService } from "../../product/services/product.service"; 6 | export class PurchaseProductService extends BaseService { 7 | constructor( 8 | private readonly productService: ProductService = new ProductService() 9 | ) { 10 | super(PurchaseProductEntity); 11 | } 12 | 13 | async findAllPurchaseProducts(): Promise { 14 | return (await this.execRepository).find(); 15 | } 16 | async findPurchaseProductById( 17 | id: string 18 | ): Promise { 19 | return (await this.execRepository).findOneBy({ id }); 20 | } 21 | 22 | async createPurchaseProduct( 23 | body: PurchaseProductDTO 24 | ): Promise { 25 | const newPP = (await this.execRepository).create(body); 26 | const prod = await this.productService.findProductById(newPP.product.id); 27 | newPP.totalPrice = prod!.price * newPP.quantityProduct; 28 | return (await this.execRepository).save(newPP); 29 | } 30 | 31 | async deletePurchaseProduct(id: string): Promise { 32 | return (await this.execRepository).delete({ id }); 33 | } 34 | async updatePurchaseProduct( 35 | id: string, 36 | infoUpdate: PurchaseProductDTO 37 | ): Promise { 38 | return (await this.execRepository).update(id, infoUpdate); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/customer/services/customer.service.ts: -------------------------------------------------------------------------------- 1 | import { DeleteResult, UpdateResult } from "typeorm"; 2 | import { BaseService } from "../../config/base.service"; 3 | import { CustomerEntity } from "../entitites/customer.entity"; 4 | import { CustomerDTO } from "../dto/customer.dto"; 5 | import { UserService } from "../../user/services/user.service"; 6 | import { RoleType } from "../../user/dto/user.dto"; 7 | import { UserEntity } from "../../user/entities/user.entity"; 8 | export class CustomerService extends BaseService { 9 | constructor(private readonly userService: UserService = new UserService()) { 10 | super(CustomerEntity); 11 | } 12 | 13 | async findAllCustomers(): Promise { 14 | return (await this.execRepository).find(); 15 | } 16 | async findCustomerById(id: string): Promise { 17 | return (await this.execRepository).findOneBy({ id }); 18 | } 19 | async createCustomer(body: CustomerDTO): Promise { 20 | const createCustomer = (await this.execRepository).create(body); 21 | const user = await this.userService.findUserById(createCustomer.user.id); 22 | if (user) { 23 | await this.userService.updateUser(user.id, { 24 | ...user, 25 | role: RoleType.CUSTOMER, 26 | }); 27 | 28 | return (await this.execRepository).save(body); 29 | } 30 | 31 | return null; 32 | } 33 | async deleteCustomer(id: string): Promise { 34 | return (await this.execRepository).delete({ id }); 35 | } 36 | async updateCustomer( 37 | id: string, 38 | infoUpdate: CustomerDTO 39 | ): Promise { 40 | return (await this.execRepository).update(id, infoUpdate); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "curso-node-ts", 3 | "version": "1.0.0", 4 | "description": "Curso Nodejs con TypeScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "tsc && node dist/server.js", 9 | "start:dev": "tsc && concurrently \"tsc -w\" \"nodemon dist/server.js\"", 10 | "start:prod": "SET NODE_ENV=production && npm start", 11 | "typeorm": "typeorm-ts-node-esm -d ./src/config/data.source.ts", 12 | "m:gen": "npm run typeorm migration:generate", 13 | "m:run": "npm run typeorm migration:run" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/rodrigoalejandrorios/api-nodejs-typescript.git" 18 | }, 19 | "author": "Rodrigo Rios", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/rodrigoalejandrorios/api-nodejs-typescript/issues" 23 | }, 24 | "homepage": "https://github.com/rodrigoalejandrorios/api-nodejs-typescript#readme", 25 | "dependencies": { 26 | "bcrypt": "^5.0.1", 27 | "class-transformer": "^0.5.1", 28 | "class-validator": "^0.13.2", 29 | "cors": "^2.8.5", 30 | "dotenv": "^16.0.0", 31 | "express": "^4.17.3", 32 | "jsonwebtoken": "^8.5.1", 33 | "morgan": "^1.10.0", 34 | "mysql": "^2.18.1", 35 | "passport": "^0.6.0", 36 | "passport-jwt": "^4.0.0", 37 | "passport-local": "^1.0.0", 38 | "reflect-metadata": "^0.1.13", 39 | "typeorm": "0.3.6", 40 | "typeorm-naming-strategies": "4.1.0", 41 | "typescript": "^4.6.2" 42 | }, 43 | "devDependencies": { 44 | "@types/bcrypt": "^5.0.0", 45 | "@types/cors": "^2.8.12", 46 | "@types/express": "^4.17.13", 47 | "@types/jsonwebtoken": "^8.5.8", 48 | "@types/morgan": "^1.9.3", 49 | "@types/passport-jwt": "^3.0.6", 50 | "@types/passport-local": "^1.0.34", 51 | "concurrently": "^7.0.0", 52 | "nodemon": "^2.0.15", 53 | "ts-node": "^10.7.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/auth/services/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { ConfigServer } from "../../config/config"; 2 | import * as jwt from "jsonwebtoken"; 3 | import * as bcrypt from "bcrypt"; 4 | import { UserService } from "../../user/services/user.service"; 5 | import { UserEntity } from "../../user/entities/user.entity"; 6 | import { PayloadToken } from "../interfaces/auth.interface"; 7 | 8 | export class AuthService extends ConfigServer { 9 | constructor( 10 | private readonly userService: UserService = new UserService(), 11 | private readonly jwtInstance = jwt 12 | ) { 13 | super(); 14 | } 15 | 16 | public async validateUser( 17 | username: string, 18 | password: string 19 | ): Promise { 20 | const userByEmail = await this.userService.findByEmail(username); 21 | const userByUsername = await this.userService.findByUsername(username); 22 | 23 | if (userByUsername) { 24 | const isMatch = await bcrypt.compare(password, userByUsername.password); 25 | if (isMatch) { 26 | return userByUsername; 27 | } 28 | } 29 | if (userByEmail) { 30 | const isMatch = await bcrypt.compare(password, userByEmail.password); 31 | if (isMatch) { 32 | return userByEmail; 33 | } 34 | } 35 | 36 | return null; 37 | } 38 | 39 | //JWT_SECRET 40 | 41 | sing(payload: jwt.JwtPayload, secret: any) { 42 | return this.jwtInstance.sign(payload, secret, { expiresIn: "1h" }); 43 | } 44 | 45 | public async generateJWT( 46 | user: UserEntity 47 | ): Promise<{ accessToken: string; user: UserEntity }> { 48 | const userConsult = await this.userService.findUserWithRole( 49 | user.id, 50 | user.role 51 | ); 52 | 53 | const payload: PayloadToken = { 54 | role: userConsult!.role, 55 | sub: userConsult!.id, 56 | }; 57 | 58 | if (userConsult) { 59 | user.password = "Not permission"; 60 | } 61 | 62 | return { 63 | accessToken: this.sing(payload, this.getEnvironment("JWT_SECRET")), 64 | user, 65 | }; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/user/services/user.service.ts: -------------------------------------------------------------------------------- 1 | import { DeleteResult, UpdateResult } from "typeorm"; 2 | import * as bcrypt from "bcrypt"; 3 | import { BaseService } from "../../config/base.service"; 4 | import { RoleType, UserDTO } from "../dto/user.dto"; 5 | import { UserEntity } from "../entities/user.entity"; 6 | export class UserService extends BaseService { 7 | constructor() { 8 | super(UserEntity); 9 | } 10 | 11 | async findAllUser(): Promise { 12 | return (await this.execRepository).find(); 13 | } 14 | async findUserById(id: string): Promise { 15 | return (await this.execRepository).findOneBy({ id }); 16 | } 17 | 18 | async findUserWithRole( 19 | id: string, 20 | role: RoleType 21 | ): Promise { 22 | const user = (await this.execRepository) 23 | .createQueryBuilder("user") 24 | .where({ id }) 25 | .andWhere({ role }) 26 | .getOne(); 27 | 28 | return user; 29 | } 30 | 31 | async findUserWithRelation(id: string): Promise { 32 | return (await this.execRepository) 33 | .createQueryBuilder("user") 34 | .leftJoinAndSelect("user.customer", "customer") 35 | .where({ id }) 36 | .getOne(); 37 | } 38 | 39 | async findByEmail(email: string): Promise { 40 | return (await this.execRepository) 41 | .createQueryBuilder("user") 42 | .addSelect("user.password") 43 | .where({ email }) 44 | .getOne(); 45 | } 46 | async findByUsername(username: string): Promise { 47 | return (await this.execRepository) 48 | .createQueryBuilder("user") 49 | .addSelect("user.password") 50 | .where({ username }) 51 | .getOne(); 52 | } 53 | async createUser(body: UserDTO): Promise { 54 | const newUser = (await this.execRepository).create(body); 55 | const hashPass = await bcrypt.hash(newUser.password, 10); 56 | newUser.password = hashPass; 57 | return (await this.execRepository).save(newUser); 58 | } 59 | async deleteUser(id: string): Promise { 60 | return (await this.execRepository).delete({ id }); 61 | } 62 | async updateUser(id: string, infoUpdate: UserDTO): Promise { 63 | return (await this.execRepository).update(id, infoUpdate); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import express from "express"; 3 | import morgan from "morgan"; 4 | import cors from "cors"; 5 | import { UserRouter } from "./user/user.router"; 6 | import { ConfigServer } from "./config/config"; 7 | import { PurchaseRouter } from "./purchase/purchase.router"; 8 | import { ProductRouter } from "./product/product.router"; 9 | import { CustomerRouter } from "./customer/customer.router"; 10 | import { CategoryRouter } from "./category/category.router"; 11 | import { PurchaseProductRouter } from "./purchase/purchase-product.router"; 12 | import { DataSource } from "typeorm"; 13 | import { LoginStrategy } from "./auth/strategies/login.strategy"; 14 | import { JwtStrategy } from "./auth/strategies/jwt.strategy"; 15 | import { AuthRouter } from "./auth/auth.router"; 16 | 17 | class ServerBootstrap extends ConfigServer { 18 | public app: express.Application = express(); 19 | private port: number = this.getNumberEnv("PORT"); 20 | 21 | constructor() { 22 | super(); 23 | this.app.use(express.json()); 24 | this.app.use(express.urlencoded({ extended: true })); 25 | this.passportUse(); 26 | this.dbConnect(); 27 | this.app.use(morgan("dev")); 28 | 29 | this.app.use( 30 | cors({ 31 | origin: true, 32 | methods: "GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS", 33 | credentials: true, 34 | }) 35 | ); 36 | 37 | this.app.use("/api", this.routers()); 38 | this.listen(); 39 | } 40 | 41 | routers(): Array { 42 | return [ 43 | new UserRouter().router, 44 | new PurchaseRouter().router, 45 | new ProductRouter().router, 46 | new CustomerRouter().router, 47 | new CategoryRouter().router, 48 | new PurchaseProductRouter().router, 49 | new AuthRouter().router, 50 | ]; 51 | } 52 | 53 | passportUse() { 54 | return [new LoginStrategy().use, new JwtStrategy().use]; 55 | } 56 | 57 | async dbConnect(): Promise { 58 | return this.initConnect 59 | .then(() => { 60 | console.log("Connect Success"); 61 | }) 62 | .catch((err) => { 63 | console.error(err); 64 | }); 65 | } 66 | 67 | public listen() { 68 | this.app.listen(this.port, () => { 69 | console.log( 70 | `Listen in ${this.port} :: ENV = ${this.getEnvironment("ENV")}` 71 | ); 72 | }); 73 | } 74 | } 75 | 76 | new ServerBootstrap(); 77 | -------------------------------------------------------------------------------- /src/customer/controllers/customer.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { DeleteResult, UpdateResult } from "typeorm"; 3 | import { HttpResponse } from "../../shared/response/http.response"; 4 | import { CustomerService } from "../services/customer.service"; 5 | 6 | export class CustomerController { 7 | constructor( 8 | private readonly customerService: CustomerService = new CustomerService(), 9 | private readonly httpResponse: HttpResponse = new HttpResponse() 10 | ) {} 11 | async getCustomers(req: Request, res: Response) { 12 | try { 13 | const data = await this.customerService.findAllCustomers(); 14 | if (data.length === 0) { 15 | return this.httpResponse.NotFound(res, "No existe dato"); 16 | } 17 | return this.httpResponse.Ok(res, data); 18 | } catch (e) { 19 | console.error(e); 20 | return this.httpResponse.Error(res, e); 21 | } 22 | } 23 | async getCustomerById(req: Request, res: Response) { 24 | const { id } = req.params; 25 | try { 26 | const data = await this.customerService.findCustomerById(id); 27 | if (!data) { 28 | return this.httpResponse.NotFound(res, "No existe dato"); 29 | } 30 | return this.httpResponse.Ok(res, data); 31 | } catch (e) { 32 | console.error(e); 33 | return this.httpResponse.Error(res, e); 34 | } 35 | } 36 | async createCustomer(req: Request, res: Response) { 37 | try { 38 | const data = await this.customerService.createCustomer(req.body); 39 | return this.httpResponse.Ok(res, data); 40 | } catch (e) { 41 | console.error(e); 42 | return this.httpResponse.Error(res, e); 43 | } 44 | } 45 | async updateCustomer(req: Request, res: Response) { 46 | const { id } = req.params; 47 | try { 48 | const data: UpdateResult = await this.customerService.updateCustomer( 49 | id, 50 | req.body 51 | ); 52 | if (!data.affected) { 53 | return this.httpResponse.NotFound(res, "Hay un error en actualizar"); 54 | } 55 | 56 | return this.httpResponse.Ok(res, data); 57 | } catch (e) { 58 | console.error(e); 59 | return this.httpResponse.Error(res, e); 60 | } 61 | } 62 | async deleteCustomer(req: Request, res: Response) { 63 | const { id } = req.params; 64 | try { 65 | const data: DeleteResult = await this.customerService.deleteCustomer(id); 66 | if (!data.affected) { 67 | return this.httpResponse.NotFound(res, "Hay un error en actualizar"); 68 | } 69 | return this.httpResponse.Ok(res, data); 70 | } catch (e) { 71 | console.error(e); 72 | return this.httpResponse.Error(res, e); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/purchase/controllers/purchase.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { DeleteResult, UpdateResult } from "typeorm"; 3 | import { HttpResponse } from "../../shared/response/http.response"; 4 | import { PurchaseService } from "../services/purchase.service"; 5 | 6 | export class PurchaseController { 7 | constructor( 8 | private readonly purchaseService: PurchaseService = new PurchaseService(), 9 | private readonly httpResponse: HttpResponse = new HttpResponse() 10 | ) {} 11 | async getPurchases(req: Request, res: Response) { 12 | try { 13 | const data = await this.purchaseService.findAllPurchases(); 14 | if (data.length === 0) { 15 | return this.httpResponse.NotFound(res, "No existe dato"); 16 | } 17 | return this.httpResponse.Ok(res, data); 18 | } catch (e) { 19 | console.error(e); 20 | return this.httpResponse.Error(res, e); 21 | } 22 | } 23 | async getPurchaseById(req: Request, res: Response) { 24 | const { id } = req.params; 25 | try { 26 | const data = await this.purchaseService.findPurchaseById(id); 27 | if (!data) { 28 | return this.httpResponse.NotFound(res, "No existe dato"); 29 | } 30 | return this.httpResponse.Ok(res, data); 31 | } catch (e) { 32 | console.error(e); 33 | return this.httpResponse.Error(res, e); 34 | } 35 | } 36 | async createPurchase(req: Request, res: Response) { 37 | try { 38 | const data = await this.purchaseService.createPurchase(req.body); 39 | return this.httpResponse.Ok(res, data); 40 | } catch (e) { 41 | console.error(e); 42 | return this.httpResponse.Error(res, e); 43 | } 44 | } 45 | async updatePurchase(req: Request, res: Response) { 46 | const { id } = req.params; 47 | try { 48 | const data: UpdateResult = await this.purchaseService.updatePurchase( 49 | id, 50 | req.body 51 | ); 52 | if (!data.affected) { 53 | return this.httpResponse.NotFound(res, "Hay un error en actualizar"); 54 | } 55 | 56 | return this.httpResponse.Ok(res, data); 57 | } catch (e) { 58 | console.error(e); 59 | return this.httpResponse.Error(res, e); 60 | } 61 | } 62 | async deletePurchase(req: Request, res: Response) { 63 | const { id } = req.params; 64 | try { 65 | const data: DeleteResult = await this.purchaseService.deletePurchase(id); 66 | if (!data.affected) { 67 | return this.httpResponse.NotFound(res, "Hay un error en borrar"); 68 | } 69 | 70 | return this.httpResponse.Ok(res, data); 71 | } catch (e) { 72 | console.error(e); 73 | return this.httpResponse.Error(res, e); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/purchase/controllers/purchase-product.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { DeleteResult, UpdateResult } from "typeorm"; 3 | import { HttpResponse } from "../../shared/response/http.response"; 4 | import { PurchaseProductService } from "../services/purchase-product.service"; 5 | 6 | export class PurchaseProductController { 7 | constructor( 8 | private readonly purchaseProductService: PurchaseProductService = new PurchaseProductService(), 9 | private readonly httpResponse: HttpResponse = new HttpResponse() 10 | ) {} 11 | async getPurchaseProducts(_req: Request, res: Response) { 12 | try { 13 | const data = await this.purchaseProductService.findAllPurchaseProducts(); 14 | if (data.length === 0) { 15 | return this.httpResponse.NotFound(res, "No existe dato"); 16 | } 17 | return this.httpResponse.Ok(res, data); 18 | } catch (e) { 19 | console.error(e); 20 | return this.httpResponse.Error(res, e); 21 | } 22 | } 23 | async getPurchaseProductById(req: Request, res: Response) { 24 | const { id } = req.params; 25 | try { 26 | const data = await this.purchaseProductService.findPurchaseProductById( 27 | id 28 | ); 29 | if (!data) { 30 | return this.httpResponse.NotFound(res, "No existe dato"); 31 | } 32 | return this.httpResponse.Ok(res, data); 33 | } catch (e) { 34 | console.error(e); 35 | return this.httpResponse.Error(res, e); 36 | } 37 | } 38 | async createPurchaseProduct(req: Request, res: Response) { 39 | try { 40 | const data = await this.purchaseProductService.createPurchaseProduct( 41 | req.body 42 | ); 43 | return this.httpResponse.Ok(res, data); 44 | } catch (e) { 45 | console.error(e); 46 | return this.httpResponse.Error(res, e); 47 | } 48 | } 49 | async updatePurchaseProduct(req: Request, res: Response) { 50 | const { id } = req.params; 51 | try { 52 | const data: UpdateResult = 53 | await this.purchaseProductService.updatePurchaseProduct(id, req.body); 54 | 55 | if (!data.affected) { 56 | return this.httpResponse.NotFound(res, "Hay un error en actualizar"); 57 | } 58 | 59 | return this.httpResponse.Ok(res, data); 60 | } catch (e) { 61 | console.error(e); 62 | return this.httpResponse.Error(res, e); 63 | } 64 | } 65 | async deletePurchaseProduct(req: Request, res: Response) { 66 | const { id } = req.params; 67 | try { 68 | const data: DeleteResult = 69 | await this.purchaseProductService.deletePurchaseProduct(id); 70 | if (!data.affected) { 71 | return this.httpResponse.NotFound(res, "Hay un error en borrar"); 72 | } 73 | 74 | return this.httpResponse.Ok(res, data); 75 | } catch (e) { 76 | console.error(e); 77 | return this.httpResponse.Error(res, e); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/user/controllers/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { UserService } from "../services/user.service"; 3 | import { HttpResponse } from "../../shared/response/http.response"; 4 | import { DeleteResult, UpdateResult } from "typeorm"; 5 | 6 | export class UserController { 7 | constructor( 8 | private readonly userService: UserService = new UserService(), 9 | private readonly httpResponse: HttpResponse = new HttpResponse() 10 | ) {} 11 | async getUsers(req: Request, res: Response) { 12 | try { 13 | const data = await this.userService.findAllUser(); 14 | if (data.length === 0) { 15 | return this.httpResponse.NotFound(res, "No existe dato"); 16 | } 17 | return this.httpResponse.Ok(res, data); 18 | } catch (e) { 19 | return this.httpResponse.Error(res, e); 20 | } 21 | } 22 | 23 | async getUserById(req: Request, res: Response) { 24 | const { id } = req.params; 25 | try { 26 | const data = await this.userService.findUserById(id); 27 | if (!data) { 28 | return this.httpResponse.NotFound(res, "No existe dato"); 29 | } 30 | return this.httpResponse.Ok(res, data); 31 | } catch (e) { 32 | console.error(e); 33 | return this.httpResponse.Error(res, e); 34 | } 35 | } 36 | async getUserWithRelationById(req: Request, res: Response) { 37 | const { id } = req.params; 38 | try { 39 | const data = await this.userService.findUserWithRelation(id); 40 | if (!data) { 41 | return this.httpResponse.NotFound(res, "No existe dato"); 42 | } 43 | return this.httpResponse.Ok(res, data); 44 | } catch (e) { 45 | console.error(e); 46 | return this.httpResponse.Error(res, e); 47 | } 48 | } 49 | async createUser(req: Request, res: Response) { 50 | try { 51 | const data = await this.userService.createUser(req.body); 52 | return this.httpResponse.Ok(res, data); 53 | } catch (e) { 54 | console.error(e); 55 | return this.httpResponse.Error(res, e); 56 | } 57 | } 58 | async updateUser(req: Request, res: Response) { 59 | const { id } = req.params; 60 | try { 61 | const data: UpdateResult = await this.userService.updateUser( 62 | id, 63 | req.body 64 | ); 65 | 66 | if (!data.affected) { 67 | return this.httpResponse.NotFound(res, "Hay un error en actualizar"); 68 | } 69 | 70 | return this.httpResponse.Ok(res, data); 71 | } catch (e) { 72 | console.error(e); 73 | return this.httpResponse.Error(res, e); 74 | } 75 | } 76 | async deleteUser(req: Request, res: Response) { 77 | const { id } = req.params; 78 | try { 79 | const data: DeleteResult = await this.userService.deleteUser(id); 80 | if (!data.affected) { 81 | return this.httpResponse.NotFound(res, "Hay un error en borrar"); 82 | } 83 | return this.httpResponse.Ok(res, data); 84 | } catch (e) { 85 | console.error(e); 86 | return this.httpResponse.Error(res, e); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/category/controllers/category.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { DeleteResult, UpdateResult } from "typeorm"; 3 | import { HttpResponse } from "../../shared/response/http.response"; 4 | import { CategoryService } from "../services/category.service"; 5 | 6 | export class CategoryController { 7 | constructor( 8 | private readonly categoryService: CategoryService = new CategoryService(), 9 | private readonly httpResponse: HttpResponse = new HttpResponse() 10 | ) {} 11 | async getCategories(req: Request, res: Response) { 12 | try { 13 | const data = await this.categoryService.findAllCategoties(); 14 | if (data.length === 0) { 15 | return this.httpResponse.NotFound(res, "No existe dato"); 16 | } 17 | return this.httpResponse.Ok(res, data); 18 | } catch (e) { 19 | console.error(e); 20 | return this.httpResponse.Error(res, e); 21 | } 22 | } 23 | async getCategoryById(req: Request, res: Response) { 24 | const { id } = req.params; 25 | try { 26 | const data = await this.categoryService.findCategoryById(id); 27 | if (!data) { 28 | return this.httpResponse.NotFound(res, "No existe dato"); 29 | } 30 | return this.httpResponse.Ok(res, data); 31 | } catch (e) { 32 | console.error(e); 33 | return this.httpResponse.Error(res, e); 34 | } 35 | } 36 | 37 | async findCategoryWithProduct(req: Request, res: Response) { 38 | const { id } = req.params; 39 | try { 40 | const data = await this.categoryService.findCategoryWithProduct(id); 41 | if (!data) { 42 | return this.httpResponse.NotFound(res, "No existe dato"); 43 | } 44 | return this.httpResponse.Ok(res, data); 45 | } catch (e) { 46 | console.error(e); 47 | return this.httpResponse.Error(res, e); 48 | } 49 | } 50 | 51 | async createCategory(req: Request, res: Response) { 52 | try { 53 | const data = await this.categoryService.createCategory(req.body); 54 | return this.httpResponse.Ok(res, data); 55 | } catch (e) { 56 | console.error(e); 57 | return this.httpResponse.Error(res, e); 58 | } 59 | } 60 | async updateCategory(req: Request, res: Response) { 61 | const { id } = req.params; 62 | try { 63 | const data: UpdateResult = await this.categoryService.updateCategory( 64 | id, 65 | req.body 66 | ); 67 | if (!data.affected) { 68 | return this.httpResponse.NotFound(res, "Hay un error en actualizar"); 69 | } 70 | return this.httpResponse.Ok(res, data); 71 | } catch (e) { 72 | console.error(e); 73 | return this.httpResponse.Error(res, e); 74 | } 75 | } 76 | async deleteCategory(req: Request, res: Response) { 77 | const { id } = req.params; 78 | try { 79 | const data: DeleteResult = await this.categoryService.deleteCategory(id); 80 | if (!data.affected) { 81 | return this.httpResponse.NotFound(res, "Hay un error en borrar"); 82 | } 83 | 84 | return this.httpResponse.Ok(res, data); 85 | } catch (e) { 86 | console.error(e); 87 | return this.httpResponse.Error(res, e); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/product/controllers/product.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { DeleteResult, UpdateResult } from "typeorm"; 3 | import { HttpResponse } from "../../shared/response/http.response"; 4 | import { ProductService } from "../services/product.service"; 5 | 6 | export class ProductController { 7 | constructor( 8 | private readonly productService: ProductService = new ProductService(), 9 | private readonly httpResponse: HttpResponse = new HttpResponse() 10 | ) {} 11 | async getProducts(req: Request, res: Response) { 12 | try { 13 | const data = await this.productService.findAllProducts(); 14 | if (data.length === 0) { 15 | return this.httpResponse.NotFound(res, "No existe dato"); 16 | } 17 | return this.httpResponse.Ok(res, data); 18 | } catch (e) { 19 | console.error(e); 20 | return this.httpResponse.Error(res, e); 21 | } 22 | } 23 | async getProductById(req: Request, res: Response) { 24 | const { id } = req.params; 25 | try { 26 | const data = await this.productService.findProductById(id); 27 | if (!data) { 28 | return this.httpResponse.NotFound(res, "No existe dato"); 29 | } 30 | return this.httpResponse.Ok(res, data); 31 | } catch (e) { 32 | console.error(e); 33 | return this.httpResponse.Error(res, e); 34 | } 35 | } 36 | 37 | async findProductsByName(req: Request, res: Response) { 38 | const { search } = req.query; 39 | try { 40 | if (search !== undefined) { 41 | const data = await this.productService.findProductsByName(search); 42 | if (!data) { 43 | return this.httpResponse.NotFound(res, "No existe dato"); 44 | } 45 | return this.httpResponse.Ok(res, data); 46 | } 47 | } catch (e) { 48 | console.error(e); 49 | return this.httpResponse.Error(res, e); 50 | } 51 | } 52 | async createProduct(req: Request, res: Response) { 53 | try { 54 | const data = await this.productService.createProduct(req.body); 55 | if (!data) { 56 | return this.httpResponse.NotFound(res, "No existe dato"); 57 | } 58 | return this.httpResponse.Ok(res, data); 59 | } catch (e) { 60 | console.error(e); 61 | return this.httpResponse.Error(res, e); 62 | } 63 | } 64 | async updateProduct(req: Request, res: Response) { 65 | const { id } = req.params; 66 | try { 67 | const data: UpdateResult = await this.productService.updateProduct( 68 | id, 69 | req.body 70 | ); 71 | if (!data.affected) { 72 | return this.httpResponse.NotFound(res, "Hay un error en actualizar"); 73 | } 74 | 75 | return this.httpResponse.Ok(res, data); 76 | } catch (e) { 77 | console.error(e); 78 | return this.httpResponse.Error(res, e); 79 | } 80 | } 81 | async deleteProduct(req: Request, res: Response) { 82 | const { id } = req.params; 83 | try { 84 | const data: DeleteResult = await this.productService.deleteProduct(id); 85 | res.status(200).json(data); 86 | if (!data.affected) { 87 | return this.httpResponse.NotFound(res, "Hay un error en borrar"); 88 | } 89 | return this.httpResponse.Ok(res, data); 90 | } catch (e) { 91 | console.error(e); 92 | return this.httpResponse.Error(res, e); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Curso de NodeJS con TypeScript y TypeORM 2 | 3 | ## Mira el curso completo [aqui](https://www.youtube.com/c/codrrdev) 4 | 5 | En este curso aprenderas como generar una API REST compleja con NodeJS utilizando como lenguage core `TypeScript` y `TypeORM` como ORM SQL. 6 | 7 | ## Tecnologias a aplicar: 8 | 9 | - POO. 10 | - Docker Compose como base de datos. 11 | - Configuracion de TypeScript. 12 | - Configuracion de rutas, controladores, servicios y entidades. 13 | 14 | ## Lista de dependencias para instalacion: 15 | 16 | Dependencias necesarias: 17 | 18 | ``` 19 | npm install class-validator class-transformer cors dotenv express morgan mysql typeorm typeorm-naming-strategies typescript 20 | ``` 21 | 22 | Dependencias de desarrollo necesarias: 23 | 24 | ``` 25 | npm install -D @types/cors @types/express @types/morgan concurrently nodemon 26 | ``` 27 | 28 | # Clases: 29 | 30 | METODOS: 31 | 32 | - PRACTICO 33 | - TEORICO 34 | - TEORICO / PRACTICO 35 | 36 | | CLASE 1 | Metodo | Contenido | 37 | | --------------- | ------------------------------------------------------ | -------------------------------------------- | 38 | | **Inicio - P1** | TEORICO / PRACTICO | Creación de `package.json` | 39 | | **Inicio - P1** | PRACTICO | Instalando dependencias necesarias | 40 | | **Inicio - P1** | PRACTICO | Agregando dependencias a utilizar | 41 | | **Inicio - P1** | PRACTICO | Configurando `Express` | 42 | | **Inicio - P1** | PRACTICO | Levantando un servidor a traves de una clase | 43 | | **Ruteo - P2** | TEORICO / PRACTICO | Aplicar un prefijo global para nuestra API | 44 | | **Ruteo - P2** | PRACTICO | Generando mi primera ruta | 45 | | **Ruteo - P2** | PRACTICO | Ejecutando lo realizado en Postman | 46 | 47 | | CLASE 2 | Metodo | Contenido | 48 | | --------------- | ------------------------------------------------------ | ----------------------------------------------------------- | 49 | | **Ruteo** | PRACTICO | Modalidad de ruta para aplicar en un servidor basado en POO | 50 | | **Ruteo** | PRACTICO | Generando rutas extendidas de una ruta base | 51 | | **Controlador** | TEORICO / PRACTICO | ¿Que es un controlador? Explicado en ruta | 52 | 53 | | CLASE 3 | Metodo | Contenido | 54 | | ---------- | ------------------------------------------------------ | -------------------------------------------------------- | 55 | | **Config** | PRACTICO | Configuracion de variables de entorno | 56 | | **Config** | TEORICO / PRACTICO | ¿Que es un entorno de ejecucion? Explicado en config | 57 | | **Config** | PRACTICO | Declaracion de variables de entorno en nuestro server.ts | 58 | 59 | | CLASE 4 | Metodo | Contenido | 60 | | ----------------------- | ------------------------------------------------------ | --------------------------------------------------------- | 61 | | **Docker Compose (DB)** | PRACTICO | Crear nuestro `docker-compose.yml` | 62 | | **Docker Compose (DB)** | TEORICO / PRACTICO | Ejecutando nuestro docker-compose y comprobar la conexion | 63 | | **TypeORM (DB)** | PRACTICO | Crear nuestro getter de configuracion de conexion | 64 | | **TypeORM (DB)** | PRACTICO | Ejecutar la conexion en nuestro server | 65 | | **TypeORM (DB)** | PRACTICO | Crear nuestra entidad base con datos comunes | 66 | | **TypeORM (DB)** | PRACTICO | Creando nuestra primer entidad para nuestra base de datos | 67 | 68 | | CLASE 5 | Metodo | Contenido | 69 | | ----------- | -------------------------------------------- | ----------------------------------------------------------------------------------- | 70 | | **Entidad** | TEORICO | Propuesta de arquitectura de entidades | 71 | | **General** | PRACTICO | Modificacion de distribucion de proyecto de manera modular | 72 | | **Entidad** | TEORICO | Muestra de relaciones (uno a muchos (N:1), uno a uno (1:1) y muchos a muchos (N:N)) | 73 | | **Entidad** | PRACTICO | Users: Modificacion de entidad usuario | 74 | | **Entidad** | PRACTICO | Customer: Creacion de entidad y relaciones | 75 | | **Entidad** | PRACTICO | Products: Creacion de entidad y relaciones | 76 | | **Entidad** | PRACTICO | Categories: Creacion de entidad y relaciones | 77 | | **Entidad** | PRACTICO | Purchases: Creacion de entidad y relaciones | 78 | | **Entidad** | PRACTICO | `purchases_products`: Creacion de entidad N:N custom y relaciones | 79 | 80 | | CLASE 6 | Metodo | Contenido | 81 | | --------------- | ------------------------------------------------------ | ----------------------------------------------------------------------- | 82 | | **Entidad** | PRACTICO | Instalando `class-transformer` para excluir datos en nuestra entidad | 83 | | **Servicio** | TEORICO / PRACTICO | Que son y para que sirven los servicios | 84 | | **Servicio** | PRACTICO | Instanciando metodos desplegados con funcion de repositorio con TypeORM | 85 | | **Servicio** | PRACTICO | Creacion de `findAll` `findById` `create` `updtae` `delete` | 86 | | **Controlador** | PRACTICO | Integracion de metodos instanciando servicios con los controladores | 87 | 88 | | CLASE 7 | Metodo | Contenido | 89 | | --------------- | -------------------------------------------- | ------------------------------------------------------------ | 90 | | **Controlador** | PRACTICO | Crear nuestro enum de status code | 91 | | **Controlador** | PRACTICO | Crear nuestra clase `HttpResponse` e integrando nuestro enum | 92 | | **Controlador** | PRACTICO | Instanciar nuestra clase de respuesta en nuestro controlador | 93 | | **Controlador** | PRACTICO | Editar nuestros metodos en controlador | 94 | 95 | | CLASE 8 | Metodo | Contenido | 96 | | --------------- | ------------------------------------------------------ | --------------------------------------------------------------------------- | 97 | | **TypeORM** | PRACTICO | Pasaje de typeorm 0.2 a 0.3 | 98 | | **TypeORM** | PRACTICO | Crear nuestro `Data Source` | 99 | | **TypeORM** | PRACTICO | Editar nuestra configuracion de `Data Source` y prepararla para migraciones | 100 | | **TypeORM** | PRACTICO | Editar cada uno de los metodos deprecados de la version anterior | 101 | | **Servicio** | PRACTICO | Modificar los metodos que ya no son soportados en la version 0.3 | 102 | | **Migraciones** | TEORICO / PRACTICO | Por que migraciones? | 103 | | **Migraciones** | PRACTICO | Creando nuestros script de `typeorm` `migration:generate` y `migration:run` | 104 | | **Migraciones** | PRACTICO | Correr migraciones y probando nuestro codigo | 105 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | "experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */, 18 | "emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */, 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | "module": "commonjs" /* Specify what module code is generated. */, 28 | // "rootDir": "./", /* Specify the root folder within your source files. */ 29 | // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | "outDir": "./dist" /* Specify an output folder for all emitted files. */, 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 68 | 69 | /* Interop Constraints */ 70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 72 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, 73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 74 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 75 | 76 | /* Type Checking */ 77 | "strict": true /* Enable all strict type-checking options. */, 78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 82 | "strictPropertyInitialization": true /* Check for class properties that are declared but not set in the constructor. */, 83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 96 | 97 | /* Completeness */ 98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 100 | }, 101 | "exclude": ["node_modules", "dist", "mysql"] 102 | } 103 | --------------------------------------------------------------------------------