diff --git a/cases/case3/Amaro/.gitignore b/cases/case3/Amaro/.gitignore new file mode 100644 index 0000000..8ece3ba --- /dev/null +++ b/cases/case3/Amaro/.gitignore @@ -0,0 +1,4 @@ +node_modules +package-lock.json +build +.env \ No newline at end of file diff --git a/cases/case3/Amaro/package.json b/cases/case3/Amaro/package.json new file mode 100644 index 0000000..659a3e1 --- /dev/null +++ b/cases/case3/Amaro/package.json @@ -0,0 +1,40 @@ +{ + "name": "Amaro", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start:dev": "ts-node-dev ./src/index.ts", + "start": "node ./build/index.js", + "build": "tsc" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/cors": "^2.8.12", + "@types/express": "^4.17.13", + "@types/knex": "^0.16.1", + "@types/node": "^18.7.1", + "knex": "^2.2.0", + "mysql": "^2.18.1", + "ts-node-dev": "^2.0.0", + "typescript": "^4.7.4", + "@types/bcryptjs": "^2.4.2", + "@types/jest": "^25.2.3", + "@types/jsonwebtoken": "^8.5.0", + "@types/uuid": "^8.3.4" + }, + "dependencies": { + "cors": "^2.8.5", + "dotenv": "^16.0.1", + "express": "^4.18.1", + "bcryptjs": "^2.4.3", + "jest": "^26.0.1", + "jsonwebtoken": "^8.5.1", + "nodemon": "^2.0.19", + "process": "^0.11.10", + "ts-jest": "^26.1.0", + "uuid": "^8.3.2" + } +} diff --git a/cases/case3/Amaro/src/business/CategoriasBusiness.ts b/cases/case3/Amaro/src/business/CategoriasBusiness.ts new file mode 100644 index 0000000..a519246 --- /dev/null +++ b/cases/case3/Amaro/src/business/CategoriasBusiness.ts @@ -0,0 +1,38 @@ +import { IdGenerator } from "../services/IdGenerator"; +import { CategoriaDatabase } from "../data/CategoriasDatabase"; +import { categoria, categoriaInputDTO } from "../model/categoria"; + +const idGenerator = new IdGenerator(); + +export class CategoriaBusiness { + public adicionarCategoria = async (input: categoriaInputDTO) => { + try { + const { TAG, produto_id } = input; + + const id: string = idGenerator.generate(); + + const categoriaDatabase = new CategoriaDatabase(); + const categoria: categoria = { + id, + TAG, + produto_id, + }; + + await categoriaDatabase.adicionarCategoria({ + id, + TAG, + produto_id, + }); + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + }; + + public TodasCategorias = async (categoria: categoria, config: any) => { + try { + return await new CategoriaDatabase().TodasCategorias(categoria, config); + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + }; +} diff --git a/cases/case3/Amaro/src/business/ProdutosBusiness.ts b/cases/case3/Amaro/src/business/ProdutosBusiness.ts new file mode 100644 index 0000000..5f59b78 --- /dev/null +++ b/cases/case3/Amaro/src/business/ProdutosBusiness.ts @@ -0,0 +1,72 @@ +import { IdGenerator } from "../services/IdGenerator"; +import { ProdutosDatabase } from "../data/ProdutosDatabase"; +import { produto, produtoInputDTO } from "../model/produto"; + +const idGenerator = new IdGenerator(); + +export class ProdutosBusiness { + public adicionarProduto = async (input: produtoInputDTO) => { + try { + const { nome, marca, preco_compra, icms, lucro } = input; + + const calculoPrecoCustoEVenda = ( + preco_compra: any, + icms: any, + lucro: any + ) => { + let preco_custo: number = (icms / 100) * preco_compra + preco_compra; + + let precoVenda: number = (lucro / 100) * preco_custo + preco_custo; + + return precoVenda; + }; + + const preco_venda: number = calculoPrecoCustoEVenda( + input.preco_compra, + input.icms, + input.lucro + ); + + const id: string = idGenerator.generate(); + + const produtosDatabase = new ProdutosDatabase(); + const produto: produto = { + id, + nome, + marca, + preco_compra, + icms, + lucro, + preco_venda, + }; + + await produtosDatabase.adicionarProduto({ + id, + nome, + marca, + preco_compra, + icms, + lucro, + preco_venda, + }); + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + }; + + public ListarTodosProdutos = async (produto: produto) => { + try { + return await new ProdutosDatabase().ListarTodosProdutos(produto, produto); + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + }; + + public PesquisaProdutos = async (produto: produto) => { + try { + return await new ProdutosDatabase().PesquisaProdutos(produto, produto); + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + }; +} diff --git a/cases/case3/Amaro/src/business/UserBusiness.ts b/cases/case3/Amaro/src/business/UserBusiness.ts new file mode 100644 index 0000000..a648ca7 --- /dev/null +++ b/cases/case3/Amaro/src/business/UserBusiness.ts @@ -0,0 +1,53 @@ +import { UserInputDTO, LoginInputDTO } from "../model/user"; +import { UserDatabase } from "../data/UserDatabase"; +import { IdGenerator } from "../services/IdGenerator"; +import { HashManager } from "../services/HashManager"; +import { Authenticator } from "../services/Authenticator"; +import { UserNotFoud } from "../error/customError"; + +export class UserBusiness { + async createUser(user: UserInputDTO) { + const idGenerator = new IdGenerator(); + const id = idGenerator.generate(); + + const hashManager = new HashManager(); + const hashPassword = await hashManager.hash(user.password); + + const userDatabase = new UserDatabase(); + await userDatabase.createUser( + id, + user.email, + user.name, + hashPassword, + user.role + ); + + const authenticator = new Authenticator(); + const accessToken = authenticator.generateToken({ id, role: user.role }); + + return accessToken; + } + + async getUserByEmail(user: LoginInputDTO) { + const userDatabase = new UserDatabase(); + const userFromDB = await userDatabase.getUserByEmail(user.email); + + const hashManager = new HashManager(); + const hashCompare = await hashManager.compare( + user.password, + userFromDB.getPassword() + ); + + const authenticator = new Authenticator(); + const accessToken = authenticator.generateToken({ + id: userFromDB.getId(), + role: userFromDB.getRole(), + }); + + if (!hashCompare) { + throw new UserNotFoud(); + } + + return accessToken; + } +} diff --git a/cases/case3/Amaro/src/controller/CategoriasController.ts b/cases/case3/Amaro/src/controller/CategoriasController.ts new file mode 100644 index 0000000..564060e --- /dev/null +++ b/cases/case3/Amaro/src/controller/CategoriasController.ts @@ -0,0 +1,87 @@ +import { Request, Response } from "express"; +import { CategoriaBusiness } from "../business/CategoriasBusiness"; +import { CategoriaDatabase } from "../data/CategoriasDatabase"; +import { categoria, categoriaInputDTO } from "../model/categoria"; +import { InvalidData, InvalidTAG, InvalidID } from "../error/customError"; + +export class CategoriaController { + public adicionarCategoria = async ( + req: Request, + res: Response + ): Promise => { + try { + if (!req.body.TAG || !req.body.produto_id) { + throw new InvalidData(); + } + + if (typeof req.body.TAG !== "string") { + throw new InvalidTAG(); + } + if (typeof req.body.produto_id !== "string") { + throw new InvalidID(); + } + + const { TAG, produto_id } = req.body; + + const input: categoriaInputDTO = { TAG, produto_id }; + + const categoriaBusiness = new CategoriaBusiness(); + categoriaBusiness.adicionarCategoria(input); + + res.status(201).send({ message: "Categoria adicionada com sucesso!" }); + } catch (error: any) { + res.status(400).send(error.sqlMessage || error.message); + } + }; + + public TodasCategorias = async (req: Request, res: Response) => { + try { + const input: any = { + id: req.params.id, + search: req.query.search, + sort: req.query.sort as string, + order: req.query.order as string, + page: Number(req.query.page), + size: Number(req.query.size), + }; + + let offset = input.size * (input.page - 1); + input.offset = offset; + + if (!input.resultado) { + input.resultado = "%"; + } + + if (input.sort !== "tag") { + input.sort = "tag"; + } + + if ( + !input.order || + (input.order.toUpperCase() !== "ASC" && + input.order.toUpperCase() !== "DESC") + ) { + input.order = "ASC"; + } + + if (isNaN(input.page) || input.page < 1) { + input.page = 1; + } + + if (isNaN(input.size) || input.size < 1) { + input.size = 10; + } + + const result = await new CategoriaDatabase().TodasCategorias( + input, + input + ); + + res.send(result).status(200); + } catch (error: any) { + res + .send({ message: error.message }) + .status(error.sqlMessage || error.message); + } + }; +} diff --git a/cases/case3/Amaro/src/controller/ProdutosController.ts b/cases/case3/Amaro/src/controller/ProdutosController.ts new file mode 100644 index 0000000..2495036 --- /dev/null +++ b/cases/case3/Amaro/src/controller/ProdutosController.ts @@ -0,0 +1,131 @@ +import { Request, Response } from "express"; +import { ProdutosBusiness } from "../business/ProdutosBusiness"; +import { ProdutosDatabase } from "../data/ProdutosDatabase"; +import { produto, produtoInputDTO } from "../model/produto"; +import { InvalidData, Invalidnumber, InvalidText } from "../error/customError"; + +export class ProdutosController { + public adicionarProduto = async ( + req: Request, + res: Response + ): Promise => { + try { + if ( + !req.body.nome || + !req.body.marca || + !req.body.preco_compra || + !req.body.icms || + !req.body.lucro + ) { + throw new InvalidData(); + } + + if (typeof req.body.nome !== "string") { + throw new InvalidText(); + } + if (typeof req.body.marca !== "string") { + throw new InvalidText(); + } + if (typeof req.body.preco_compra !== "number") { + throw new Invalidnumber(); + } + if (typeof req.body.icms !== "number") { + throw new Invalidnumber(); + } + if (typeof req.body.lucro !== "number") { + throw new Invalidnumber(); + } + + const { nome, marca, preco_compra, icms, lucro, preco_venda } = req.body; + + const input: produtoInputDTO = { + nome, + marca, + preco_compra, + icms, + lucro, + preco_venda, + }; + + const produtoBusiness = new ProdutosBusiness(); + produtoBusiness.adicionarProduto(input); + + res.status(201).send({ + message: + "Produto adicionado com sucesso!", + }); + } catch (error: any) { + res.status(400).send(error.sqlMessage || error.message); + } + }; + public ListarTodosProdutos = async (req: Request, res: Response) => { + try { + const input: any = { + id: req.params.id, + }; + + const produtos = await new ProdutosDatabase().ListarTodosProdutos( + input, + input + ); + + res.send(produtos).status(200); + } catch (error: any) { + res + .send({ message: error.message }) + .status(error.sqlMessage || error.message); + } + }; + + public PesquisaProdutos = async (req: Request, res: Response) => { + try { + const input: any = { + id: req.params.id, + search: req.query.search, + g: req.query.g, + sort: req.query.sort as string, + order: req.query.order as string, + page: Number(req.query.page), + size: Number(req.query.size), + }; + + let offset = input.size * (input.page - 1); + input.offset = offset; + + if (!input.resultado) { + input.resultado = "%"; + } + + if (input.sort !== "nome") { + input.sort = "nome"; + } + + if ( + !input.order || + (input.order.toUpperCase() !== "ASC" && + input.order.toUpperCase() !== "DESC") + ) { + input.order = "ASC"; + } + + if (isNaN(input.page) || input.page < 1) { + input.page = 1; + } + + if (isNaN(input.size) || input.size < 1) { + input.size = 10; + } + + const produtos = await new ProdutosDatabase().PesquisaProdutos( + input, + input + ); + + res.send(produtos).status(200); + } catch (error: any) { + res + .send({ message: error.message }) + .status(error.sqlMessage || error.message); + } + }; +} diff --git a/cases/case3/Amaro/src/controller/UserController.ts b/cases/case3/Amaro/src/controller/UserController.ts new file mode 100644 index 0000000..0eaf6e2 --- /dev/null +++ b/cases/case3/Amaro/src/controller/UserController.ts @@ -0,0 +1,68 @@ +import { Request, Response } from "express"; +import { UserInputDTO, LoginInputDTO } from "../model/user"; +import { UserBusiness } from "../business/UserBusiness"; +import { BaseDatabase } from "../data/BaseDatabase"; +import { InvalidEmail, InvalidPassword } from "../error/customError"; + +export class UserController { + async signup(req: Request, res: Response) { + try { + if (!req.body.email.includes("@")) { + throw new InvalidEmail(); + } + + if (!req.body.email || req.body.email.indexOf("@") === -1) { + throw new InvalidEmail(); + } + + if (!req.body.password || req.body.password.length < 6) { + throw new InvalidPassword(); + } + const input: UserInputDTO = { + email: req.body.email, + name: req.body.name, + password: req.body.password, + role: req.body.role, + }; + + const userBusiness = new UserBusiness(); + const token = await userBusiness.createUser(input); + + res.status(200).send({ token: token }); + res.status(200).send({ message: "Usuario Cadastrado com Sucesso!" }); + } catch (error: any) { + res.status(400).send({ error: error.message }); + } + + await BaseDatabase.destroyConnection(); + } + + async login(req: Request, res: Response) { + try { + if (!req.body.email.includes("@")) { + throw new InvalidEmail(); + } + + if (!req.body.email || req.body.email.indexOf("@") === -1) { + throw new InvalidEmail(); + } + + if (!req.body.password || req.body.password.length < 6) { + throw new InvalidPassword(); + } + const loginData: LoginInputDTO = { + email: req.body.email, + password: req.body.password, + }; + + const userBusiness = new UserBusiness(); + const token = await userBusiness.getUserByEmail(loginData); + + res.status(200).send({ token }); + } catch (error: any) { + res.status(400).send({ error: error.message }); + } + + await BaseDatabase.destroyConnection(); + } +} diff --git a/cases/case3/Amaro/src/data/BaseDatabase.ts b/cases/case3/Amaro/src/data/BaseDatabase.ts new file mode 100644 index 0000000..39cdd84 --- /dev/null +++ b/cases/case3/Amaro/src/data/BaseDatabase.ts @@ -0,0 +1,32 @@ +import knex from "knex"; +import dotenv from "dotenv"; + +dotenv.config(); +export abstract class BaseDatabase { + private static connection: any | null = null; + + protected getConnection(): any { + if (!BaseDatabase.connection) { + BaseDatabase.connection = knex({ + client: "mysql", + connection: { + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_SCHEMA, + port: 3306 || process.env.PORT, + multipleStatements: true, + }, + }); + } + + return BaseDatabase.connection; + } + + public static async destroyConnection(): Promise { + if (BaseDatabase.connection) { + await BaseDatabase.connection.destroy(); + BaseDatabase.connection = null; + } + } +} diff --git a/cases/case3/Amaro/src/data/CategoriasDatabase.ts b/cases/case3/Amaro/src/data/CategoriasDatabase.ts new file mode 100644 index 0000000..44e8841 --- /dev/null +++ b/cases/case3/Amaro/src/data/CategoriasDatabase.ts @@ -0,0 +1,33 @@ +import { categoria } from "../model/categoria"; +import { BaseDatabase } from "./BaseDatabase"; + +export class CategoriaDatabase extends BaseDatabase { + private static CATEGORIAS = "CATEGORIAS"; + public adicionarCategoria = async (categoria: categoria) => { + try { + await this.getConnection() + .insert({ + id: categoria.id, + TAG: categoria.TAG, + produto_id: categoria.produto_id, + }) + .into(CategoriaDatabase.CATEGORIAS); + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + }; + + public TodasCategorias = async (config: any, categoria: categoria) => { + try { + const result = await this.getConnection() + .select("TAG", "produto_id") + .orderBy(config.sort, config.order) + .limit(config.size) + .offset(config.offset) + .from(CategoriaDatabase.CATEGORIAS); + return { Categotias: result }; + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + }; +} diff --git a/cases/case3/Amaro/src/data/ProdutosDatabase.ts b/cases/case3/Amaro/src/data/ProdutosDatabase.ts new file mode 100644 index 0000000..f26aa15 --- /dev/null +++ b/cases/case3/Amaro/src/data/ProdutosDatabase.ts @@ -0,0 +1,56 @@ +import { produto } from "../model/produto"; +import { BaseDatabase } from "./BaseDatabase"; + +export class ProdutosDatabase extends BaseDatabase { + private static PRODUTOS = "PRODUTOS"; + public adicionarProduto = async (produto: produto) => { + try { + await this.getConnection() + .insert({ + id: produto.id, + nome: produto.nome, + marca: produto.marca, + preco_compra: produto.preco_compra, + preco_venda: produto.preco_venda, + }) + .into(ProdutosDatabase.PRODUTOS); + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + }; + public ListarTodosProdutos = async (config: any, produto: produto) => { + try { + const result = await this.getConnection().raw(` select + p.NOME as PRODUTO, + GROUP_CONCAT(DISTINCT c.TAG) as CATEGORIAS + from PRODUTOS p + LEFT JOIN CATEGORIAS c + ON p.id = c.produto_id + group by p.NOME`); + + return { Produtos: result }; + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + }; + + public PesquisaProdutos = async (config: any, produto: produto) => { + try { + const result = await this.getConnection() + .select( + "PRODUTO.nome as Produto", + "GROUP_CONCAT(`CATEGORIAS.TAG`, ',') as CATEGORIAS" + ) + .leftJoin("CATEGORIAS", "PRODUTO.id", "CATEGORIAS.produto_id") + .groupBy("PRODUTO.nome") + .from(ProdutosDatabase.PRODUTOS); + const newResult = result.map((itens: { TAG: any; }) => { + console.log(itens.TAG); + }); + + return { Produtos: result }; + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + }; +} diff --git a/cases/case3/Amaro/src/data/UserDatabase.ts b/cases/case3/Amaro/src/data/UserDatabase.ts new file mode 100644 index 0000000..1657da7 --- /dev/null +++ b/cases/case3/Amaro/src/data/UserDatabase.ts @@ -0,0 +1,37 @@ +import { BaseDatabase } from "./BaseDatabase"; +import { User } from "../model/user"; + +export class UserDatabase extends BaseDatabase { + private static Usuarios = "USUARIOS"; + + public async createUser( + id: string, + email: string, + name: string, + password: string, + role: string + ): Promise { + try { + await this.getConnection() + .insert({ + id: id, + email: email, + name: name, + password: password, + role: role, + }) + .into(UserDatabase.Usuarios); + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + } + + public async getUserByEmail(email: string): Promise { + const result = await this.getConnection() + .select("*") + .from(UserDatabase.Usuarios) + .where({ email }); + + return User.toUserModel(result[0]); + } +} diff --git a/cases/case3/Amaro/src/error/customError.ts b/cases/case3/Amaro/src/error/customError.ts new file mode 100644 index 0000000..663b762 --- /dev/null +++ b/cases/case3/Amaro/src/error/customError.ts @@ -0,0 +1,56 @@ +export abstract class BaseError extends Error { + constructor(message: string, public code: number) { + super(message); + } +} + +export class CustomError extends Error { + constructor(statusCode: number, message: string) { + super(message); + } +} +export class InvalidEmail extends CustomError { + constructor() { + super(400, "Email em formato inválido"); + } +} +export class InvalidPassword extends CustomError { + constructor() { + super(400, "Senha inválida"); + } +} +export class UserNotFoud extends CustomError { + constructor() { + super(400, "Email ou senha inválidos"); + } +} +export class InvalidName extends CustomError { + constructor() { + super(400, "Nome inválido"); + } +} +export class Invalidnumber extends CustomError { + constructor() { + super(400, "Informe um valor numérico"); + } +} +export class InvalidText extends CustomError { + constructor() { + super(400, "O campo de conter apenas texto"); + } +} +export class InvalidID extends CustomError { + constructor() { + super(400, "Informe um ID válido"); + } +} +export class InvalidTAG extends CustomError { + constructor() { + super(400, "Insira uma categoria válida"); + } +} +export class InvalidData extends CustomError { + constructor() { + super(400, "Preencha os campos corretamente"); + } +} diff --git a/cases/case3/Amaro/src/index.ts b/cases/case3/Amaro/src/index.ts new file mode 100644 index 0000000..bf38ab0 --- /dev/null +++ b/cases/case3/Amaro/src/index.ts @@ -0,0 +1,32 @@ +import dotenv from "dotenv"; +import { AddressInfo } from "net"; +import express from "express"; +import { userRouter } from "./routes/userRouter"; +import { produtoRouter } from "./routes/ProdutoRouter"; +import { categoriaRouter } from "./routes/CategoriasRouter"; +dotenv.config(); +const app = express(); +app.use(express.json()); + +app.post("/signup", userRouter); +app.post("/login", userRouter); + +app.post("/tag", categoriaRouter); +app.get("/tag", categoriaRouter); +app.put("/tag/:id", categoriaRouter); +app.delete("/tag/:id", categoriaRouter); + +app.post("/produto", produtoRouter); +app.get("/produto", produtoRouter); +app.get("/produto/pesquisa", produtoRouter); +app.put("/produto/:id", produtoRouter); +app.delete("/produto/:id", produtoRouter); + +const server = app.listen(process.env.PORT || 3000, () => { + if (server) { + const address = server.address() as AddressInfo; + console.log(`Servidor rodando em http://localhost:${address.port}`); + } else { + console.error(`Falha ao rodar o servidor.`); + } +}); diff --git a/cases/case3/Amaro/src/model/categoria.ts b/cases/case3/Amaro/src/model/categoria.ts new file mode 100644 index 0000000..4be6fdb --- /dev/null +++ b/cases/case3/Amaro/src/model/categoria.ts @@ -0,0 +1,10 @@ +export type categoria = { + id: string; + TAG: string; + produto_id: string; +}; + +export interface categoriaInputDTO { + TAG: string; + produto_id: string; +} diff --git a/cases/case3/Amaro/src/model/produto.ts b/cases/case3/Amaro/src/model/produto.ts new file mode 100644 index 0000000..d95120d --- /dev/null +++ b/cases/case3/Amaro/src/model/produto.ts @@ -0,0 +1,17 @@ +export type produto = { + id: string; + nome: string; + marca: string; + preco_compra: number; + icms: number; + lucro: number; + preco_venda: number; +}; +export interface produtoInputDTO { + nome: string; + marca: string; + preco_compra: number; + icms: number; + lucro: number; + preco_venda: number; +} diff --git a/cases/case3/Amaro/src/model/user.ts b/cases/case3/Amaro/src/model/user.ts new file mode 100644 index 0000000..5737fc5 --- /dev/null +++ b/cases/case3/Amaro/src/model/user.ts @@ -0,0 +1,87 @@ +export class User { + constructor( + private id: string, + private name: string, + private email: string, + private password: string, + private role: UserRole + ) {} + + getId() { + return this.id; + } + + getName() { + return this.name; + } + + getEmail() { + return this.email; + } + + getPassword() { + return this.password; + } + + getRole() { + return this.role; + } + + setId(id: string) { + this.id = id; + } + + setName(name: string) { + this.name = name; + } + + setEmail(email: string) { + this.email = email; + } + + setPassword(password: string) { + this.password = password; + } + + setRole(role: UserRole) { + this.role = role; + } + + static stringToUserRole(input: string): UserRole { + switch (input) { + case "NORMAL": + return UserRole.NORMAL; + case "ADMIN": + return UserRole.ADMIN; + default: + throw new Error("Invalid user role"); + } + } + + static toUserModel(user: any): User { + return new User( + user.id, + user.name, + user.email, + user.password, + User.stringToUserRole(user.role) + ); + } +} + +export interface UserInputDTO { + email: string; + password: string; + name: string; + role: string; +} + +export interface LoginInputDTO { + email: string; + password: string; +} + +export enum UserRole { + NORMAL = "NORMAL", + ADMIN = "ADMIN", +} diff --git a/cases/case3/Amaro/src/routes/CategoriasRouter.ts b/cases/case3/Amaro/src/routes/CategoriasRouter.ts new file mode 100644 index 0000000..98a0be1 --- /dev/null +++ b/cases/case3/Amaro/src/routes/CategoriasRouter.ts @@ -0,0 +1,8 @@ +import express from "express"; +import { CategoriaController } from "../controller/CategoriasController"; + +export const categoriaRouter = express.Router(); +const categoriaController = new CategoriaController(); + +categoriaRouter.post("/tag", categoriaController.adicionarCategoria); +categoriaRouter.get("/tag", categoriaController.TodasCategorias); diff --git a/cases/case3/Amaro/src/routes/ProdutoRouter.ts b/cases/case3/Amaro/src/routes/ProdutoRouter.ts new file mode 100644 index 0000000..0e6c6c7 --- /dev/null +++ b/cases/case3/Amaro/src/routes/ProdutoRouter.ts @@ -0,0 +1,9 @@ +import express from "express"; +import { ProdutosController } from "../controller/ProdutosController"; + +export const produtoRouter = express.Router(); +const produtoController = new ProdutosController(); + +produtoRouter.post("/produto", produtoController.adicionarProduto); +produtoRouter.get("/produto/pesquisa", produtoController.PesquisaProdutos); +produtoRouter.get("/produto", produtoController.ListarTodosProdutos); diff --git a/cases/case3/Amaro/src/routes/userRouter.ts b/cases/case3/Amaro/src/routes/userRouter.ts new file mode 100644 index 0000000..11aa62f --- /dev/null +++ b/cases/case3/Amaro/src/routes/userRouter.ts @@ -0,0 +1,8 @@ +import express from "express"; +import { UserController } from "../controller/UserController"; + +export const userRouter = express.Router(); +const userController = new UserController(); + +userRouter.post("/signup", userController.signup); +userRouter.post("/login", userController.login); diff --git a/cases/case3/Amaro/src/services/Authenticator.ts b/cases/case3/Amaro/src/services/Authenticator.ts new file mode 100644 index 0000000..4970ef1 --- /dev/null +++ b/cases/case3/Amaro/src/services/Authenticator.ts @@ -0,0 +1,34 @@ +import * as jwt from "jsonwebtoken"; + +export class Authenticator { + public generateToken( + input: AuthenticationData, + expiresIn: string = process.env.ACCESS_TOKEN_EXPIRES_IN! + ): string { + const token = jwt.sign( + { + id: input.id, + role: input.role, + }, + process.env.JWT_KEY as string, + { + expiresIn: 60, + } + ); + return token; + } + + public getData(token: string): AuthenticationData { + const payload = jwt.verify(token, process.env.JWT_KEY as string) as any; + const result = { + id: payload.id, + role: payload.role, + }; + return result; + } +} + +interface AuthenticationData { + id: string; + role?: string; +} diff --git a/cases/case3/Amaro/src/services/HashManager.ts b/cases/case3/Amaro/src/services/HashManager.ts new file mode 100644 index 0000000..e9743d2 --- /dev/null +++ b/cases/case3/Amaro/src/services/HashManager.ts @@ -0,0 +1,14 @@ +import * as bcrypt from "bcryptjs"; + +export class HashManager { + public async hash(text: string): Promise { + const rounds = 12; + const salt = await bcrypt.genSalt(rounds); + const result = await bcrypt.hash(text, salt); + return result; + } + + public async compare(text: string, hash: string): Promise { + return await bcrypt.compare(text, hash); + } +} diff --git a/cases/case3/Amaro/src/services/IdGenerator.ts b/cases/case3/Amaro/src/services/IdGenerator.ts new file mode 100644 index 0000000..9f54b4e --- /dev/null +++ b/cases/case3/Amaro/src/services/IdGenerator.ts @@ -0,0 +1,7 @@ +import { v4 } from "uuid"; + +export class IdGenerator { + generate(): string { + return v4(); + } +} diff --git a/cases/case3/Amaro/tsconfig.json b/cases/case3/Amaro/tsconfig.json new file mode 100644 index 0000000..05ec970 --- /dev/null +++ b/cases/case3/Amaro/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": "./src", /* 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": "./build", /* 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": true, /* 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. */ + } +}