diff --git a/modulo5/arquitetura-injecao/template-injecao/.gitignore b/modulo5/arquitetura-injecao/template-injecao/.gitignore new file mode 100644 index 0000000..51ef85c --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/.gitignore @@ -0,0 +1,4 @@ +.env +.DS_STORE +node_modules +build \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/package.json b/modulo5/arquitetura-injecao/template-injecao/package.json new file mode 100644 index 0000000..d4e7d06 --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/package.json @@ -0,0 +1,36 @@ +{ + "name": "projeto-cookenu-backend", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "node ./build/index.js", + "build": "tsc", + "dev": "ts-node-dev ./src/index.ts", + "migrations": "tsc && node ./build/database/migrations/Migrations.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "@types/cors": "^2.8.12", + "@types/express": "^4.17.13", + "@types/jsonwebtoken": "^8.5.8", + "@types/knex": "^0.16.1", + "@types/node": "^18.0.6", + "@types/uuid": "^8.3.4", + "@types/bcryptjs": "^2.4.2", + "ts-node-dev": "^2.0.0", + "typescript": "^4.7.4" + }, + "dependencies": { + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.0.1", + "express": "^4.18.1", + "jsonwebtoken": "^8.5.1", + "knex": "^2.2.0", + "mysql": "^2.18.1", + "uuid": "^8.3.2" + } +} diff --git a/modulo5/arquitetura-injecao/template-injecao/requests.rest b/modulo5/arquitetura-injecao/template-injecao/requests.rest new file mode 100644 index 0000000..257a75e --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/requests.rest @@ -0,0 +1,38 @@ +### Endpoint de teste +GET http://localhost:3003/ping + +### 1) Signup +POST http://localhost:3003/users/signup +Content-Type: application/json + +{ + "name": "Beltrano", + "email": "beltrano2@gmail.com", + "password": "abc123" +} + +### 2) Login +POST http://localhost:3003/users/login +Content-Type: application/json + +{ + "email": "astrodev@gmail.com", + "password": "bananinha" +} + +### 3) Get users +GET http://localhost:3003/users +Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImJiOWI3ZWU4LWFlNGItNGJkMS05YmQ2LWU3ZTIxNTk0Mzk5YiIsInJvbGUiOiJBRE1JTiIsImlhdCI6MTY2MDA1MDUyMCwiZXhwIjoxNjYwMTM2OTIwfQ.aLmP8EirhZQ1nPweO2dwNd43uQLIzSbctXklgL04TOk + +### 3) Delete user +DELETE http://localhost:3003/users/b67c6ee5-c06b-470f-8470-71803e17e509 +Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImJiOWI3ZWU4LWFlNGItNGJkMS05YmQ2LWU3ZTIxNTk0Mzk5YiIsInJvbGUiOiJBRE1JTiIsImlhdCI6MTY2MDA1MDUyMCwiZXhwIjoxNjYwMTM2OTIwfQ.aLmP8EirhZQ1nPweO2dwNd43uQLIzSbctXklgL04TOk + +### 4) Edit user +PUT http://localhost:3003/users/bb9b7ee8-ae4b-4bd1-9bd6-e7e21594399b +Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjIwMzM3NWRkLThiMzItNGU1My04NWViLWViYTkxNDQzZmIzZSIsInJvbGUiOiJOT1JNQUwiLCJpYXQiOjE2NjAwNTI2NDEsImV4cCI6MTY2MDEzOTA0MX0.jOF6vSbGc8AuQAf82UJ1bKC_Xle9YNuKYl3SRFh2Ex0 +Content-Type: application/json + +{ + "name": "Astrodev3" +} \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/business/PingBusiness.ts b/modulo5/arquitetura-injecao/template-injecao/src/business/PingBusiness.ts new file mode 100644 index 0000000..d426790 --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/business/PingBusiness.ts @@ -0,0 +1,9 @@ +export class PingBusiness { + public ping = async () => { + const response = { + message: "Pong!" + } + + return response + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/business/UserBusiness.ts b/modulo5/arquitetura-injecao/template-injecao/src/business/UserBusiness.ts new file mode 100644 index 0000000..ddab4a4 --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/business/UserBusiness.ts @@ -0,0 +1,299 @@ +import { UserDatabase } from "../database/UserDatabase" +import { IDeleteUserInputDTO, IEditUserInputDTO, IGetUsersDBDTO, IGetUsersInputDTO, IGetUsersOutputDTO, IGetUsersUser, ILoginInputDTO, ISignupInputDTO, User, USER_ROLES } from "../models/User" +import { Authenticator, ITokenPayload } from "../services/Authenticator" +import { HashManager } from "../services/HashManager" +import { IdGenerator } from "../services/IdGenerator" + +export class UserBusiness { + public signup = async (input: ISignupInputDTO) => { + const name = input.name + const email = input.email + const password = input.password + + if (!name || !email || !password) { + throw new Error("Um ou mais parâmetros faltando") + } + + if (typeof name !== "string" || name.length < 3) { + throw new Error("Parâmetro 'name' inválido") + } + + if (typeof email !== "string" || email.length < 3) { + throw new Error("Parâmetro 'email' inválido") + } + + if (!email.match(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/)) { + throw new Error("Parâmetro 'email' inválido") + } + + if (typeof password !== "string" || password.length < 3) { + throw new Error("Parâmetro 'password' inválido") + } + + const userDatabase = new UserDatabase() + const userDB = await userDatabase.findByEmail(email) + + if (userDB) { + throw new Error("E-mail já cadastrado") + } + + const idGenerator = new IdGenerator() + const hashManager = new HashManager() + + const id = idGenerator.generate() + const hashedPassword = await hashManager.hash(password) + + const user = new User( + id, + name, + email, + hashedPassword, + USER_ROLES.NORMAL + ) + + await userDatabase.createUser(user) + + const payload: ITokenPayload = { + id: user.getId(), + role: user.getRole() + } + + const authenticator = new Authenticator() + const token = authenticator.generateToken(payload) + + const response = { + message: "Cadastro realizado com sucesso", + token + } + + return response + } + + public login = async (input: ILoginInputDTO) => { + const email = input.email + const password = input.password + + if (!email || !password) { + throw new Error("Um ou mais parâmetros faltando") + } + + if (typeof email !== "string" || email.length < 3) { + throw new Error("Parâmetro 'email' inválido") + } + + if (!email.match(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/)) { + throw new Error("Parâmetro 'email' inválido") + } + + if (typeof password !== "string" || password.length < 3) { + throw new Error("Parâmetro 'password' inválido") + } + + const userDatabase = new UserDatabase() + const userDB = await userDatabase.findByEmail(email) + + if (!userDB) { + throw new Error("E-mail não cadastrado") + } + + const user = new User( + userDB.id, + userDB.name, + userDB.email, + userDB.password, + userDB.role + ) + + const hashManager = new HashManager() + const isPasswordCorrect = await hashManager.compare(password, user.getPassword()) + + if (!isPasswordCorrect) { + throw new Error("Senha incorreta") + } + + const payload: ITokenPayload = { + id: user.getId(), + role: user.getRole() + } + + const authenticator = new Authenticator() + const token = authenticator.generateToken(payload) + + const response = { + message: "Login realizado com sucesso", + token + } + + return response + } + + public getUsers = async (input: IGetUsersInputDTO) => { + const token = input.token + const search = input.search || "" + const order = input.order || "name" + const sort = input.sort || "ASC" + const limit = Number(input.limit) || 10 + const page = Number(input.page) || 1 + + const offset = limit * (page - 1) + + const authenticator = new Authenticator() + const payload = authenticator.getTokenPayload(token) + + if (!payload) { + throw new Error("Token inválido ou faltando") + } + + const getUsersInputDB: IGetUsersDBDTO = { + search, + order, + sort, + limit, + offset + } + + const userDatabase = new UserDatabase() + const usersDB = await userDatabase.getUsers(getUsersInputDB) + + const users = usersDB.map(userDB => { + const user = new User( + userDB.id, + userDB.name, + userDB.email, + userDB.password, + userDB.role + ) + + const userResponse: IGetUsersUser = { + id: user.getId(), + name: user.getName(), + email: user.getEmail() + } + + return userResponse + }) + + const response: IGetUsersOutputDTO = { + users + } + + return response + } + + public deleteUser = async (input: IDeleteUserInputDTO) => { + const token = input.token + const idToDelete = input.idToDelete + + const authenticator = new Authenticator() + const payload = authenticator.getTokenPayload(token) + + if (!payload) { + throw new Error("Token inválido ou faltando") + } + + if (payload.role !== USER_ROLES.ADMIN) { + throw new Error("Apenas admins podem deletar usuários") + } + + if (payload.id === idToDelete) { + throw new Error("Não é possível deletar a própria conta") + } + + const userDatabase = new UserDatabase() + const userDB = await userDatabase.findById(idToDelete) + + if (!userDB) { + throw new Error("Usuário a ser deletado não encontrado") + } + + await userDatabase.deleteUser(idToDelete) + + const response = { + message: "Usuário deletado com sucesso" + } + + return response + } + + public editUser = async (input: IEditUserInputDTO) => { + const { + token, + idToEdit, + email, + name, + password + } = input + + if (!token) { + throw new Error("Token faltando") + } + + if (!email && !name && !password) { + throw new Error("Parâmetros faltando") + } + + const authenticator = new Authenticator() + const payload = authenticator.getTokenPayload(token) + + if (!payload) { + throw new Error("Token inválido") + } + + if (email && typeof email !== "string") { + throw new Error("Parâmetro 'email' inválido") + } + + if (email && !email.match(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/)) { + throw new Error("Parâmetro 'email' inválido") + } + + if (name && typeof name !== "string") { + throw new Error("Parâmetro 'name' inválido") + } + + if (name && name.length < 3) { + throw new Error("Parâmetro 'name' inválido") + } + + if (password && typeof password !== "string") { + throw new Error("Parâmetro 'password' inválido") + } + + if (password && password.length < 6) { + throw new Error("Parâmetro 'password' inválido") + } + + if (payload.role === USER_ROLES.NORMAL) { + if (payload.id !== idToEdit) { + throw new Error("Usuários normais só podem editar a própria conta") + } + } + + const userDatabase = new UserDatabase() + const userDB = await userDatabase.findById(idToEdit) + + if (!userDB) { + throw new Error("Conta a ser editada não existe") + } + + const user = new User( + userDB.id, + userDB.name, + userDB.email, + userDB.password, + userDB.role + ) + + name && user.setName(name) + email && user.setEmail(email) + password && user.setPassword(password) + + await userDatabase.editUser(user) + + const response = { + message: "Edição realizada com sucesso" + } + + return response + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/controller/PingController.ts b/modulo5/arquitetura-injecao/template-injecao/src/controller/PingController.ts new file mode 100644 index 0000000..f997e2d --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/controller/PingController.ts @@ -0,0 +1,16 @@ +import { Request, Response } from "express" +import { PingBusiness } from "../business/PingBusiness" + +export class PingController { + public ping = async (req: Request, res: Response) => { + let errorCode = 400 + try { + const pingBusiness = new PingBusiness() + const response = await pingBusiness.ping() + + res.status(200).send(response) + } catch (error) { + res.status(errorCode).send({ message: error.message }) + } + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/controller/UserController.ts b/modulo5/arquitetura-injecao/template-injecao/src/controller/UserController.ts new file mode 100644 index 0000000..f5d6570 --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/controller/UserController.ts @@ -0,0 +1,93 @@ +import { Request, Response } from "express"; +import { UserBusiness } from "../business/UserBusiness"; +import { IDeleteUserInputDTO, IEditUserInputDTO, IGetUsersInputDTO, IGetUsersOutputDTO, ILoginInputDTO, ISignupInputDTO } from "../models/User"; + +export class UserController { + public signup = async (req: Request, res: Response) => { + try { + const input: ISignupInputDTO = { + name: req.body.name, + email: req.body.email, + password: req.body.password + } + + const userBusiness = new UserBusiness() + const response = await userBusiness.signup(input) + + res.status(201).send(response) + } catch (error) { + res.status(400).send({ message: error.message }) + } + } + + public login = async (req: Request, res: Response) => { + try { + const input: ILoginInputDTO = { + email: req.body.email, + password: req.body.password + } + + const userBusiness = new UserBusiness() + const response = await userBusiness.login(input) + + res.status(200).send(response) + } catch (error) { + res.status(400).send({ message: error.message }) + } + } + + public getUsers = async (req: Request, res: Response) => { + try { + const input: IGetUsersInputDTO = { + token: req.headers.authorization, + search: req.query.search as string, + order: req.query.order as string, + sort: req.query.sort as string, + limit: req.query.limit as string, + page: req.query.page as string + } + + const userBusiness = new UserBusiness() + const response = await userBusiness.getUsers(input) + + res.status(200).send(response) + } catch (error) { + res.status(400).send({ message: error.message }) + } + } + + public deleteUser = async (req: Request, res: Response) => { + try { + const input: IDeleteUserInputDTO = { + token: req.headers.authorization, + idToDelete: req.params.id + } + + const userBusiness = new UserBusiness() + const response = await userBusiness.deleteUser(input) + + res.status(200).send(response) + } catch (error) { + res.status(400).send({ message: error.message }) + } + } + + public editUser = async (req: Request, res: Response) => { + try { + const input: IEditUserInputDTO = { + token: req.headers.authorization, + idToEdit: req.params.id, + name: req.body.name, + email: req.body.email, + password: req.body.password + } + + const userBusiness = new UserBusiness() + const response = await userBusiness.editUser(input) + + res.status(200).send(response) + } catch (error) { + res.status(400).send({ message: error.message }) + } + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/database/BaseDatabase.ts b/modulo5/arquitetura-injecao/template-injecao/src/database/BaseDatabase.ts new file mode 100644 index 0000000..fd05f8c --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/database/BaseDatabase.ts @@ -0,0 +1,18 @@ +import knex from "knex" +import dotenv from "dotenv" + +dotenv.config() + +export abstract class BaseDatabase { + protected static connection = knex({ + client: "mysql", + connection: { + host: process.env.DB_HOST, + port: 3306, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_DATABASE, + multipleStatements: true + }, + }) +} diff --git a/modulo5/arquitetura-injecao/template-injecao/src/database/UserDatabase.ts b/modulo5/arquitetura-injecao/template-injecao/src/database/UserDatabase.ts new file mode 100644 index 0000000..f2c1f13 --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/database/UserDatabase.ts @@ -0,0 +1,78 @@ +import { IGetUsersDBDTO, IUserDB, User } from "../models/User" +import { BaseDatabase } from "./BaseDatabase" + +export class UserDatabase extends BaseDatabase { + public static TABLE_USERS = "Arq_Users" + + public findByEmail = async (email: string) => { + const usersDB: IUserDB[] = await BaseDatabase + .connection(UserDatabase.TABLE_USERS) + .select() + .where({ email }) + + return usersDB[0] + } + + public createUser = async (user: User) => { + const userDB: IUserDB = { + id: user.getId(), + name: user.getName(), + email: user.getEmail(), + password: user.getPassword(), + role: user.getRole() + } + + await BaseDatabase + .connection(UserDatabase.TABLE_USERS) + .insert(userDB) + } + + public getUsers = async (input: IGetUsersDBDTO) => { + const search = input.search + const order = input.order + const sort = input.sort + const limit = input.limit + const offset = input.offset + + const usersDB: IUserDB[] = await BaseDatabase + .connection(UserDatabase.TABLE_USERS) + .select() + .where("name", "LIKE", `%${search}%`) + .orderBy(order, sort) + .limit(limit) + .offset(offset) + + return usersDB + } + + public findById = async (id: string) => { + const usersDB: IUserDB[] = await BaseDatabase + .connection(UserDatabase.TABLE_USERS) + .select() + .where({ id }) + + return usersDB[0] + } + + public deleteUser = async (id: string) => { + await BaseDatabase + .connection(UserDatabase.TABLE_USERS) + .delete() + .where({ id }) + } + + public editUser = async (user: User) => { + const userDB: IUserDB = { + id: user.getId(), + name: user.getName(), + email: user.getEmail(), + password: user.getPassword(), + role: user.getRole() + } + + await BaseDatabase + .connection(UserDatabase.TABLE_USERS) + .update(userDB) + .where({ id: userDB.id }) + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/database/migrations/Migrations.ts b/modulo5/arquitetura-injecao/template-injecao/src/database/migrations/Migrations.ts new file mode 100644 index 0000000..a2c58eb --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/database/migrations/Migrations.ts @@ -0,0 +1,49 @@ +import { BaseDatabase } from "../BaseDatabase" +import { UserDatabase } from "../UserDatabase" +import { users } from "./data" + +class Migrations extends BaseDatabase { + execute = async () => { + try { + console.log("Creating tables...") + await this.createTables() + console.log("Tables created successfully.") + + console.log("Populating tables...") + await this.insertData() + console.log("Tables populated successfully.") + + console.log("Migrations completed.") + } catch (error) { + console.log("Error in migrations...") + console.log(error.message) + } finally { + console.log("Ending connection...") + BaseDatabase.connection.destroy() + console.log("Connection closed graciously.") + } + } + + createTables = async () => { + await BaseDatabase.connection.raw(` + DROP TABLE IF EXISTS ${UserDatabase.TABLE_USERS}; + + CREATE TABLE IF NOT EXISTS ${UserDatabase.TABLE_USERS}( + id VARCHAR(255) PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + role ENUM("NORMAL", "ADMIN") DEFAULT "NORMAL" NOT NULL + ); + `) + } + + insertData = async () => { + await BaseDatabase + .connection(UserDatabase.TABLE_USERS) + .insert(users) + } +} + +const migrations = new Migrations() +migrations.execute() \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/database/migrations/data.ts b/modulo5/arquitetura-injecao/template-injecao/src/database/migrations/data.ts new file mode 100644 index 0000000..6c55aa4 --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/database/migrations/data.ts @@ -0,0 +1,25 @@ +import { IUserDB, USER_ROLES } from "../../models/User" + +export const users: IUserDB[] = [ + { + id: "bb9b7ee8-ae4b-4bd1-9bd6-e7e21594399b", + name: "Astrodev", + email: "astrodev@gmail.com", + password: "$2a$12$RBAWOHpUvGTE.MEeIohAzec9tlVqtNA/x2PMPt/Hrt0vI437cQdJC", // bananinha + role: USER_ROLES.ADMIN + }, + { + id: "f03017bb-2c08-4cdc-bb63-7fbd7cebe01f", + name: "Fulano", + email: "fulano@gmail.com", + password: "$2a$12$PULtVNlAll87D6E8pR/0HO9vbzVDPaUMA89rc5cNmYoAAepbwmkcO", // qwerty00 + role: USER_ROLES.NORMAL + }, + { + id: "7079b8e4-95cd-48aa-82a9-77454e94b789", + name: "Ciclana", + email: "ciclana@gmail.com", + password: "$2a$12$LkWMqS3oPhP2iVMcZOVvWer9ahUPulxjB0EA4TWPxWaRuEEfYGu/i", // asdfg123 + role: USER_ROLES.NORMAL + } +] \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/index.ts b/modulo5/arquitetura-injecao/template-injecao/src/index.ts new file mode 100644 index 0000000..e38198a --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/index.ts @@ -0,0 +1,18 @@ +import express from 'express' +import cors from 'cors' +import dotenv from "dotenv" +import { pingRouter } from './router/pingRouter' +import { userRouter } from './router/userRouter' + +dotenv.config() + +const app = express() +app.use(express.json()) +app.use(cors()) + +app.listen(process.env.PORT || 3003, () => { + console.log(`Servidor rodando na porta ${process.env.PORT || 3003}`) +}) + +app.use("/ping", pingRouter) +app.use("/users", userRouter) \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/models/User.ts b/modulo5/arquitetura-injecao/template-injecao/src/models/User.ts new file mode 100644 index 0000000..c58982c --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/models/User.ts @@ -0,0 +1,113 @@ +export enum USER_ROLES { + NORMAL = "NORMAL", + ADMIN = "ADMIN" +} + +export interface IUserDB { + id: string, + name: string, + email: string, + password: string, + role: USER_ROLES +} + +export class User { + constructor( + private id: string, + private name: string, + private email: string, + private password: string, + private role: USER_ROLES + ) {} + + public getId = () => { + return this.id + } + + public getName = () => { + return this.name + } + + public getEmail = () => { + return this.email + } + + public getPassword = () => { + return this.password + } + + public getRole = () => { + return this.role + } + + public setId = (newId: string) => { + this.id = newId + } + + public setName = (newName: string) => { + this.name = newName + } + + public setEmail = (newEmail: string) => { + this.email = newEmail + } + + public setPassword = (newPassword: string) => { + this.password = newPassword + } + + public setRole = (newRole: USER_ROLES) => { + this.role = newRole + } +} + +export interface ISignupInputDTO { + name: string, + email: string, + password: string +} + +export interface ILoginInputDTO { + email: string, + password: string +} + +export interface IGetUsersInputDTO { + token: string, + search: string, + order: string, + sort: string, + limit: string, + page: string +} + +export interface IGetUsersUser { + id: string, + name: string, + email: string +} + +export interface IGetUsersOutputDTO { + users: IGetUsersUser[] +} + +export interface IDeleteUserInputDTO { + token: string, + idToDelete: string +} + +export interface IGetUsersDBDTO { + search: string, + order: string, + sort: string, + limit: number, + offset: number +} + +export interface IEditUserInputDTO { + token: string, + idToEdit: string, + name: string, + email: string, + password: string +} \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/router/pingRouter.ts b/modulo5/arquitetura-injecao/template-injecao/src/router/pingRouter.ts new file mode 100644 index 0000000..bee2055 --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/router/pingRouter.ts @@ -0,0 +1,8 @@ +import { Router } from 'express' +import { PingController } from '../controller/PingController' + +export const pingRouter = Router() + +const pingController = new PingController() + +pingRouter.get("/", pingController.ping) \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/router/userRouter.ts b/modulo5/arquitetura-injecao/template-injecao/src/router/userRouter.ts new file mode 100644 index 0000000..9df5659 --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/router/userRouter.ts @@ -0,0 +1,12 @@ +import { Router } from 'express' +import { UserController } from '../controller/UserController' + +export const userRouter = Router() + +const userController = new UserController() + +userRouter.post("/signup", userController.signup) +userRouter.post("/login", userController.login) +userRouter.get("/", userController.getUsers) +userRouter.delete("/:id", userController.deleteUser) +userRouter.put("/:id", userController.editUser) \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/services/Authenticator.ts b/modulo5/arquitetura-injecao/template-injecao/src/services/Authenticator.ts new file mode 100644 index 0000000..f9c083c --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/services/Authenticator.ts @@ -0,0 +1,37 @@ +import jwt from 'jsonwebtoken' +import dotenv from "dotenv" +import { USER_ROLES } from '../models/User' + +dotenv.config() + +export interface ITokenPayload { + id: string, + role: USER_ROLES +} + +export class Authenticator { + generateToken = (payload: ITokenPayload): string => { + const token = jwt.sign( + payload, + process.env.JWT_KEY, + { + expiresIn: process.env.JWT_EXPIRES_IN + } + ) + + return token + } + + getTokenPayload = (token: string): ITokenPayload | null => { + try { + const payload = jwt.verify( + token, + process.env.JWT_KEY + ) + + return payload as ITokenPayload + } catch (error) { + return null + } + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/services/HashManager.ts b/modulo5/arquitetura-injecao/template-injecao/src/services/HashManager.ts new file mode 100644 index 0000000..4eb3ac2 --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/services/HashManager.ts @@ -0,0 +1,15 @@ +import bcrypt from 'bcryptjs' + +export class HashManager { + public hash = async (plaintext: string) => { + const rounds = Number(process.env.BCRYPT_SALT_ROUNDS) + const salt = await bcrypt.genSalt(rounds) + const hash = await bcrypt.hash(plaintext, salt) + + return hash + } + + public compare = async (plaintext: string, hash: string) => { + return bcrypt.compare(plaintext, hash) + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/src/services/IdGenerator.ts b/modulo5/arquitetura-injecao/template-injecao/src/services/IdGenerator.ts new file mode 100644 index 0000000..a6ba76e --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/src/services/IdGenerator.ts @@ -0,0 +1,7 @@ +import { v4 } from 'uuid' + +export class IdGenerator { + public generate = (): string => { + return v4() + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-injecao/template-injecao/tsconfig.json b/modulo5/arquitetura-injecao/template-injecao/tsconfig.json new file mode 100644 index 0000000..d528f4a --- /dev/null +++ b/modulo5/arquitetura-injecao/template-injecao/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2021", + "module": "commonjs", + "sourceMap": true, + "outDir": "./build", + "rootDir": "./src", + "removeComments": true, + "noImplicitAny": true, + "esModuleInterop": true + } +} \ No newline at end of file