diff --git a/modulo5/arquitetura-camadas/.gitignore b/modulo5/arquitetura-camadas/.gitignore new file mode 100644 index 0000000..856f35a --- /dev/null +++ b/modulo5/arquitetura-camadas/.gitignore @@ -0,0 +1,3 @@ +/node_modules +package-lock.json +.env \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/package.json b/modulo5/arquitetura-camadas/package.json new file mode 100644 index 0000000..c8b77de --- /dev/null +++ b/modulo5/arquitetura-camadas/package.json @@ -0,0 +1,35 @@ +{ + "name": "arquitetura-camadas", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "tsc && node --inspect ./build/index.js", + "dev": "ts-node-dev --transpile-only --ignore-watch node_modules ./src/index.ts", + "test": "ts-node-dev ./src/services/authenticator.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^8.6.0", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", + "knex": "^0.21.5", + "mysql": "^2.18.1", + "ts-node-dev": "^2.0.0", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.2", + "@types/cors": "^2.8.8", + "@types/express": "^4.17.8", + "@types/jsonwebtoken": "^8.5.9", + "@types/knex": "^0.16.1", + "@types/node": "^14.11.2", + "@types/uuid": "^8.3.4", + "typescript": "^4.0.3" + } + } \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/request.rest b/modulo5/arquitetura-camadas/request.rest new file mode 100644 index 0000000..db8e460 --- /dev/null +++ b/modulo5/arquitetura-camadas/request.rest @@ -0,0 +1,15 @@ +POST http://localhost:3003/user/signup Content-Type: application/json + +{ +"name": "Lucas", "email": "lucas@gmailSDASDASDASDASDSADASDASD.com", "password": "abc1254" +} + +### POST http://localhost:3003/user/login Content-Type: application/json + +{ +"email": "lucas@gmailSDASDASDASDASDSADASDASD.com", "password": "abc1254" +} + +### GET http://localhost:3003/user Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQ2YmRhOGUwLTAxZmEtNDUyZS1hYjg2LTFmYjA4OTU2OWFlNiIsInJvbGUiOiJOT1JNQUwiLCJpYXQiOjE2NjM2MDkwMzYsImV4cCI6MTY2MzY1MjIzNn0.TZpUcqk1JvZf90oGEJi8vgvmSavpU532ybfPEVqCDbw + +### DELETE http://localhost:3003/user/d6bda8e0-01fa-452e-ab86-1fb089569ae6 Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImI3NzJmZGY0LWM5ZmItNDJjMi1hNGJmLTVhZThiODViNTE5YiIsInJvbGUiOiJBRE1JTiIsImlhdCI6MTY2MzYxMDg0MCwiZXhwIjoxNjYzNjU0MDQwfQ.UnW6aalc52xq54ToXwT1uV-S1QIxAhaF84dfDda5xIs \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/Controller/UserController.ts b/modulo5/arquitetura-camadas/src/Controller/UserController.ts new file mode 100644 index 0000000..ee24e9b --- /dev/null +++ b/modulo5/arquitetura-camadas/src/Controller/UserController.ts @@ -0,0 +1,118 @@ +import { Request, Response } from "express"; +import UserBusiness from "../business/UserBusiness"; +import { InvalidError } from "../error/invalidError"; +import { MissingFields } from "../error/MissingFields"; +import { CreateNewUser, User } from "../model/User"; + + +class UserController { + + public signup = async (req: Request, res: Response) => { + + try { + const {name, email, password} = req.body + + if(!name || !email || !password) { + throw new MissingFields() + } + + const newUser: CreateNewUser = { + name: name, + email: email, + password: password + } + + const userBusiness = new UserBusiness() + const user = await userBusiness.signup(newUser) + + res.status(201).send(user) + + } catch (error) { + if(error instanceof Error) { + return res.status(400).send({message: error.message}) + } + + res.status(500).send({message: "Erro inesperado"}) + } + + } + + public login = async (req: Request, res: Response): Promise => { + try { + const {email, password} = req.body + + if(!email || !password) { + throw new MissingFields() + } + + const userBusiness = new UserBusiness(); + + const loginUser = await userBusiness.login(email, password) + + res.status(200).send(loginUser) + + } catch (error: any) { + res.status(error.statusCode || 500) + .send({ message: error.message || "Erro inesperado" }) + + } + } + + public getAllUsers = async (req: Request, res: Response) => { + try { + + const token = req.headers.authorization as string + + if(!token) { + throw new InvalidError("Token não foi enviado") + } + + const userBusiness = new UserBusiness() + + const allUsers = await userBusiness.selectAllUsers(token) + + res.status(200).send(allUsers) + + } catch (error) { + if(error instanceof Error) { + return res.status(400).send({message: error.message}) + } + + res.status(500).send({message: "Erro inesperado"}) + + } + } + + public deleteUser = async (req: Request, res: Response): Promise => { + try { + const token = req.headers.authorization as string + const id = req.params.id + + if(!token) { + throw new InvalidError("Token não foi enviado") + } + + if(!id) { + throw new InvalidError("Seu Id não foi informado") + } + + const userBusiness = new UserBusiness() + + const deleteUser = await userBusiness.deleteUser(token, id) + + res.status(200).send(deleteUser) + + } catch (error: any) { + res.status(error.statusCode || 500) + .send({ message: error.message || "Erro inesperado" }) + } + + + } +} + + + + + +export default UserController \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/app.ts b/modulo5/arquitetura-camadas/src/app.ts new file mode 100644 index 0000000..dc8bf78 --- /dev/null +++ b/modulo5/arquitetura-camadas/src/app.ts @@ -0,0 +1,19 @@ +import express from "express"; +import cors from "cors"; +import { AddressInfo } from "net"; + +const app = express(); + +app.use(express.json()); +app.use(cors()); + +const server = app.listen(process.env.PORT || 3003, () => { + if (server) { + const address = server.address() as AddressInfo; + console.log(`Server is running in http://localhost:${address.port}`); + } else { + console.error(`Failure upon starting server.`); + } +}); + +export default app \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/business/UserBusiness.ts b/modulo5/arquitetura-camadas/src/business/UserBusiness.ts new file mode 100644 index 0000000..b5f3bae --- /dev/null +++ b/modulo5/arquitetura-camadas/src/business/UserBusiness.ts @@ -0,0 +1,173 @@ +import { type } from "os"; +import UserDataBase from "../dataBase/UserDataBase"; +import { InsufficientAuthorization } from "../error/InsufficientAuthorization"; +import { InvalidCredentiais } from "../error/InvalidCredentiais"; +import { InvalidError } from "../error/invalidError"; +import { CreateNewUser, User, UserDB, USER_ROLES } from "../model/User"; +import Authenticator, { TokenPayload } from "../service/Authenticator"; +import GenerateId from "../service/GenerateId"; +import { HashManager } from "../service/HashManager"; + + + +class UserBusiness { + public signup = async (user: CreateNewUser) => { + + const {name, email, password} = user + + + if(!name || typeof name !== "string" || name.length < 3) { + throw new InvalidError("Parâmetro 'name'' inválido") + } + + if(!email || typeof email !== "string") { + throw new InvalidError("Parâmetro 'email' inválido") + } + + if(email.indexOf("@") == -1) { + throw new InvalidError("Seu email deve possuir um @ em sua composição") + } + + if(!password || typeof password !== "string" || password.length < 6) { + throw new InvalidError("Parâmetro 'password' inválido") + } + + const UserData = new UserDataBase() + + const userDB = await UserData.getUserByEmail(email) + + if(userDB) { + throw new InvalidError("Email já cadastrado") + } + + const idGenerator = new GenerateId() + const id = idGenerator.createId() + + const hashManager = new HashManager() + const hashPassword = await hashManager.hash(password) + + const newUser = new User( + id, + name, + email, + hashPassword + ) + + await UserData.createUser(newUser) + + const payload: TokenPayload = { + id: newUser.getId(), + role: newUser.getRole() + } + + const authenticator = new Authenticator() + const token = authenticator.generateToken(payload) + + const response = { + message: "Seu usuário foi criado", + token + } + + return response + + } + + public login = async (email: string, password: string) => { + + if(email.indexOf("@") === -1 || typeof email !== "string"){ + throw new InvalidError("O Seu Email está em formato inválido") + } + + if(password.length < 6) { + throw new InvalidError("Sua senha deve possuir mais de 6 caracteres") + } + + const userData = new UserDataBase() + + const userDB = await userData.getUserByEmail(email) + + if(!userDB) { + throw new InvalidCredentiais() + } + + const hashManager = new HashManager() + + const correctPassword = await hashManager.compare(password, userDB.password) + + if(!correctPassword) { + throw new InvalidCredentiais() + } + + const payload: TokenPayload = { + id: userDB.id, + role: userDB.role + } + + const token = new Authenticator().generateToken(payload) + + const response = { + acess_Token: token + } + + + return response + + + } + + public selectAllUsers = async (token: string) => { + + const userData = new UserDataBase() + + const authenticator = new Authenticator() + const payload = authenticator.verifyToken(token) + + if(!payload) { + throw new InvalidCredentiais() + } + + const users = await userData.getAllUsers() + + if(!users) { + throw new InvalidError("Não existe usuários") + } + + + + return users + + } + + public deleteUser = async (token: string, id: string) => { + const authenticator = new Authenticator() + const payload = authenticator.verifyToken(token) + + if(payload.role !== USER_ROLES.ADMIN) { + throw new InsufficientAuthorization(); + + } + + const userData = new UserDataBase() + const userDB = await userData.getUserById(id) + + if(!userDB) { + throw new InvalidError("O Perfil não foi encontrado") + } + + if(userDB.id === payload.id) { + throw new InvalidError("Você não pode apagar seu perfil estando logado nele") + } + + await userData.removeAccount(id) + + const response = { + message: "A Conta foi apagada com sucesso" + } + + return response + } + +} + + +export default UserBusiness \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/dataBase/BaseDataBase.ts b/modulo5/arquitetura-camadas/src/dataBase/BaseDataBase.ts new file mode 100644 index 0000000..a53634e --- /dev/null +++ b/modulo5/arquitetura-camadas/src/dataBase/BaseDataBase.ts @@ -0,0 +1,30 @@ +import knex from "knex" +import * as dotenv from 'dotenv' + +dotenv.config() + + +class BaseDataBase { + + // Se tiver conexao , ele é knex , senao ele é null + private connetion: knex | null = null; + + protected getConnetion = () => { + // senao tiver conexao com o banco de dados , cria uma ! + if (!this.connetion) { + this.connetion = knex({ + client: "mysql", + connection: { + host: process.env.DB_HOST, + user: process.env.DB_USER, + database: process.env.DB_SCHEMA , + password: process.env.DB_PASSWORD, + port: 3306 + } + }) + } + + return this.connetion + } +} +export default BaseDataBase \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/dataBase/UserDataBase.ts b/modulo5/arquitetura-camadas/src/dataBase/UserDataBase.ts new file mode 100644 index 0000000..a3dec8a --- /dev/null +++ b/modulo5/arquitetura-camadas/src/dataBase/UserDataBase.ts @@ -0,0 +1,68 @@ +import { User, UserDB } from "../model/User"; +import BaseDataBase from "./BaseDataBase"; + + + +class UserDataBase extends BaseDataBase { + public static TABLE_USERS = "Arq_Users" + + public userDBModel = (user: User): UserDB => { + const userDB: UserDB = { + id: user.getId(), + name: user.getName(), + email: user.getEmail(), + password: user.getPassword(), + role: user.getRole() + + } + + return userDB + } + + public createUser = async (user: User) => { + const userDB = this.userDBModel(user) + + await this.getConnetion() + .insert(userDB) + .into(UserDataBase.TABLE_USERS) + + } + + public getUserByEmail = async (email: string): Promise => { + const result: UserDB[] = await this.getConnetion() + .select("*") + .where({email}) + .from(UserDataBase.TABLE_USERS) + + return result[0] + } + + public getAllUsers = async () => { + + const result = await this.getConnetion() + .select("id", "name", "email", "role") + .into(UserDataBase.TABLE_USERS) + + return result + + } + + public getUserById = async (id: string): Promise => { + const result: UserDB[] = await this.getConnetion() + .select("*") + .from(UserDataBase.TABLE_USERS) + .where({id}) + + return result[0] + } + + public removeAccount = async (id: string) => { + await this.getConnetion() + .delete("*") + .where({id}) + .from(UserDataBase.TABLE_USERS) + + } +} + +export default UserDataBase \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/error/BaseError.ts b/modulo5/arquitetura-camadas/src/error/BaseError.ts new file mode 100644 index 0000000..1326691 --- /dev/null +++ b/modulo5/arquitetura-camadas/src/error/BaseError.ts @@ -0,0 +1,8 @@ +export class BaseError extends Error{ + public statusCode:number + + constructor(message:string,statusCode:number){ + super(message) + this.statusCode = statusCode + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/error/InsufficientAuthorization.ts b/modulo5/arquitetura-camadas/src/error/InsufficientAuthorization.ts new file mode 100644 index 0000000..86c3ef6 --- /dev/null +++ b/modulo5/arquitetura-camadas/src/error/InsufficientAuthorization.ts @@ -0,0 +1,7 @@ +import { BaseError } from "./BaseError"; + +export class InsufficientAuthorization extends BaseError { + constructor(){ + super("Autorização insuficiente", 401) + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/error/InvalidCredentiais.ts b/modulo5/arquitetura-camadas/src/error/InvalidCredentiais.ts new file mode 100644 index 0000000..0ff8e16 --- /dev/null +++ b/modulo5/arquitetura-camadas/src/error/InvalidCredentiais.ts @@ -0,0 +1,7 @@ +import { BaseError } from "./BaseError" + +export class InvalidCredentiais extends BaseError{ + constructor(){ + super("Suas credencias estão invalidas", 401) + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/error/MissingFields.ts b/modulo5/arquitetura-camadas/src/error/MissingFields.ts new file mode 100644 index 0000000..839d62e --- /dev/null +++ b/modulo5/arquitetura-camadas/src/error/MissingFields.ts @@ -0,0 +1,8 @@ +import { BaseError } from "./BaseError"; + + +export class MissingFields extends BaseError { + constructor(){ + super("Valores do seu Body devem ser passados", 404) + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/error/invalidError.ts b/modulo5/arquitetura-camadas/src/error/invalidError.ts new file mode 100644 index 0000000..bd78615 --- /dev/null +++ b/modulo5/arquitetura-camadas/src/error/invalidError.ts @@ -0,0 +1,8 @@ +import { BaseError } from "./BaseError" + + +export class InvalidError extends BaseError { + constructor(message: string){ + super(message, 400) + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/index.ts b/modulo5/arquitetura-camadas/src/index.ts new file mode 100644 index 0000000..b1bc65f --- /dev/null +++ b/modulo5/arquitetura-camadas/src/index.ts @@ -0,0 +1,11 @@ +import app from "./app"; +import UserController from "./Controller/UserController"; + + +const userController = new UserController() + + +app.post("/user/signup", userController.signup) +app.post("/user/login", userController.login) +app.get("/user", userController.getAllUsers) +app.delete("/user/:id", userController.deleteUser)s \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/model/User.ts b/modulo5/arquitetura-camadas/src/model/User.ts new file mode 100644 index 0000000..194b1a9 --- /dev/null +++ b/modulo5/arquitetura-camadas/src/model/User.ts @@ -0,0 +1,77 @@ +export enum USER_ROLES { + NORMAL = "NORMAL", + ADMIN = "ADMIN" +} + +export interface UserDB { + id: string, + name: string, + email: string, + password: string, + role: USER_ROLES +} + +export interface CreateNewUser { + name: string, + email: string, + password: string +} + +export class User { + // private id: string + // private name: string + // private email: string + // private password: string + // private role: USER_ROLES = USER_ROLES.NORMAL + + constructor( + private id: string, + private name: string, + private email: string, + private password: string, + private role: USER_ROLES = USER_ROLES.NORMAL + ) + { + // this.id = id, + // this.name = name, + // this.email = email, + // this.password = password, + // this.role = role + } + 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 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 + } + + + +} \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/service/Authenticator.ts b/modulo5/arquitetura-camadas/src/service/Authenticator.ts new file mode 100644 index 0000000..758bd05 --- /dev/null +++ b/modulo5/arquitetura-camadas/src/service/Authenticator.ts @@ -0,0 +1,37 @@ +import jwt from "jsonwebtoken" +import dotenv from "dotenv" +import { USER_ROLES } from "../model/User"; + +dotenv.config(); + +export interface TokenPayload { + id: string, + role: USER_ROLES +} + + +class Authenticator { + + generateToken = (payload: TokenPayload) => { + + const token = jwt.sign( + payload + , + process.env.JWT_KEY as string, + { + expiresIn: process.env.EXPIRES_IN + } + ); + + return token + } + + verifyToken = (token: string) => { + + const payload: TokenPayload = jwt.verify(token, process.env.JWT_KEY as string) as any + + return payload + } +} + +export default Authenticator \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/service/GenerateId.ts b/modulo5/arquitetura-camadas/src/service/GenerateId.ts new file mode 100644 index 0000000..920d2fd --- /dev/null +++ b/modulo5/arquitetura-camadas/src/service/GenerateId.ts @@ -0,0 +1,12 @@ +import { v4 } from "uuid" + +class GenerateId { + + createId = (): string => { + return v4(); + } + +} + + +export default GenerateId \ No newline at end of file diff --git a/modulo5/arquitetura-camadas/src/service/HashManager.ts b/modulo5/arquitetura-camadas/src/service/HashManager.ts new file mode 100644 index 0000000..3360384 --- /dev/null +++ b/modulo5/arquitetura-camadas/src/service/HashManager.ts @@ -0,0 +1,20 @@ +import bcrypt from 'bcryptjs' +import dontev from 'dotenv' + +dontev.config() + +export class HashManager { + public hash = async (plaintext: string) => + { + const rounds = Number(process.env.BRYPT_COST) + 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-camadas/tsconfig.json b/modulo5/arquitetura-camadas/tsconfig.json new file mode 100644 index 0000000..87604aa --- /dev/null +++ b/modulo5/arquitetura-camadas/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "rootDir": "./src", + "outDir": "./build", + "target": "es6", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } + } + \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/.gitignore b/modulo5/arquitetura-dtos/.gitignore new file mode 100644 index 0000000..856f35a --- /dev/null +++ b/modulo5/arquitetura-dtos/.gitignore @@ -0,0 +1,3 @@ +/node_modules +package-lock.json +.env \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/package.json b/modulo5/arquitetura-dtos/package.json new file mode 100644 index 0000000..b3a82fa --- /dev/null +++ b/modulo5/arquitetura-dtos/package.json @@ -0,0 +1,35 @@ +{ + "name": "arquitetura-dtos", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "tsc && node --inspect ./build/index.js", + "dev": "ts-node-dev --transpile-only --ignore-watch node_modules ./src/index.ts", + "test": "ts-node-dev ./src/services/authenticator.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^8.6.0", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1", + "knex": "^0.21.5", + "mysql": "^2.18.1", + "ts-node-dev": "^2.0.0", + "uuid": "^9.0.0" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.2", + "@types/cors": "^2.8.8", + "@types/express": "^4.17.8", + "@types/jsonwebtoken": "^8.5.9", + "@types/knex": "^0.16.1", + "@types/node": "^14.11.2", + "@types/uuid": "^8.3.4", + "typescript": "^4.0.3" + } + } \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/request.rest b/modulo5/arquitetura-dtos/request.rest new file mode 100644 index 0000000..77939c1 --- /dev/null +++ b/modulo5/arquitetura-dtos/request.rest @@ -0,0 +1,21 @@ +POST http://localhost:3003/user/signup Content-Type: application/json + +{ +"name": "Lucas", "email": "luiz42@gmail.com", "password": "abc1254" +} + +### POST http://localhost:3003/user/login Content-Type: application/json + +{ +"email": "lucas@gmailSDASDASDASDASDSADASDASD.com", "password": "abc1254" +} + +### GET http://localhost:3003/user Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImQ2YmRhOGUwLTAxZmEtNDUyZS1hYjg2LTFmYjA4OTU2OWFlNiIsInJvbGUiOiJOT1JNQUwiLCJpYXQiOjE2NjM2MDkwMzYsImV4cCI6MTY2MzY1MjIzNn0.TZpUcqk1JvZf90oGEJi8vgvmSavpU532ybfPEVqCDbw + +### DELETE http://localhost:3003/user/d6bda8e0-01fa-452e-ab86-1fb089569ae6 Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImI3NzJmZGY0LWM5ZmItNDJjMi1hNGJmLTVhZThiODViNTE5YiIsInJvbGUiOiJBRE1JTiIsImlhdCI6MTY2MzYxMDg0MCwiZXhwIjoxNjYzNjU0MDQwfQ.UnW6aalc52xq54ToXwT1uV-S1QIxAhaF84dfDda5xIs + +### PUT http://localhost:3003/user/16eaba0e-4332-4964-adc0-3d344dd3e1c5 Authorization:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6ImI3NzJmZGY0LWM5ZmItNDJjMi1hNGJmLTVhZThiODViNTE5YiIsInJvbGUiOiJBRE1JTiIsImlhdCI6MTY2MzY5Nzk1NiwiZXhwIjoxNjYzNzQxMTU2fQ.E6Nb10sL43D8Zij1BOJf58YzUDfU6d1AZacAo7T0DAI Content-Type: application/json + +{ +"name": "Astrodev3", "email": "Lucas@gmail.com", "password": "Baninha123" +} \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/Controller/UserController.ts b/modulo5/arquitetura-dtos/src/Controller/UserController.ts new file mode 100644 index 0000000..1cabd9b --- /dev/null +++ b/modulo5/arquitetura-dtos/src/Controller/UserController.ts @@ -0,0 +1,176 @@ +import { Request, Response } from "express"; +import UserBusiness from "../business/UserBusiness"; +import { InvalidError } from "../error/invalidError"; +import { MissingFields } from "../error/MissingFields"; +import { IDeleteInputDTO, IEditUserDTO, IGetUsersInputDTO, loginInputDTO, SignupInputDTO } from "../model/User"; + + +class UserController { + + public signup = async (req: Request, res: Response): Promise => { + + try { + const {name, email, password} = req.body + + if(!name || !email || !password) { + throw new MissingFields() + } + + const newUser: SignupInputDTO = { + name: name, + email: email, + password: password + } + + const userBusiness = new UserBusiness() + const user = await userBusiness.signup(newUser) + + res.status(201).send(user) + + } catch (error) { + if(error instanceof Error) { + res.status(400).send({message: error.message}) + return + } + + res.status(500).send({message: "Erro inesperado"}) + } + + } + + public login = async (req: Request, res: Response): Promise => { + try { + const {email, password} = req.body + + if(!email || !password) { + throw new MissingFields() + } + + const login: loginInputDTO = { + email: email, + password: password + } + + const userBusiness = new UserBusiness(); + + const loginUser = await userBusiness.login(login) + + res.status(200).send(loginUser) + + } catch (error: any) { + res.status(error.statusCode || 500) + .send({ message: error.message || "Erro inesperado" }) + + } + } + + public getAllUsers = async (req: Request, res: Response) => { + try { + + const token = req.headers.authorization as string + const search = req.query.search as string + const order = req.query.order as string + const sort = req.query.sort as string + const limit = req.query.limit as string + const page = req.query.page as string + + if(!token) { + throw new InvalidError("Token não foi enviado") + } + + + const users: IGetUsersInputDTO = { + token: token, + search: search, + order: order, + sort: sort, + limit: limit, + page: page + } + + const userBusiness = new UserBusiness() + + const allUsers = await userBusiness.selectAllUsers(users) + + res.status(200).send(allUsers) + + } catch (error) { + if(error instanceof Error) { + return res.status(400).send({message: error.message}) + } + + res.status(500).send({message: "Erro inesperado"}) + + } + } + + public deleteUser = async (req: Request, res: Response): Promise => { + try { + const token = req.headers.authorization as string + const id = req.params.id + + if(!token) { + throw new InvalidError("Token não foi enviado") + } + + if(!id) { + throw new InvalidError("Seu Id não foi informado") + } + + const user: IDeleteInputDTO = { + token: token, + id: id + } + + const userBusiness = new UserBusiness() + + const deleteUser = await userBusiness.deleteUser(user) + + res.status(200).send(deleteUser) + + } catch (error: any) { + res.status(error.statusCode || 500) + .send({ message: error.message || "Erro inesperado" }) + } + + + } + + public editUser = async (req: Request, res: Response): Promise => { + try { + const token = req.headers.authorization as string + const id = req.params.id + const {name, email, password} = req.body + + + const newUser: IEditUserDTO = { + token: token, + id: id, + name: name, + email: email, + password: password + } + + const userBusiness = new UserBusiness() + + const user = await userBusiness.editUser(newUser) + + res.status(200).send(user) + + } catch (error) { + if(error instanceof Error) { + res.status(400).send({message: error.message}) + return + } + + res.status(500).send({message: "Erro inesperado"}) + + } + } +} + + + + + +export default UserController \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/app.ts b/modulo5/arquitetura-dtos/src/app.ts new file mode 100644 index 0000000..dc8bf78 --- /dev/null +++ b/modulo5/arquitetura-dtos/src/app.ts @@ -0,0 +1,19 @@ +import express from "express"; +import cors from "cors"; +import { AddressInfo } from "net"; + +const app = express(); + +app.use(express.json()); +app.use(cors()); + +const server = app.listen(process.env.PORT || 3003, () => { + if (server) { + const address = server.address() as AddressInfo; + console.log(`Server is running in http://localhost:${address.port}`); + } else { + console.error(`Failure upon starting server.`); + } +}); + +export default app \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/business/UserBusiness.ts b/modulo5/arquitetura-dtos/src/business/UserBusiness.ts new file mode 100644 index 0000000..8c9a97c --- /dev/null +++ b/modulo5/arquitetura-dtos/src/business/UserBusiness.ts @@ -0,0 +1,296 @@ +import { type } from "os"; +import UserDataBase from "../dataBase/UserDataBase"; +import { InsufficientAuthorization } from "../error/InsufficientAuthorization"; +import { InvalidCredentiais } from "../error/InvalidCredentiais"; +import { InvalidError } from "../error/invalidError"; +import { MissingFields } from "../error/MissingFields"; +import { GetUserModelInputDTO, GetUsersInputDBDTO, IDeleteInputDTO, IEditUserDTO, IGetUsersInputDTO, ISignupAndLoginOutpuDTO, loginInputDTO, SignupInputDTO, User, UserDB, USER_ROLES } from "../model/User"; +import Authenticator, { TokenPayload } from "../service/Authenticator"; +import GenerateId from "../service/GenerateId"; +import { HashManager } from "../service/HashManager"; + + + +class UserBusiness { + public signup = async (user: SignupInputDTO) => { + + const {name, email, password} = user + + + if(!name || typeof name !== "string" || name.length < 3) { + throw new InvalidError("Parâmetro 'name'' inválido") + } + + if(!email || typeof email !== "string") { + throw new InvalidError("Parâmetro 'email' inválido") + } + + if(email.indexOf("@") == -1) { + throw new InvalidError("Seu email deve possuir um @ em sua composição") + } + + if(!password || typeof password !== "string" || password.length < 6) { + throw new InvalidError("Parâmetro 'password' inválido") + } + + const UserData = new UserDataBase() + + const userDB = await UserData.getUserByEmail(email) + + if(userDB) { + throw new InvalidError("Email já cadastrado") + } + + const idGenerator = new GenerateId() + const id = idGenerator.createId() + + const hashManager = new HashManager() + const hashPassword = await hashManager.hash(password) + + const newUser = new User( + id, + name, + email, + hashPassword + ) + + await UserData.createUser(newUser) + + const payload: TokenPayload = { + id: newUser.getId(), + role: newUser.getRole() + } + + const authenticator = new Authenticator() + const token = authenticator.generateToken(payload) + + const response: ISignupAndLoginOutpuDTO = { + message: "Seu usuário foi criado", + token + } + + return response + + } + + public login = async (user: loginInputDTO) => { + const email = user.email + const password = user.password + + if(!email.match(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/) || typeof email !== "string"){ + throw new InvalidError("O Seu Email está em formato inválido") + } + + if(password.length < 6) { + throw new InvalidError("Sua senha deve possuir mais de 6 caracteres") + } + + const userData = new UserDataBase() + + const userDB = await userData.getUserByEmail(email) + + if(!userDB) { + throw new InvalidCredentiais() + } + + const hashManager = new HashManager() + + const correctPassword = await hashManager.compare(password, userDB.password) + + if(!correctPassword) { + throw new InvalidCredentiais() + } + + const payload: TokenPayload = { + id: userDB.id, + role: userDB.role + } + + const token = new Authenticator().generateToken(payload) + + const response = { + acess_Token: token + } + + + return response + + + } + + public selectAllUsers = async (users: IGetUsersInputDTO) => { + const token = users.token + const search = users.search || "" + const order = users.order || "name" + const sort = users.sort || "ASC" + const limit = Number(users.limit) || 10 + const page = Number(users.page) || 1 + + const offset = limit * (page - 1) + + if(typeof token !== "string") { + throw new InvalidError("Seu Token não está em formato correto") + } + + if(typeof search !== "string") { + throw new InvalidError("Sua busca não está em formato de string") + } + + if(typeof order !== "string" || order !== "ASC" && order !== "DESC") { + throw new InvalidError("Sua ordenação está em formato errado") + + } + + if(typeof limit !== "number" ){ + throw new InvalidError("Seu Limite não está em parâmetro number") + } + + if(typeof page !== "number") { + throw new InvalidError("O Limite passado não está em Number") + } + + const getUsersDB: GetUsersInputDBDTO = { + search, + order, + sort, + limit, + offset + } + + const userData = new UserDataBase() + + const authenticator = new Authenticator() + const payload = authenticator.verifyToken(token) + + if(!payload) { + throw new InvalidCredentiais() + } + + const user = await userData.getAllUsers(getUsersDB) + + if(!user) { + throw new InvalidError("Não existe usuários") + } + + const totalUsers = user.map((userDB) => { + const user = new User( + userDB.id, + userDB.name, + userDB.email, + userDB.password, + userDB.role + + ) + + const userResponse: GetUserModelInputDTO = { + id: user.getId(), + name: user.getName(), + email: user.getEmail() + } + + return userResponse + + }) + + const response = { + totalUsers + } + + return response + + } + + public deleteUser = async (user: IDeleteInputDTO) => { + const token = user.token + const id = user.id + + const authenticator = new Authenticator() + const payload = authenticator.verifyToken(token) + + if(payload.role !== USER_ROLES.ADMIN) { + throw new InsufficientAuthorization(); + + } + + const userData = new UserDataBase() + const userDB = await userData.getUserById(id) + + if(!userDB) { + throw new InvalidError("O Perfil não foi encontrado") + } + + if(userDB.id === payload.id) { + throw new InvalidError("Você não pode apagar seu perfil estando logado nele") + } + + await userData.removeAccount(id) + + const response = { + message: "A Conta foi apagada com sucesso" + } + + return response + } + + public editUser = async (user: IEditUserDTO) => { + const { token, id, email, name, password } = user + + if(!token) { + throw new InvalidError("Token não foi enviado") + } + + if(!id) { + throw new InvalidError("Seu Id não foi informado") + } + + const authenticator = new Authenticator() + const payload = authenticator.verifyToken(token) + + if(!payload) { + throw new InvalidCredentiais() + } + + if(email && typeof email !== "string" || !email.match(/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/)) { + throw new InvalidError("Parâmetro 'email' inválido") + } + + if(name && typeof name !== "string" || name.length < 3) { + throw new InvalidError("Parâmetro name invalido") + } + + if(password && typeof password !== "string" || password.length < 6) { + throw new InvalidError("Parâmetro 'password' inválido") + } + + const userData = new UserDataBase() + const userDB = await userData.getUserById(id) + + if(!userDB) { + throw new InvalidError("Usuário não existe") + } + + const Edituser = new User ( + userDB.id, + userDB.name, + userDB.email, + userDB.password, + userDB.role + ) + + name && Edituser.setName(name) + email && Edituser.setEmail(email) + password && Edituser.setPassword(password) + + await userData.editUsers(Edituser) + + const response = { + message: "Edição realizada" + } + + return response + } + +} + + +export default UserBusiness \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/dataBase/BaseDataBase.ts b/modulo5/arquitetura-dtos/src/dataBase/BaseDataBase.ts new file mode 100644 index 0000000..a53634e --- /dev/null +++ b/modulo5/arquitetura-dtos/src/dataBase/BaseDataBase.ts @@ -0,0 +1,30 @@ +import knex from "knex" +import * as dotenv from 'dotenv' + +dotenv.config() + + +class BaseDataBase { + + // Se tiver conexao , ele é knex , senao ele é null + private connetion: knex | null = null; + + protected getConnetion = () => { + // senao tiver conexao com o banco de dados , cria uma ! + if (!this.connetion) { + this.connetion = knex({ + client: "mysql", + connection: { + host: process.env.DB_HOST, + user: process.env.DB_USER, + database: process.env.DB_SCHEMA , + password: process.env.DB_PASSWORD, + port: 3306 + } + }) + } + + return this.connetion + } +} +export default BaseDataBase \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/dataBase/UserDataBase.ts b/modulo5/arquitetura-dtos/src/dataBase/UserDataBase.ts new file mode 100644 index 0000000..2f8cba3 --- /dev/null +++ b/modulo5/arquitetura-dtos/src/dataBase/UserDataBase.ts @@ -0,0 +1,86 @@ +import { GetUsersInputDBDTO, IGetUsersInputDTO, User, UserDB } from "../model/User"; +import BaseDataBase from "./BaseDataBase"; + + + +class UserDataBase extends BaseDataBase { + public static TABLE_USERS = "Arq_Users" + + public userDBModel = (user: User): UserDB => { + const userDB: UserDB = { + id: user.getId(), + name: user.getName(), + email: user.getEmail(), + password: user.getPassword(), + role: user.getRole() + + } + + return userDB + } + + public createUser = async (user: User) => { + const userDB = this.userDBModel(user) + + await this.getConnetion() + .insert(userDB) + .into(UserDataBase.TABLE_USERS) + + } + + public getUserByEmail = async (email: string): Promise => { + const result: UserDB[] = await this.getConnetion() + .select("*") + .where({email}) + .from(UserDataBase.TABLE_USERS) + + return result[0] + } + + public getAllUsers = async (users: GetUsersInputDBDTO ) => { + const search = users.search + const order = users.order + const sort = users.sort + const limit = users.limit + const offset = users.offset + + const result: UserDB[] = await this.getConnetion() + .select("*") + .where("name", "LIKE", `%${search}%`) + .orderBy(order, sort) + .limit(limit) + .offset(offset) + .into(UserDataBase.TABLE_USERS) + + return result + + } + + public getUserById = async (id: string): Promise => { + const result: UserDB[] = await this.getConnetion() + .select("*") + .from(UserDataBase.TABLE_USERS) + .where({id}) + + return result[0] + } + + public removeAccount = async (id: string) => { + await this.getConnetion() + .delete("*") + .where({id}) + .from(UserDataBase.TABLE_USERS) + + } + + public editUsers = async (user: User) => { + const userDB = this.userDBModel(user) + this.getConnetion() + .update(userDB) + .where({id: userDB.id}) + .into(UserDataBase.TABLE_USERS) + + } +} + +export default UserDataBase \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/error/BaseError.ts b/modulo5/arquitetura-dtos/src/error/BaseError.ts new file mode 100644 index 0000000..1326691 --- /dev/null +++ b/modulo5/arquitetura-dtos/src/error/BaseError.ts @@ -0,0 +1,8 @@ +export class BaseError extends Error{ + public statusCode:number + + constructor(message:string,statusCode:number){ + super(message) + this.statusCode = statusCode + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/error/InsufficientAuthorization.ts b/modulo5/arquitetura-dtos/src/error/InsufficientAuthorization.ts new file mode 100644 index 0000000..86c3ef6 --- /dev/null +++ b/modulo5/arquitetura-dtos/src/error/InsufficientAuthorization.ts @@ -0,0 +1,7 @@ +import { BaseError } from "./BaseError"; + +export class InsufficientAuthorization extends BaseError { + constructor(){ + super("Autorização insuficiente", 401) + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/error/InvalidCredentiais.ts b/modulo5/arquitetura-dtos/src/error/InvalidCredentiais.ts new file mode 100644 index 0000000..0ff8e16 --- /dev/null +++ b/modulo5/arquitetura-dtos/src/error/InvalidCredentiais.ts @@ -0,0 +1,7 @@ +import { BaseError } from "./BaseError" + +export class InvalidCredentiais extends BaseError{ + constructor(){ + super("Suas credencias estão invalidas", 401) + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/error/MissingFields.ts b/modulo5/arquitetura-dtos/src/error/MissingFields.ts new file mode 100644 index 0000000..839d62e --- /dev/null +++ b/modulo5/arquitetura-dtos/src/error/MissingFields.ts @@ -0,0 +1,8 @@ +import { BaseError } from "./BaseError"; + + +export class MissingFields extends BaseError { + constructor(){ + super("Valores do seu Body devem ser passados", 404) + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/error/invalidError.ts b/modulo5/arquitetura-dtos/src/error/invalidError.ts new file mode 100644 index 0000000..bd78615 --- /dev/null +++ b/modulo5/arquitetura-dtos/src/error/invalidError.ts @@ -0,0 +1,8 @@ +import { BaseError } from "./BaseError" + + +export class InvalidError extends BaseError { + constructor(message: string){ + super(message, 400) + } +} \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/index.ts b/modulo5/arquitetura-dtos/src/index.ts new file mode 100644 index 0000000..4f15827 --- /dev/null +++ b/modulo5/arquitetura-dtos/src/index.ts @@ -0,0 +1,6 @@ +import app from "./app"; +import { userRouter } from "./router/userRouter"; + + + +app.use("/user", userRouter) \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/model/User.ts b/modulo5/arquitetura-dtos/src/model/User.ts new file mode 100644 index 0000000..aa54976 --- /dev/null +++ b/modulo5/arquitetura-dtos/src/model/User.ts @@ -0,0 +1,123 @@ +export enum USER_ROLES { + NORMAL = "NORMAL", + ADMIN = "ADMIN" +} + +export interface UserDB { + id: string, + name: string, + email: string, + password: string, + role: USER_ROLES +} + +export class User { + // private id: string + // private name: string + // private email: string + // private password: string + // private role: USER_ROLES = USER_ROLES.NORMAL + + constructor( + private id: string, + private name: string, + private email: string, + private password: string, + private role: USER_ROLES = USER_ROLES.NORMAL + ) + { + // this.id = id, + // this.name = name, + // this.email = email, + // this.password = password, + // this.role = role + } + 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 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 SignupInputDTO { + name: string, + email: string, + password: string +} + +export interface loginInputDTO { + email: string, + password: string + +} + +export interface ISignupAndLoginOutpuDTO { + message: string, + token: string +} + +export interface IGetUsersInputDTO { + token: string, + search: string, + order: string, + sort: string, + limit: string, + page: string +} + +export interface GetUsersInputDBDTO { + search: string, + order: string, + sort: string, + limit: number, + offset: number +} + +export interface GetUserModelInputDTO { + id: string, + name: string, + email: string +} + +export interface IDeleteInputDTO { + token: string, + id: string +} + +export interface IEditUserDTO { + token: string + id: string + name: string + email: string + password: string +} \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/router/userRouter.ts b/modulo5/arquitetura-dtos/src/router/userRouter.ts new file mode 100644 index 0000000..af82d96 --- /dev/null +++ b/modulo5/arquitetura-dtos/src/router/userRouter.ts @@ -0,0 +1,16 @@ +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.getAllUsers) + +userRouter.delete("/:id", userController.deleteUser) +userRouter.put("/:id", userController.editUser) \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/service/Authenticator.ts b/modulo5/arquitetura-dtos/src/service/Authenticator.ts new file mode 100644 index 0000000..758bd05 --- /dev/null +++ b/modulo5/arquitetura-dtos/src/service/Authenticator.ts @@ -0,0 +1,37 @@ +import jwt from "jsonwebtoken" +import dotenv from "dotenv" +import { USER_ROLES } from "../model/User"; + +dotenv.config(); + +export interface TokenPayload { + id: string, + role: USER_ROLES +} + + +class Authenticator { + + generateToken = (payload: TokenPayload) => { + + const token = jwt.sign( + payload + , + process.env.JWT_KEY as string, + { + expiresIn: process.env.EXPIRES_IN + } + ); + + return token + } + + verifyToken = (token: string) => { + + const payload: TokenPayload = jwt.verify(token, process.env.JWT_KEY as string) as any + + return payload + } +} + +export default Authenticator \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/service/GenerateId.ts b/modulo5/arquitetura-dtos/src/service/GenerateId.ts new file mode 100644 index 0000000..920d2fd --- /dev/null +++ b/modulo5/arquitetura-dtos/src/service/GenerateId.ts @@ -0,0 +1,12 @@ +import { v4 } from "uuid" + +class GenerateId { + + createId = (): string => { + return v4(); + } + +} + + +export default GenerateId \ No newline at end of file diff --git a/modulo5/arquitetura-dtos/src/service/HashManager.ts b/modulo5/arquitetura-dtos/src/service/HashManager.ts new file mode 100644 index 0000000..3360384 --- /dev/null +++ b/modulo5/arquitetura-dtos/src/service/HashManager.ts @@ -0,0 +1,20 @@ +import bcrypt from 'bcryptjs' +import dontev from 'dotenv' + +dontev.config() + +export class HashManager { + public hash = async (plaintext: string) => + { + const rounds = Number(process.env.BRYPT_COST) + 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-dtos/tsconfig.json b/modulo5/arquitetura-dtos/tsconfig.json new file mode 100644 index 0000000..87604aa --- /dev/null +++ b/modulo5/arquitetura-dtos/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "rootDir": "./src", + "outDir": "./build", + "target": "es6", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + } + } + \ No newline at end of file