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
5 changes: 5 additions & 0 deletions modulo6/rodada-case-2/amaro-backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.env
node_modules
build
.DS_STORE
coverage
8 changes: 8 additions & 0 deletions modulo6/rodada-case-2/amaro-backend/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
roots: ["<rootDir>/tests"],
transform: {
"^.+\\.tsx?$": "ts-jest",
},
testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
}
40 changes: 40 additions & 0 deletions modulo6/rodada-case-2/amaro-backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "template-backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node ./build/src/index.js",
"build": "tsc",
"dev": "ts-node-dev ./src/index.ts",
"migrations": "tsc && node ./build/src/database/migrations/Migrations.js",
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/bcryptjs": "^2.4.2",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.14",
"@types/jest": "^29.0.3",
"@types/jsonwebtoken": "^8.5.9",
"@types/knex": "^0.16.1",
"@types/node": "^18.7.23",
"@types/uuid": "^8.3.4",
"jest": "^29.1.1",
"ts-jest": "^29.0.2",
"ts-node-dev": "^2.0.0",
"typescript": "^4.8.4"
},
"dependencies": {
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.0.2",
"express": "^4.18.1",
"jsonwebtoken": "^8.5.1",
"knex": "^2.3.0",
"mysql": "^2.18.1",
"uuid": "^9.0.0"
}
}
21 changes: 21 additions & 0 deletions modulo6/rodada-case-2/amaro-backend/requests.rest
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
### Ping
GET http://localhost:3003/ping

### Signup
POST http://localhost:3003/users/signup
Content-Type: application/json

{
"name": "alice",
"email": "alice@gmail.com",
"password": "alice99"
}

### Login
POST http://localhost:3003/users/login
Content-Type: application/json

{
"email": "astrodev@gmail.com",
"password": "bananinha"
}
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
}
}
136 changes: 136 additions & 0 deletions modulo6/rodada-case-2/amaro-backend/src/business/UserBusiness.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import { UserDatabase } from "../database/UserDatabase"
import { NotFoundError } from "../errors/NotFoundError"
import { ConflictError} from "../errors/ConflictError"
import { ParamsError} from "../errors/ParamsError"
import { ILoginInputDTO, ILoginOutputDTO, ISignupInputDTO, ISignupOutputDTO, User, USER_ROLES } from "../models/Users"
import { Authenticator, ITokenPayload } from "../services/Authenticator"
import { HashManager } from "../services/HashManager"
import { IdGenerator } from "../services/IdGenerator"
import { AuthenticationError } from "../errors/AuthenticationError"

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

public signup = async (input: ISignupInputDTO): Promise<ISignupOutputDTO> => {
const { name, email, password } = input

if (typeof name !== "string") {
throw new ParamsError("Parâmetro 'name' inválido: deve ser uma string")
}

if (typeof email !== "string") {
throw new ParamsError("Parâmetro 'email' inválido: deve ser uma string")
}

if (typeof password !== "string") {
throw new ParamsError("Parâmetro 'password' inválido: deve ser uma string")
}

if (name.length < 3) {
throw new ParamsError("Parâmetro 'name' inválido: mínimo de 3 caracteres")
}

if (password.length < 6) {
throw new ParamsError("Parâmetro 'password' inválido: mínimo de 6 caracteres")
}

if (!email.match(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g)) {
throw new ParamsError("Parâmetro 'email' inválido")
}

const isEmailAlreadyExists = await this.userDatabase.findByEmail(email)

if (isEmailAlreadyExists) {
throw new ConflictError("Email 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: ISignupOutputDTO = {
message: "Cadastro realizado com sucesso",
token
}

return response
}

public login = async (input: ILoginInputDTO): Promise<ILoginOutputDTO> => {
const { email, password } = input

if (typeof email !== "string") {
throw new ParamsError("Parâmetro 'email' inválido")
}

if (typeof password !== "string") {
throw new ParamsError("Parâmetro 'password' inválido")
}

if (password.length < 6) {
throw new ParamsError("Parâmetro 'password' inválido: mínimo de 6 caracteres")
}

if (!email.match(/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g)) {
throw new ParamsError("Parâmetro 'email' inválido")
}

const userDB = await this.userDatabase.findByEmail(email)

if (!userDB) {
throw new NotFoundError("Email 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 AuthenticationError("Password incorreto")
}

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

const token = this.authenticator.generateToken(payload)

const response: ILoginOutputDTO = {
message: "Login realizado com sucesso",
token
}

return response
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Request, Response } from "express"
import { PingBusiness } from "../business/PingBusiness"
import { BaseError } from "../errors/BaseError"

export class PingController {
constructor(
private pingBusiness: PingBusiness
) {}

public ping = async (req: Request, res: Response) => {
try {
const response = await this.pingBusiness.ping()
res.status(200).send(response)
} catch (error) {
console.log(error)
if (error instanceof BaseError) {
return res.status(error.statusCode).send({ message: error.message })
}
res.status(500).send({ message: "Erro inesperado no endpoint ping" })
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Request, Response } from "express";
import { UserBusiness } from "../business/UserBusiness";
import { BaseError } from "../errors/BaseError";
import { ILoginInputDTO, ISignupInputDTO } from "../models/Users";

export class UserController {
constructor(
private userBusiness: UserBusiness
) {}

public signup = async (req: Request, res: Response) => {
try {
const input: ISignupInputDTO = {
name: req.body.name,
email: req.body.email,
password: req.body.password
}

const response = await this.userBusiness.signup(input)
res.status(201).send(response)
} catch (error) {
console.log(error)
if (error instanceof BaseError) {
return res.status(error.statusCode).send({ message: error.message })
}
res.status(500).send({ message: "Erro inesperado ao cadastrar usuário" })
}
}

public login = async (req: Request, res: Response) => {
try {
const input: ILoginInputDTO = {
email: req.body.email,
password: req.body.password
}

const response = await this.userBusiness.login(input)
res.status(200).send(response)
} catch (error) {
console.log(error)
if (error instanceof BaseError) {
return res.status(error.statusCode).send({ message: error.message })
}
res.status(500).send({ message: "Erro inesperado ao cadastrar usuário" })
}
}
}
18 changes: 18 additions & 0 deletions modulo6/rodada-case-2/amaro-backend/src/database/BaseDatabase.ts
Original file line number Diff line number Diff line change
@@ -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
},
})
}
39 changes: 39 additions & 0 deletions modulo6/rodada-case-2/amaro-backend/src/database/UserDatabase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { IUserDB, User } from "../models/Users"
import { BaseDatabase } from "./BaseDatabase"

export class UserDatabase extends BaseDatabase {

public static TABLE_USERS = "Template_Users"
public static TABLE_PRODUCTS = "Am_Products"
public static TABLE_TAGS = "Am_Tags"
public static TABLE_TAGS_PRODUCTS = "Am_Tags_Products"

public toUserDBModel = (user: User): IUserDB => {
const userDB: IUserDB = {
id: user.getId(),
name: user.getName(),
email: user.getEmail(),
password: user.getPassword(),
role: user.getRole()
}

return userDB
}

public findByEmail = async (email: string): Promise<IUserDB | undefined> => {
const result: IUserDB[] = await BaseDatabase
.connection(UserDatabase.TABLE_USERS)
.select()
.where({ email })

return result[0]
}

public createUser = async (user: User): Promise<void> => {
const userDB = this.toUserDBModel(user)

await BaseDatabase
.connection(UserDatabase.TABLE_USERS)
.insert(userDB)
}
}
Loading