diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..31d867b1 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +PORT=8080 +HOST_NAME=http://localhost +USER_MONGO_ATLAS=boticario +PASSWORD_MONGO_ATLAS=boticario123 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 6214c438..48d19853 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,5 @@ typings/ # DynamoDB Local files .dynamodb/ + +package-lock.json \ No newline at end of file diff --git a/Product - Boticario.postman_collection.json b/Product - Boticario.postman_collection.json new file mode 100644 index 00000000..a6358dc8 --- /dev/null +++ b/Product - Boticario.postman_collection.json @@ -0,0 +1,106 @@ +{ + "info": { + "_postman_id": "b17672af-8b36-48c9-9f95-2cc79df9a93d", + "name": "Product - Boticario", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "Create Product", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"sku\": 43264,\r\n \"name\": \"L'Oréal Professionnel Expert Absolut Repair Cortex Lipidium - Máscara de Reconstrução 500g\",\r\n \"inventory\": {\r\n \"warehouses\": [\r\n {\r\n \"locality\": \"SP\",\r\n \"quantity\": 22,\r\n \"type\": \"ECOMMERCE\"\r\n },\r\n {\r\n \"locality\": \"MOEMA\",\r\n \"quantity\": 18,\r\n \"type\": \"PHYSICAL_STORE\"\r\n }\r\n ]\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/products", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "products" + ] + } + }, + "response": [] + }, + { + "name": "Get Product", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8080/products/43264", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "products", + "43264" + ] + } + }, + "response": [] + }, + { + "name": "Update Product", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\r\n \"name\": \"L'Oréal Professionnel Expert Absolut Repair Cortex Lipidium - Máscara de Reconstrução 500g\",\r\n \"inventory\": {\r\n \"warehouses\": [\r\n {\r\n \"locality\": \"SP\",\r\n \"quantity\": 26,\r\n \"type\": \"ECOMMERCE\"\r\n },\r\n {\r\n \"locality\": \"PR\",\r\n \"quantity\": 24,\r\n \"type\": \"PHYSICAL_STORE\"\r\n }\r\n ]\r\n }\r\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8080/products/43264", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "products", + "43264" + ] + } + }, + "response": [] + }, + { + "name": "Delete Product", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "http://localhost:8080/products/43264", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8080", + "path": [ + "products", + "43264" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 0857d707..ec23dd06 100644 --- a/README.md +++ b/README.md @@ -1,74 +1,66 @@ -### Backend Test -[![Build Status](https://travis-ci.org/belezanaweb/test-nodejs.svg?branch=master)](https://travis-ci.org/belezanaweb/test-nodejs) +### test-nodejs Boticario -Esta é uma avaliação básica de código. +## Requirements +- Node (v18.16.0) +- NPM (v9.5.1) or yarn (v1.22.19) -O objetivo é conhecer um pouco do seu conhecimento/prática de RESTful e NodeJS. +## Install dependencies +```sh +$ npm i +``` +## Running project +```sh +$ npm run start:dev +``` -Recomendamos que você não gaste mais do que 4 - 6 horas. +## Running tests +```sh +$ npm run test +``` -Faça um fork deste repositório. +## Documentation -Ao finalizar o teste, submeta um pull request para o repositório que nosso time será notificado. +- Apos rodar o projeto acesse o link `http://localhost:8080/api` -### Tarefas +## UseCases +- Create Product +- Get Product +- Update Product +- Delete Product -Com a seguinte representação de produto: - -```json -{ - "sku": 43264, - "name": "L'Oréal Professionnel Expert Absolut Repair Cortex Lipidium - Máscara de Reconstrução 500g", - "inventory": { - "quantity": 15, - "warehouses": [ - { - "locality": "SP", - "quantity": 12, - "type": "ECOMMERCE" - }, - { - "locality": "MOEMA", - "quantity": 3, - "type": "PHYSICAL_STORE" - } - ] - }, - "isMarketable": true -} -``` +### Observations + +- Realizei a deleção para deleção logica (Soft-Delete) +- Foi disponibilizado na raiz do projeto uma collection do postman de produtos `Product - Boticario.postman_collection.json` + +### Tarefas Crie endpoints para as seguintes ações: -- [ ] Criação de produto onde o payload será o json informado acima (exceto as propriedades **isMarketable** e **inventory.quantity**) +- [x] Criação de produto onde o payload será o json informado acima (exceto as propriedades **isMarketable** e **inventory.quantity**) -- [ ] Edição de produto por **sku** +- [x] Edição de produto por **sku** -- [ ] Recuperação de produto por **sku** +- [x] Recuperação de produto por **sku** -- [ ] Deleção de produto por **sku** +- [x] Deleção de produto por **sku** ### Requisitos -- [ ] Toda vez que um produto for recuperado por **sku** deverá ser calculado a propriedade: **inventory.quantity** +- [x] Toda vez que um produto for recuperado por **sku** deverá ser calculado a propriedade: **inventory.quantity** A propriedade inventory.quantity é a soma da quantity dos warehouses -- [ ] Toda vez que um produto for recuperado por **sku** deverá ser calculado a propriedade: **isMarketable** +- [x] Toda vez que um produto for recuperado por **sku** deverá ser calculado a propriedade: **isMarketable** Um produto é marketable sempre que seu inventory.quantity for maior que 0 -- [ ] Caso um produto já existente em memória tente ser criado com o mesmo **sku** uma exceção deverá ser lançada +- [x] Caso um produto já existente em memória tente ser criado com o mesmo **sku** uma exceção deverá ser lançada Dois produtos são considerados iguais se os seus skus forem iguais -- [ ] Ao atualizar um produto, o antigo deve ser sobrescrito com o que esta sendo enviado na requisição +- [x] Ao atualizar um produto, o antigo deve ser sobrescrito com o que esta sendo enviado na requisição A requisição deve receber o sku e atualizar com o produto que tbm esta vindo na requisição - -### Dicas - -- Os produtos podem ficar em memória, não é necessário persistir os dados -- Testes são sempre bem-vindos :smiley: diff --git a/jest.config.ts b/jest.config.ts new file mode 100644 index 00000000..7e9d4eed --- /dev/null +++ b/jest.config.ts @@ -0,0 +1,5 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + setupFilesAfterEnv: ['/jest.setup.ts'], +}; diff --git a/jest.setup.ts b/jest.setup.ts new file mode 100644 index 00000000..49b43222 --- /dev/null +++ b/jest.setup.ts @@ -0,0 +1,2 @@ +import 'reflect-metadata' +jest.setTimeout(30000) \ No newline at end of file diff --git a/package.json b/package.json index bb1fb633..e4503e5f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "start:dev": "tsnd --respawn --transpile-only --clear src/server.ts", + "test": "jest", + "test:watch": "jest --watch" }, "repository": { "type": "git", @@ -16,5 +18,24 @@ "bugs": { "url": "https://github.com/belezanaweb/test-nodejs/issues" }, - "homepage": "https://github.com/belezanaweb/test-nodejs#readme" + "homepage": "https://github.com/belezanaweb/test-nodejs#readme", + "devDependencies": { + "@jest/globals": "^29.5.0", + "@types/express": "^4.17.17", + "@types/jest": "^29.5.1", + "@types/node": "^18.15.12", + "jest": "^29.5.0", + "reflect-metadata": "^0.1.13", + "ts-jest": "^29.1.0", + "ts-node-dev": "^2.0.0", + "typescript": "^4.9.5" + }, + "dependencies": { + "dotenv": "^16.0.3", + "express": "^4.18.2", + "express-async-errors": "^3.1.1", + "mongoose": "^7.0.4", + "swagger-ui-express": "^4.6.2", + "tsyringe": "^4.7.0" + } } diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 00000000..7e892db3 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,30 @@ +import "reflect-metadata"; +import express, { Request, Response, NextFunction } from "express"; +import "express-async-errors" +import router from "./routes"; +import swaggerUi from 'swagger-ui-express'; +import swaggerDocs from './swagger.json'; +import { AppError } from "./shared/excepetions/errors"; +import '../src/shared/container' + +const app = express(); + +app.use(express.json()); +app.use(router); +app.use("/api", swaggerUi.serve, swaggerUi.setup(swaggerDocs)); +app.use( + (err: Error, request: Request, response: Response, next: NextFunction) => { + if (err instanceof AppError) { + return response.status(err.statusCode).json({ + message: err.message, + }); + } + + return response.status(500).json({ + status: "error", + message: `Internal server error - ${err.message}`, + }); + } +); + +export { app }; \ No newline at end of file diff --git a/src/domain/entities/products/Product.spec.ts b/src/domain/entities/products/Product.spec.ts new file mode 100644 index 00000000..39f95f30 --- /dev/null +++ b/src/domain/entities/products/Product.spec.ts @@ -0,0 +1,31 @@ +import { describe, it, expect, beforeAll } from "@jest/globals" +import { IProduct } from "./Product" +import { Inventory } from "./interface/Inventory" +import { Warehouses } from "./interface/Warehouses" + +describe('Product', () => { + let product: IProduct + let inventory: Inventory + let warehouses: Array + + beforeAll(() => { + warehouses = [{ + locality: "Florianopolis", + quantity: 15, + type: "PHYSICAL_STORE" + }, { + locality: "PR", + quantity: 51, + type: "PHYSICAL_STORE" + }] + inventory = { warehouses: warehouses } + product = new IProduct({ sku: 10231, name: 'Zaad', inventory: inventory}) + }) + + it('should be able create a new product', () => { + expect(product).toBeInstanceOf(IProduct) + expect(product.sku).toEqual(10231) + expect(product.name).toEqual('Zaad') + expect(product.inventory).toEqual(inventory) + }) +}) \ No newline at end of file diff --git a/src/domain/entities/products/Product.ts b/src/domain/entities/products/Product.ts new file mode 100644 index 00000000..f2280a1d --- /dev/null +++ b/src/domain/entities/products/Product.ts @@ -0,0 +1,20 @@ +import { Inventory } from "./interface/Inventory" + +export class IProduct { + constructor({ + sku, + name, + inventory, + }: { sku: number, name: string, inventory: Inventory}) { + this.sku = sku + this.name = name + this.inventory = inventory + this.isDeleted = false + } + + public sku: number + public name: string + public inventory: Inventory + public isMarketable?: boolean + public isDeleted?: boolean +} \ No newline at end of file diff --git a/src/domain/entities/products/interface/Inventory.ts b/src/domain/entities/products/interface/Inventory.ts new file mode 100644 index 00000000..384143ac --- /dev/null +++ b/src/domain/entities/products/interface/Inventory.ts @@ -0,0 +1,6 @@ +import { Warehouses } from "./Warehouses" + +export interface Inventory { + quantity?: number + warehouses: Warehouses[] +} \ No newline at end of file diff --git a/src/domain/entities/products/interface/Warehouses.ts b/src/domain/entities/products/interface/Warehouses.ts new file mode 100644 index 00000000..40dd8e19 --- /dev/null +++ b/src/domain/entities/products/interface/Warehouses.ts @@ -0,0 +1,5 @@ +export interface Warehouses { + locality: string + quantity: number + type: string +} \ No newline at end of file diff --git a/src/domain/repositories/IProducts.repository.ts b/src/domain/repositories/IProducts.repository.ts new file mode 100644 index 00000000..89ae8d79 --- /dev/null +++ b/src/domain/repositories/IProducts.repository.ts @@ -0,0 +1,12 @@ +import { IProduct } from "../entities/products/Product" +import { ICreateProductDTO } from "../../modules/products/dtos/ICreateProductDTO" +import { IUpdateProductDTO } from "../../modules/products/dtos/IUpdateProductDTO" + +interface IProductsRepository { + create(product: ICreateProductDTO): Promise + getProduct(sku: number): Promise + update(sku: number, data: IUpdateProductDTO): Promise + delete(sku: number, data: IProduct): Promise +} + +export { IProductsRepository } \ No newline at end of file diff --git a/src/domain/repositories/Mongo.repository.ts b/src/domain/repositories/Mongo.repository.ts new file mode 100644 index 00000000..ee53b759 --- /dev/null +++ b/src/domain/repositories/Mongo.repository.ts @@ -0,0 +1,33 @@ +import { IProduct } from "../entities/products/Product"; +import { ICreateProductDTO } from "../../modules/products/dtos/ICreateProductDTO"; +import { IProductsRepository } from "./IProducts.repository"; +import { Product } from "./schemas/Product.Schema"; + +export class MongoRepository implements IProductsRepository { + async create(newProduct: ICreateProductDTO): Promise { + const { sku, name, inventory } = newProduct + let product = await Product.create({ + sku, + name, + inventory, + isDeleted: false + }) + return product.save() + } + + async getProduct(sku: number): Promise { + return await Product.findOne({ sku }).lean().catch(err => err) + } + + async update(sku: number, data: IProduct): Promise { + return await Product.findOneAndUpdate({ sku: sku }, data, { returnDocument: 'after' }) + } + + async delete(sku: number, product: IProduct): Promise { + return await Product + .findOneAndUpdate({ sku: sku }, product) + .catch(err => { + throw err + }) + } +} \ No newline at end of file diff --git a/src/domain/repositories/schemas/Product.Schema.ts b/src/domain/repositories/schemas/Product.Schema.ts new file mode 100644 index 00000000..1d25df60 --- /dev/null +++ b/src/domain/repositories/schemas/Product.Schema.ts @@ -0,0 +1,17 @@ +import { Schema, model } from "mongoose"; +import { IProduct } from "../../entities/products/Product"; + +const Product = model(`Product`, new Schema({ + sku: { type: Number, required: true, unique: true }, + name: { type: String, required: true }, + inventory: { + warehouses: [{ + locality: { type: String, required: true }, + quantity: { type: Number, required: true }, + type: { type: String, required: true } + }] + }, + isDeleted: { type: Boolean, required: true} +})) + +export { Product } \ No newline at end of file diff --git a/src/modules/products/dtos/ICreateProductDTO.ts b/src/modules/products/dtos/ICreateProductDTO.ts new file mode 100644 index 00000000..951e2ec3 --- /dev/null +++ b/src/modules/products/dtos/ICreateProductDTO.ts @@ -0,0 +1,18 @@ +interface ICreateProductDTO { + sku: number + name: string + inventory: Inventory +} + +interface Inventory { + warehouses: Warehouses[] +} + +interface Warehouses { + locality: string + quantity: number + type: string +} + + +export { ICreateProductDTO } \ No newline at end of file diff --git a/src/modules/products/dtos/IUpdateProductDTO.ts b/src/modules/products/dtos/IUpdateProductDTO.ts new file mode 100644 index 00000000..16ee5e48 --- /dev/null +++ b/src/modules/products/dtos/IUpdateProductDTO.ts @@ -0,0 +1,16 @@ +interface IUpdateProductDTO { + name: string + inventory: Inventory +} + +interface Inventory { + warehouses: Warehouses[] +} + +interface Warehouses { + locality: string + quantity: number + type: string +} + +export { IUpdateProductDTO } \ No newline at end of file diff --git a/src/modules/products/service/IStockProduct.service.ts b/src/modules/products/service/IStockProduct.service.ts new file mode 100644 index 00000000..aeeda711 --- /dev/null +++ b/src/modules/products/service/IStockProduct.service.ts @@ -0,0 +1,7 @@ +import { IProduct } from "../../../domain/entities/products/Product"; + +interface IStockProductService { + execute(product: IProduct): Promise +} + +export { IStockProductService } \ No newline at end of file diff --git a/src/modules/products/service/StockProduct.service.spec.ts b/src/modules/products/service/StockProduct.service.spec.ts new file mode 100644 index 00000000..83a0e512 --- /dev/null +++ b/src/modules/products/service/StockProduct.service.spec.ts @@ -0,0 +1,60 @@ +import { describe, it, expect, beforeAll, beforeEach } from "@jest/globals" +import { StockProductService } from "./StockProduct.service" + +describe('StockProductService', () => { + const stockProductService = new StockProductService() + + it('should return marketable true if quantity granther then 0', async () => { + const product = generateMongoProductWithQuantity() + const stockProduct = await stockProductService.execute(product) + expect(stockProduct.isMarketable).toEqual(true) + expect(stockProduct.inventory.quantity).toEqual(66) + }) + + it('should return marketable false if quantity is equal 0', async () => { + const product = generateMongoProductWithoutQuantity() + const stockProduct = await stockProductService.execute(product) + expect(stockProduct.isMarketable).toEqual(false) + expect(stockProduct.inventory.quantity).toEqual(0) + }) +}) + +function generateMongoProductWithQuantity() { + return { + sku: 10231, + name: 'Zaad', + inventory: { + warehouses: [ + { + locality: "FLORIANOPOLIS", + quantity: 15, + type: "PHYSICAL_STORE" + }, { + locality: "PR", + quantity: 51, + type: "PHYSICAL_STORE" + } + ] + } + } +} + +function generateMongoProductWithoutQuantity() { + return { + sku: 51423, + name: 'Malbec', + inventory: { + warehouses: [ + { + locality: "PE", + quantity: 0, + type: "PHYSICAL_STORE" + }, { + locality: "SP", + quantity: 0, + type: "PHYSICAL_STORE" + } + ] + } + } +} \ No newline at end of file diff --git a/src/modules/products/service/StockProduct.service.ts b/src/modules/products/service/StockProduct.service.ts new file mode 100644 index 00000000..a1ec75d7 --- /dev/null +++ b/src/modules/products/service/StockProduct.service.ts @@ -0,0 +1,10 @@ +import { IProduct } from "../../../domain/entities/products/Product"; + +export class StockProductService { + async execute (product: IProduct): Promise { + const { inventory: { warehouses } } = product + const quantity = warehouses.reduce((total, { quantity }) => total + quantity, 0) + Object.assign(product, { isMarketable: (quantity > 0), inventory: { quantity, warehouses }}) + return product + } +} \ No newline at end of file diff --git a/src/modules/products/useCases/createProduct/CreateProduct.useCase.ts b/src/modules/products/useCases/createProduct/CreateProduct.useCase.ts new file mode 100644 index 00000000..cbacdf0c --- /dev/null +++ b/src/modules/products/useCases/createProduct/CreateProduct.useCase.ts @@ -0,0 +1,28 @@ +import { inject, injectable } from "tsyringe"; +import { IProductsRepository } from "../../../../domain/repositories/IProducts.repository"; +import { ICreateProductDTO } from "../../dtos/ICreateProductDTO"; +import { AppError } from "../../../../shared/excepetions/errors"; + +@injectable() +class CreateProductUseCase { + constructor( + @inject("ProductsRepository") + private productsRepository: IProductsRepository + ) { } + + async execute({ + sku, + name, + inventory + }: ICreateProductDTO) : Promise { + const foundProduct = await this.productsRepository.getProduct(sku) + + if (foundProduct) { + throw new AppError('sku already exist') + } + + await this.productsRepository.create({sku, name, inventory}) + } +} + +export { CreateProductUseCase } \ No newline at end of file diff --git a/src/modules/products/useCases/createProduct/CreateProductController.ts b/src/modules/products/useCases/createProduct/CreateProductController.ts new file mode 100644 index 00000000..eebb35ec --- /dev/null +++ b/src/modules/products/useCases/createProduct/CreateProductController.ts @@ -0,0 +1,24 @@ +import { Request, Response } from "express"; +import { CreateProductUseCase } from './CreateProduct.useCase'; +import { container } from "tsyringe" +import { AppError } from "../../../../shared/excepetions/errors"; + +export class CreateProductController { + constructor() { } + + async handle(request: Request, response: Response): Promise { + const { sku, name, inventory } = request.body + try { + const createProductUseCase = container.resolve(CreateProductUseCase) + await createProductUseCase.execute({ + sku, + name, + inventory + }) + + return response.status(201).send() + } catch (error: any) { + throw new AppError(error.message || 'Error on createa new product') + } + } +} \ No newline at end of file diff --git a/src/modules/products/useCases/createProduct/CreateProductUseCase.spec.ts b/src/modules/products/useCases/createProduct/CreateProductUseCase.spec.ts new file mode 100644 index 00000000..b08df955 --- /dev/null +++ b/src/modules/products/useCases/createProduct/CreateProductUseCase.spec.ts @@ -0,0 +1,74 @@ +import { describe, it, expect, beforeAll, beforeEach, jest } from "@jest/globals" +import { CreateProductUseCase } from "./CreateProduct.useCase" +import { MongoRepository } from "../../../../domain/repositories/Mongo.repository" +import { IProduct } from "../../../../domain/entities/products/Product" +import { AppError } from "../../../../shared/excepetions/errors" + +describe('CreateProductUseCase', () => { + let createProductUseCase: CreateProductUseCase + let productsRepository: MongoRepository + + beforeEach(() => { + productsRepository = new MongoRepository() + createProductUseCase = new CreateProductUseCase(productsRepository) + }) + + it('should be possible create a product', async () => { + jest.spyOn(productsRepository, 'getProduct').mockImplementationOnce(async () => getProductEmpty()) + jest.spyOn(productsRepository, 'create').mockImplementationOnce(async () => createProductRepository()) + const product = createProductRepository() + const createProduct = await createProductUseCase.execute(product) + expect(createProduct).toBe(void(0)) + }) + + // it('should be an error create a product with same sku', async () => { + // jest.spyOn(productsRepository, 'getProduct').mockImplementationOnce(async () => getProduct()) + // const product = createProductRepository() + // const createProduct = await createProductUseCase.execute(product) + // await expect(createProduct).rejects.toBeInstanceOf(AppError) + // }) +}) + +function getProductEmpty() { + return null +} + +function createProductRepository(): IProduct { + return { + sku: 10231, + name: 'Zaad', + inventory: { + warehouses: [ + { + locality: "FLORIANOPOLIS", + quantity: 15, + type: "PHYSICAL_STORE" + }, { + locality: "PR", + quantity: 51, + type: "PHYSICAL_STORE" + } + ] + } + } +} + +function getProduct() { + return { + sku: 10231, + name: 'Zaad', + inventory: { + warehouses: [ + { + locality: "FLORIANOPOLIS", + quantity: 15, + type: "PHYSICAL_STORE" + }, { + locality: "PR", + quantity: 51, + type: "PHYSICAL_STORE" + } + ] + } + } +} \ No newline at end of file diff --git a/src/modules/products/useCases/deleteProduct/DeleteProduct.useCase.ts b/src/modules/products/useCases/deleteProduct/DeleteProduct.useCase.ts new file mode 100644 index 00000000..3788fc83 --- /dev/null +++ b/src/modules/products/useCases/deleteProduct/DeleteProduct.useCase.ts @@ -0,0 +1,25 @@ +import { inject, injectable } from "tsyringe" +import { IProductsRepository } from "../../../../domain/repositories/IProducts.repository"; +import { AppError } from "../../../../shared/excepetions/errors"; +import { IProduct } from "../../../../domain/entities/products/Product"; + +@injectable() +class DeleteProductUseCase { + constructor( + @inject("ProductsRepository") + private productsRepository: IProductsRepository + ) { } + + async execute( sku : number) : Promise { + const foundProduct = await this.productsRepository.getProduct(sku) + + if (!foundProduct) { + throw new AppError(`Product does not exist`) + } + + Object.assign(foundProduct, { isDeleted: true }) + return await this.productsRepository.delete(sku, foundProduct) + } +} + +export { DeleteProductUseCase } \ No newline at end of file diff --git a/src/modules/products/useCases/deleteProduct/DeleteProductController.ts b/src/modules/products/useCases/deleteProduct/DeleteProductController.ts new file mode 100644 index 00000000..087d7e71 --- /dev/null +++ b/src/modules/products/useCases/deleteProduct/DeleteProductController.ts @@ -0,0 +1,22 @@ +import { Request, Response } from "express"; +import { container } from "tsyringe" +import { DeleteProductUseCase } from "./DeleteProduct.useCase"; +import { AppError } from "../../../../shared/excepetions/errors"; + +export class DeleteProductController { + constructor() { } + + async handle(request: Request, response: Response): Promise { + try { + const { sku } = request.params + const deleteProductUseCase = container.resolve(DeleteProductUseCase) + await deleteProductUseCase.execute( + +sku + ) + + return response.status(200).json({success: true}) + } catch (error: any) { + throw new AppError(error.message || 'Error on delete product') + } + } +} \ No newline at end of file diff --git a/src/modules/products/useCases/deleteProduct/DeleteProductUseCase.spec.ts b/src/modules/products/useCases/deleteProduct/DeleteProductUseCase.spec.ts new file mode 100644 index 00000000..086a4594 --- /dev/null +++ b/src/modules/products/useCases/deleteProduct/DeleteProductUseCase.spec.ts @@ -0,0 +1,75 @@ +import { describe, it, expect, beforeAll, beforeEach, jest } from "@jest/globals" +import { MongoRepository } from "../../../../domain/repositories/Mongo.repository" +import { IProduct } from "../../../../domain/entities/products/Product" +import { AppError } from "../../../../shared/excepetions/errors" +import { DeleteProductUseCase } from "./DeleteProduct.useCase" + +describe('DeleteProductUseCase', () => { + let deleteProductUseCase: DeleteProductUseCase + let productsRepository: MongoRepository + + beforeEach(() => { + productsRepository = new MongoRepository() + deleteProductUseCase = new DeleteProductUseCase(productsRepository) + }) + + it('should be update isDeleted field to true', async () => { + jest.spyOn(productsRepository, 'getProduct').mockImplementationOnce(async () => getProduct()) + jest.spyOn(productsRepository, 'delete').mockImplementationOnce(async () => deleteProductRepository()) + const deleteProduct = await deleteProductUseCase.execute(10231) + expect(deleteProduct.isDeleted).toEqual(true) + }) + + // it('should be return error when doesnt found a product by sku', async () => { + // jest.spyOn(productsRepository, 'getProduct').mockImplementationOnce(async () => getProductEmpty()) + // const deleteProduct = await deleteProductUseCase.execute(10231) + // await expect(deleteProduct).rejects.toBeInstanceOf(AppError) + // }) + +}) + +function getProductEmpty() { + return null +} + +function deleteProductRepository(): IProduct { + return { + sku: 10231, + name: 'Zaad', + inventory: { + warehouses: [ + { + locality: "FLORIANOPOLIS", + quantity: 15, + type: "PHYSICAL_STORE" + }, { + locality: "PR", + quantity: 51, + type: "PHYSICAL_STORE" + } + ] + }, + isDeleted: true + } +} + +function getProduct() { + return { + sku: 10231, + name: 'Zaad', + inventory: { + warehouses: [ + { + locality: "FLORIANOPOLIS", + quantity: 15, + type: "PHYSICAL_STORE" + }, { + locality: "PR", + quantity: 51, + type: "PHYSICAL_STORE" + } + ] + }, + isDeleted: false + } +} \ No newline at end of file diff --git a/src/modules/products/useCases/getProduct/GetProduct.useCase.ts b/src/modules/products/useCases/getProduct/GetProduct.useCase.ts new file mode 100644 index 00000000..df5cf795 --- /dev/null +++ b/src/modules/products/useCases/getProduct/GetProduct.useCase.ts @@ -0,0 +1,26 @@ +import { inject, injectable } from "tsyringe" +import { IProduct } from "../../../../domain/entities/products/Product"; +import { IProductsRepository } from "../../../../domain/repositories/IProducts.repository"; +import { StockProductService } from "../../service/StockProduct.service"; +import { AppError } from "../../../../shared/excepetions/errors"; + +@injectable() +class GetProductUseCase { + constructor( + @inject("ProductsRepository") + private productsRepository: IProductsRepository, + private productService: StockProductService + ) { } + + async execute( sku : number) : Promise { + const foundProduct = await this.productsRepository.getProduct(sku) + + if (!foundProduct) { + throw new AppError(`sku ${sku} is not exist`) + } + + return await this.productService.execute(foundProduct) + } +} + +export { GetProductUseCase } \ No newline at end of file diff --git a/src/modules/products/useCases/getProduct/GetProductController.ts b/src/modules/products/useCases/getProduct/GetProductController.ts new file mode 100644 index 00000000..4f600ee7 --- /dev/null +++ b/src/modules/products/useCases/getProduct/GetProductController.ts @@ -0,0 +1,23 @@ +import { Request, Response } from "express"; +import { container } from "tsyringe" +import { GetProductUseCase } from "./GetProduct.useCase"; +import { AppError } from "../../../../shared/excepetions/errors"; + +export class GetProductController { + constructor() { } + + async handle(request: Request, response: Response): Promise { + try { + const { sku } = request.params + + const getProductUseCase = container.resolve(GetProductUseCase) + const getProduct = await getProductUseCase.execute( + +sku + ) + + return response.status(200).json(getProduct) + } catch (error: any) { + throw new AppError(error.message || 'Error on finding product by sku') + } + } +} \ No newline at end of file diff --git a/src/modules/products/useCases/getProduct/GetProductUseCase.spec.ts b/src/modules/products/useCases/getProduct/GetProductUseCase.spec.ts new file mode 100644 index 00000000..566e3ba2 --- /dev/null +++ b/src/modules/products/useCases/getProduct/GetProductUseCase.spec.ts @@ -0,0 +1,49 @@ +import { describe, it, expect, beforeEach, jest } from "@jest/globals" +import { MongoRepository } from "../../../../domain/repositories/Mongo.repository" +import { GetProductUseCase } from "./GetProduct.useCase" +import { StockProductService } from "../../service/StockProduct.service" +import { IProduct } from "../../../../domain/entities/products/Product" + +describe('GetProductUseCase', () => { + let getProductUseCase: GetProductUseCase + let productService: StockProductService + let productsRepository: MongoRepository + + beforeEach(() => { + productsRepository = new MongoRepository() + productService = new StockProductService() + getProductUseCase = new GetProductUseCase(productsRepository, productService) + }) + + it('should return a product by sku', async () => { + jest.spyOn(productsRepository, 'getProduct').mockImplementationOnce(async () => getProduct()) + const product = await getProductUseCase.execute(10231) + expect(product).toHaveProperty('sku', 10231) + expect(product).toHaveProperty('name', 'Zaad') + expect(product).toHaveProperty('inventory') + expect(product).toHaveProperty('isMarketable', true) + expect(product.inventory).toHaveProperty('quantity', 66) + }) + + +}) + +function getProduct(): IProduct { + return { + sku: 10231, + name: 'Zaad', + inventory: { + warehouses: [ + { + locality: "FLORIANOPOLIS", + quantity: 15, + type: "PHYSICAL_STORE" + }, { + locality: "PR", + quantity: 51, + type: "PHYSICAL_STORE" + } + ] + } + } +} \ No newline at end of file diff --git a/src/modules/products/useCases/updateProduct/UpdateProduct.useCase.ts b/src/modules/products/useCases/updateProduct/UpdateProduct.useCase.ts new file mode 100644 index 00000000..956aca0e --- /dev/null +++ b/src/modules/products/useCases/updateProduct/UpdateProduct.useCase.ts @@ -0,0 +1,25 @@ +import { inject, injectable } from "tsyringe" +import { IProduct } from "../../../../domain/entities/products/Product"; +import { IProductsRepository } from "../../../../domain/repositories/IProducts.repository"; +import { IUpdateProductDTO } from "../../dtos/IUpdateProductDTO"; +import { AppError } from "../../../../shared/excepetions/errors"; + +@injectable() +class UpdateProductUseCase { + constructor( + @inject("ProductsRepository") + private productsRepository: IProductsRepository + ) { } + + async execute( sku : number, newData: IUpdateProductDTO) : Promise { + const foundProduct = await this.productsRepository.getProduct(sku) + + if (!foundProduct) { + throw new AppError(`Product does not exist`) + } + + return await this.productsRepository.update(sku, newData) + } +} + +export { UpdateProductUseCase } \ No newline at end of file diff --git a/src/modules/products/useCases/updateProduct/UpdateProductController.ts b/src/modules/products/useCases/updateProduct/UpdateProductController.ts new file mode 100644 index 00000000..4afe6a5e --- /dev/null +++ b/src/modules/products/useCases/updateProduct/UpdateProductController.ts @@ -0,0 +1,25 @@ +import { Request, Response } from "express"; +import { container } from "tsyringe" +import { UpdateProductUseCase } from "./UpdateProduct.useCase"; +import { AppError } from "../../../../shared/excepetions/errors"; + +export class UpdateProductController { + constructor() { } + + async handle(request: Request, response: Response): Promise { + try { + const { sku } = request.params + const data = request.body + + const updateProductUseCase = container.resolve(UpdateProductUseCase) + const updatedProduct = await updateProductUseCase.execute( + +sku, + data + ) + + return response.status(200).json(updatedProduct) + } catch (error: any) { + throw new AppError(error.message || 'Error on update product by sku') + } + } +} \ No newline at end of file diff --git a/src/modules/products/useCases/updateProduct/UpdateProductUseCase.spec.ts b/src/modules/products/useCases/updateProduct/UpdateProductUseCase.spec.ts new file mode 100644 index 00000000..ba4e02bf --- /dev/null +++ b/src/modules/products/useCases/updateProduct/UpdateProductUseCase.spec.ts @@ -0,0 +1,89 @@ +import { describe, it, expect, beforeAll, beforeEach, jest } from "@jest/globals" +import { MongoRepository } from "../../../../domain/repositories/Mongo.repository" +import { IProduct } from "../../../../domain/entities/products/Product" +import { UpdateProductUseCase } from "./UpdateProduct.useCase" +import { IUpdateProductDTO } from "../../dtos/IUpdateProductDTO" + +describe('UpdateProductUseCase', () => { + let updateProductUseCase: UpdateProductUseCase + let productsRepository: MongoRepository + + beforeEach(() => { + productsRepository = new MongoRepository() + updateProductUseCase = new UpdateProductUseCase(productsRepository) + }) + + it('should update product', async () => { + jest.spyOn(productsRepository, 'getProduct').mockImplementationOnce(async () => getProduct()) + jest.spyOn(productsRepository, 'update').mockImplementationOnce(async () => newProductUpdate()) + const newInfo = newInfoForUpdate() + const updateProduct = await updateProductUseCase.execute(10231, newInfo) + expect(updateProduct.name).toEqual('Zaad - Mondo') + expect(updateProduct.inventory.warehouses[0].locality).toEqual('ALAGOAS') + expect(updateProduct.inventory.warehouses[1].locality).toEqual('SERGIPE') + expect(updateProduct.inventory.warehouses[0].quantity).toEqual(2) + expect(updateProduct.inventory.warehouses[1].quantity).toEqual(11) + expect(updateProduct.name).toEqual('Zaad - Mondo') + }) + +}) + +function newProductUpdate(): IProduct { + return { + sku: 10231, + name: 'Zaad - Mondo', + inventory: { + warehouses: [ + { + locality: "ALAGOAS", + quantity: 2, + type: "PHYSICAL_STORE" + }, { + locality: "SERGIPE", + quantity: 11, + type: "PHYSICAL_STORE" + } + ] + } + } +} + +function newInfoForUpdate(): IUpdateProductDTO { + return { + name: 'Zaad - Mondo', + inventory: { + warehouses: [ + { + locality: "ALAGOAS", + quantity: 2, + type: "PHYSICAL_STORE" + }, { + locality: "SERGIPE", + quantity: 11, + type: "PHYSICAL_STORE" + } + ] + } + } +} + +function getProduct(): IProduct { + return { + sku: 10231, + name: 'Zaad', + inventory: { + warehouses: [ + { + locality: "FLORIANOPOLIS", + quantity: 15, + type: "PHYSICAL_STORE" + }, { + locality: "PR", + quantity: 51, + type: "PHYSICAL_STORE" + } + ] + }, + isDeleted: false + } +} \ No newline at end of file diff --git a/src/routes/index.ts b/src/routes/index.ts new file mode 100644 index 00000000..dab7c948 --- /dev/null +++ b/src/routes/index.ts @@ -0,0 +1,7 @@ +import { Router } from "express" +import productsRoute from "../routes/products" + +const router = Router() +router.use("/products", productsRoute) + +export default router \ No newline at end of file diff --git a/src/routes/products/index.ts b/src/routes/products/index.ts new file mode 100644 index 00000000..e0564502 --- /dev/null +++ b/src/routes/products/index.ts @@ -0,0 +1,18 @@ +import { Router } from "express" +import { CreateProductController } from "../../modules/products/useCases/createProduct/CreateProductController" +import { GetProductController } from "../../modules/products/useCases/getProduct/GetProductController" +import { UpdateProductController } from "../../modules/products/useCases/updateProduct/UpdateProductController" +import { DeleteProductController } from "../../modules/products/useCases/deleteProduct/DeleteProductController" + +const createProductController = new CreateProductController() +const getProductController = new GetProductController() +const updateProductController = new UpdateProductController() +const deleteProductController = new DeleteProductController() + +const router = Router() +router.post('/', createProductController.handle) +router.get('/:sku', getProductController.handle) +router.put('/:sku', updateProductController.handle) +router.delete('/:sku', deleteProductController.handle) + +export default router \ No newline at end of file diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 00000000..6e63a2eb --- /dev/null +++ b/src/server.ts @@ -0,0 +1,15 @@ +import * as dotenv from 'dotenv' +dotenv.config() +import { app } from "./app" +import mongoose from 'mongoose' + +const PORT = process.env.PORT +const HOST = process.env.HOST_NAME +const password = process.env.PASSWORD_MONGO_ATLAS +const user = process.env.USER_MONGO_ATLAS + +mongoose.connect(`mongodb+srv://${user}:${password}@boticario.9b0wn6v.mongodb.net/?retryWrites=true&w=majority`) + .then(() => console.log("Database Connected")) + .catch((err) => { throw err }) + +app.listen(PORT, () => console.log(`Server is running on ${HOST}:${PORT}`)) \ No newline at end of file diff --git a/src/shared/container/index.ts b/src/shared/container/index.ts new file mode 100644 index 00000000..1a994eb5 --- /dev/null +++ b/src/shared/container/index.ts @@ -0,0 +1,2 @@ +export * from "./repositoriesContainer" +export * from "./servicesContainer" \ No newline at end of file diff --git a/src/shared/container/repositoriesContainer.ts b/src/shared/container/repositoriesContainer.ts new file mode 100644 index 00000000..611e466d --- /dev/null +++ b/src/shared/container/repositoriesContainer.ts @@ -0,0 +1,5 @@ +import { container } from "tsyringe" +import { IProductsRepository } from "../../domain/repositories/IProducts.repository" +import { MongoRepository } from "../../domain/repositories/Mongo.repository" + +container.registerSingleton("ProductsRepository", MongoRepository) \ No newline at end of file diff --git a/src/shared/container/servicesContainer.ts b/src/shared/container/servicesContainer.ts new file mode 100644 index 00000000..e744a78e --- /dev/null +++ b/src/shared/container/servicesContainer.ts @@ -0,0 +1,5 @@ +import { container } from "tsyringe" +import { StockProductService } from "../../modules/products/service/StockProduct.service" +import { IStockProductService } from "../../modules/products/service/IStockProduct.service" + +container.registerSingleton("StockProductService", StockProductService) \ No newline at end of file diff --git a/src/shared/excepetions/errors.ts b/src/shared/excepetions/errors.ts new file mode 100644 index 00000000..1e24b280 --- /dev/null +++ b/src/shared/excepetions/errors.ts @@ -0,0 +1,10 @@ +export class AppError { + public readonly message: string + + public readonly statusCode: number + + constructor(message: string, statusCode = 400) { + this.message = message + this.statusCode = statusCode + } +} \ No newline at end of file diff --git a/src/swagger.json b/src/swagger.json new file mode 100644 index 00000000..33331486 --- /dev/null +++ b/src/swagger.json @@ -0,0 +1,204 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "Boticario NodeJS test", + "description": "Matheus Fujihara", + "contact": { + "email": "fujihara_matheus@hotmail.com" + }, + "version": "1.0.2" + }, + "servers": [ + { + "url": "http://localhost:8080", + "description": "dev" + } + ], + "paths": { + "/products": { + "post": { + "summary": "Create Product", + "description": "Route to create new product", + "tag": [ + "Products" + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + }, + "examples": { + "product": { + "value": { + "sku": 43264, + "name": "L'Oréal Professionnel Expert Absolut Repair Cortex Lipidium - Máscara de Reconstrução 500g", + "inventory": { + "warehouses": [ + { + "locality": "SP", + "quantity": 12, + "type": "ECOMMERCE" + }, + { + "locality": "MOEMA", + "quantity": 3, + "type": "PHYSICAL_STORE" + } + ] + } + } + } + } + } + } + }, + "responses": { + "201": { + "description": "Product created successfully" + }, + "400": { + "description": "Error on createa new product" + } + } + } + }, + "/products/{sku}": { + "get": { + "description": "Get product by sku", + "summary": "Get Product", + "tag": [ + "Products" + ], + "parameters": [ + { + "name": "sku", + "in": "path", + "description": "sku", + "required": true + } + ], + "responses": { + "200": { + "description": "Product found successfully", + "content": { + "application/json": { + "schema": { + "type": "object", + "$ref": "#/components/schemas/Product" + } + } + } + }, + "400": { + "description": "Error on finding product by sku" + } + } + }, + "put": { + "summary": "Update Product", + "description": "Update product by sku", + "tag": [ + "Products" + ], + "parameters": [ + { + "name": "sku", + "in": "path", + "description": "Product SKU", + "required": true + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Product" + }, + "examples": { + "product": { + "value": { + "name": "Kit o Boticário Match SOS Cauterização Pós-Química Triplo", + "inventory": { + "warehouses": [ + { + "locality": "RR", + "quantity": 10, + "type": "ECOMMERCE" + }, + { + "locality": "NATAL", + "quantity": 7, + "type": "PHYSICAL_STORE" + } + ] + } + } + } + } + } + } + }, + "responses": { + "200": { + "description": "Update product successfully" + }, + "400": { + "description": "Error on update product by sku" + } + } + }, + "delete": { + "summary": "Delete Product", + "description": "Soft Delete by sku", + "tag": [ + "Products" + ], + "parameters": [ + { + "name": "sku", + "in": "path", + "description": "Product SKU", + "required": true + } + ], + "responses": { + "200": { + "description": "Product deleted successfully" + }, + "400": { + "description": "Error on delete product" + } + } + } + } + }, + "components": { + "schemas": { + "Product": { + "type": "object", + "properties": { + "sku": { + "type": "number" + }, + "name": { + "type": "string" + }, + "inventory": { + "type": "object", + "properties": { + "warehouses": { + "type": "array", + "items": { + "locality": "string", + "quantity": "number", + "type": "string" + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..bc05a59b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,103 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ + "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + "resolveJsonModule": true, /* Enable importing .json files. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "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. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": false, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +}