Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions modulo5/arquitetura-injecao/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.env
.DS_STORE
node_modules
build
3 changes: 3 additions & 0 deletions modulo5/arquitetura-injecao/.vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": []
}
3,114 changes: 3,114 additions & 0 deletions modulo5/arquitetura-injecao/package-lock.json

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions modulo5/arquitetura-injecao/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
38 changes: 38 additions & 0 deletions modulo5/arquitetura-injecao/requests.rest
Original file line number Diff line number Diff line change
@@ -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": "Jurubeba",
"email": "jurubeba@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.eyJpZCI6ImFkMjcxN2IzLTJjMzAtNDVlMS1hYmVkLTFlZWE2YmU5MWY1YiIsInJvbGUiOiJOT1JNQUwiLCJpYXQiOjE2NjM3OTM1MzMsImV4cCI6MTY2Mzg3OTkzM30.OAsNDC8Q-5pH0zunZ1Cl2Un13XCuQ8-D5NbQWGsrDpw

### 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"
}
9 changes: 9 additions & 0 deletions modulo5/arquitetura-injecao/src/business/PingBusiness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export class PingBusiness {
public ping = async () => {
const response = {
message: "Pong!"
}

return response
}
}
293 changes: 293 additions & 0 deletions modulo5/arquitetura-injecao/src/business/UserBusiness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
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 {

constructor(
private userDatabase: UserDatabase,
private idGenerator: IdGenerator,
private hashManager: HashManager,
private authenticator: Authenticator
) { }

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 userDB = await this.userDatabase.findByEmail(email)

if (userDB) {
throw new Error("E-mail já cadastrado")
}

const id = this.idGenerator.generate()
const hashedPassword = await this.hashManager.hash(password)

const user = new User(
id,
name,
email,
hashedPassword,
USER_ROLES.NORMAL
)

await this.userDatabase.createUser(user)

const payload: ITokenPayload = {
id: user.getId(),
role: user.getRole()
}

const token = this.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 userDB = await this.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 isPasswordCorrect = await this.hashManager.compare(password, user.getPassword())

if (!isPasswordCorrect) {
throw new Error("Senha incorreta")
}

const payload: ITokenPayload = {
id: user.getId(),
role: user.getRole()
}

const token = this.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 payload = this.authenticator.getTokenPayload(token)

if (!payload) {
throw new Error("Token inválido ou faltando")
}

const getUsersInputDB: IGetUsersDBDTO = {
search,
order,
sort,
limit,
offset
}

const usersDB = await this.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 payload = this.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 userDB = await this.userDatabase.findById(idToDelete)

if (!userDB) {
throw new Error("Usuário a ser deletado não encontrado")
}

await this.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 payload = this.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 userDB = await this.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 this.userDatabase.editUser(user)

const response = {
message: "Edição realizada com sucesso"
}

return response
}
}
Loading