diff --git a/modulo5/case-estante-coe/.gitignore b/modulo5/case-estante-coe/.gitignore new file mode 100644 index 0000000..8ece3ba --- /dev/null +++ b/modulo5/case-estante-coe/.gitignore @@ -0,0 +1,4 @@ +node_modules +package-lock.json +build +.env \ No newline at end of file diff --git a/modulo5/case-estante-coe/README.md b/modulo5/case-estante-coe/README.md new file mode 100644 index 0000000..01fc0b7 --- /dev/null +++ b/modulo5/case-estante-coe/README.md @@ -0,0 +1,23 @@ +# Teste prático da EstanteVirtual + +### Documentação postman: + +### Descrição: + +* Teste prático da EstanteVirtual*, Com a chegada dos jogos olímpicos, fomos designados para construir uma API REST em Ruby para o COB (Comitê Olímico Brasileiro), que será responsável por marcar e dizer os vencedores das seguintes modalidades: + +As modalidades escolhidas foram: +100m rasos: Menor tempo vence +Lançamento de Dardo: Maior distância vence +Para verifica-lo deve-se baixar o arquivo atraves do git clone e instalar as dependencias atraves do npm i + +### Funcionalidades: + +1. Cadastro +2. Login +3. Endpoint de registrar banda +4. Endpoint de visualização de detalhes sobre a banda +5. Endpoint de adicionar um show a um dia +6. Endpoint de pegar todos os shows de uma data + + diff --git a/modulo5/case-estante-coe/jest.config.js b/modulo5/case-estante-coe/jest.config.js new file mode 100644 index 0000000..2ff86e1 --- /dev/null +++ b/modulo5/case-estante-coe/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + roots: ["/tests"], + transform: { + "^.+\\.tsx?$": "ts-jest", + }, + testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], +}; \ No newline at end of file diff --git a/modulo5/case-estante-coe/package.json b/modulo5/case-estante-coe/package.json new file mode 100644 index 0000000..447b9db --- /dev/null +++ b/modulo5/case-estante-coe/package.json @@ -0,0 +1,22 @@ +{ + "name": "case1", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start:dev": "ts-node-dev ./src/index.ts", + "start": "tsc && node ./build/index.js", + "build": "tsc", + "dev": "ts-node-dev ./src/index.ts" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bcryptjs": "^2.4.3", + "cors": "^2.8.5", + "dotenv": "^16.0.1", + "jsonwebtoken": "^8.5.1", + "typescript": "^4.6.4" + } +} diff --git a/modulo5/case-estante-coe/src/Business/DartCompetition/DartCompetitionBusiness.ts b/modulo5/case-estante-coe/src/Business/DartCompetition/DartCompetitionBusiness.ts new file mode 100644 index 0000000..171888e --- /dev/null +++ b/modulo5/case-estante-coe/src/Business/DartCompetition/DartCompetitionBusiness.ts @@ -0,0 +1,93 @@ +import { Competition, InputDTO, Progretion } from "../../Model/Competition"; +import { IdGenerator } from "../../Services/IdGeneretor"; +import { DartCompetitionRepository } from "./DartCompetitionRepository"; + +export default class DartCompetitionBusiness { + private idGenerator: IdGenerator; + private competitionData: DartCompetitionRepository; + + constructor( + competitionDataImplementation: DartCompetitionRepository + ) { + this.idGenerator = new IdGenerator + this.competitionData = competitionDataImplementation + } + + insertDart = async (input: InputDTO) => { + const { athlete, competition, unity, valor } = input + + if (!competition || !athlete || !valor || !unity) { + throw new Error("Preencha todos os campos") + } + + if (typeof competition !== "string" || typeof athlete !== "string" || typeof valor !== "string" || typeof unity !== "string") { + throw new Error("Preencha valores válidos") + } + + if (unity !== "m") { + throw new Error("unity inválida!") + } + + const verificaProgretionCompetition = await this.competitionData.getSituationByName(competition, Progretion.FINISH) + + if (verificaProgretionCompetition?.length > 0) { + throw new Error("Competição finalizada.") + } + + const verificaChances = await this.competitionData.getAthleteCount(athlete, competition) + + if (verificaChances >= 3) { + throw new Error("O athlete já realizou todos os lançamentos!") + } + + if (unity !== "m") { + throw new Error("unity inválida!") + } + + const id = this.idGenerator.generate() + + const progretion = Progretion.PROGRESS + + const novacompetition = new Competition( + id, + competition, + athlete, + valor, + unity, + progretion + ) + + await this.competitionData.insert(novacompetition) + + + } + + finishDart = async (input: string) => { + const competition = input + + const progretion = Progretion.FINISH + + const verifyCompetition = await this.competitionData.getCompetitionByName(competition) + + if (!verifyCompetition) { + throw new Error("Competição ou modalidade não existente.") + } + + await this.competitionData.updateSituation(progretion, competition) + } + + getRanking = async (input: string) => { + const competition = input + + const verificacompetition = await this.competitionData.getCompetitionByName(competition) + + if (!verificacompetition) { + throw new Error("Competição ou modalidade não existente") + + } + + const resultado = await this.competitionData.getRanking(competition) + + return resultado + } +} \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Business/DartCompetition/DartCompetitionRepository.ts b/modulo5/case-estante-coe/src/Business/DartCompetition/DartCompetitionRepository.ts new file mode 100644 index 0000000..cad133c --- /dev/null +++ b/modulo5/case-estante-coe/src/Business/DartCompetition/DartCompetitionRepository.ts @@ -0,0 +1,12 @@ +import { Competition, Progretion } from "../../Model/Competition" + +export interface DartCompetitionRepository { + insert(competition: Competition): Promise + getByAthlete(athlete: string): Promise + getCompetitionByName(competition: string): Promise + updateSituation(situation: Progretion.FINISH, competition: string): Promise + getSituationByName(competition: string, situation: Progretion.FINISH): Promise + getCompetitionAndAthlete(competition: string): Promise + getRanking(competition: string): Promise + getAthleteCount(athlete: string, competition: string): Promise +} \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Business/RunnerCompetition/RunnerBusiness.ts b/modulo5/case-estante-coe/src/Business/RunnerCompetition/RunnerBusiness.ts new file mode 100644 index 0000000..289beef --- /dev/null +++ b/modulo5/case-estante-coe/src/Business/RunnerCompetition/RunnerBusiness.ts @@ -0,0 +1,91 @@ +import { Competition, InputDTO, Progretion } from "../../Model/Competition"; +import { IdGenerator } from "../../Services/IdGeneretor"; +import { RunnerCompetitionRepository } from "./RunnerCompetitionRepository"; + + +export class RunnerCompetitionBusiness { + private idGenerator: IdGenerator; + private competitionData: RunnerCompetitionRepository; + + constructor( + competitionDataImplementation: RunnerCompetitionRepository + ) { + this.idGenerator = new IdGenerator + this.competitionData = competitionDataImplementation + } + + insertRunner = async (input: InputDTO): Promise => { + const { athlete, competition, unity, valor } = input + + if (!competition || !athlete || !valor || !unity) { + throw new Error("Preencha todos os campos") + } + + if (typeof competition !== "string" || typeof athlete !== "string" || typeof valor !== "string" || typeof unity !== "string") { + throw new Error("Preencha valores válidos") + } + + if (unity !== "s") { + throw new Error("unity inválida!") + } + + const verificaProgretioncompetition = await this.competitionData.getSituationByName(competition, Progretion.FINISH) + + if (verificaProgretioncompetition.length > 0) { + throw new Error("Competição finalizada.") + } + + const verificaCompetidor = await this.competitionData.getByAthlete(athlete) + + if (verificaCompetidor) { + const verificaQualcompetition = await this.competitionData.getCompetitionAndAthlete(competition) + + if (verificaQualcompetition === athlete) { + throw new Error(" 'athlete' já realizou essa prova") + } + + const id = this.idGenerator.generate() + + const progretion = Progretion.PROGRESS + + const novacompetition = new Competition( + id, + competition, + athlete, + valor, + unity, + progretion + ) + + await this.competitionData.insert(novacompetition) + } + } + + finishRun = async (input: string) => { + const competition = input + + const progretion = Progretion.FINISH + + const verificacompetition = await this.competitionData.getCompetitionByName(competition) + + if (!verificacompetition) { + throw new Error("Competição ou modalidade não existente.") + } + + await this.competitionData.updateSituation(progretion, competition) + } + + getRanking = async (input: string) => { + const competition = input + + const verificacompetition = await this.competitionData.getCompetitionByName(competition) + + if (!verificacompetition) { + throw new Error("Competição ou modalidade não existente") + } + + const resultado = await this.competitionData.getRanking(competition) + + return resultado + } +} \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Business/RunnerCompetition/RunnerCompetitionRepository.ts b/modulo5/case-estante-coe/src/Business/RunnerCompetition/RunnerCompetitionRepository.ts new file mode 100644 index 0000000..7981d44 --- /dev/null +++ b/modulo5/case-estante-coe/src/Business/RunnerCompetition/RunnerCompetitionRepository.ts @@ -0,0 +1,12 @@ +import { Competition, Progretion } from "../../Model/Competition"; + +export interface RunnerCompetitionRepository { + insert(competition: Competition): Promise + getByAthlete(athlete: string): Promise + getCompetitionByName(competition: string): Promise + updateSituation(situation: Progretion.FINISH, competition: string): Promise + getSituationByName(competition: string, situation: Progretion.FINISH): Promise + getCompetitionAndAthlete(competition: string): Promise + getRanking(competition: string): Promise + getAthleteCount(athlete: string, competition: string): Promise +} \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Controller/DartCompetitionController.ts b/modulo5/case-estante-coe/src/Controller/DartCompetitionController.ts new file mode 100644 index 0000000..c2f2bcf --- /dev/null +++ b/modulo5/case-estante-coe/src/Controller/DartCompetitionController.ts @@ -0,0 +1,64 @@ +import { Request, Response } from "express"; +import DartCompetitionBusiness from "../Business/DartCompetition/DartCompetitionBusiness"; +import { DartCompetitionDatabase } from "../Data/DartCompetitionDatabase"; + +import { InputDTO } from "../Model/Competition"; + + +export default class DartCompetitionController { + private dartCompetitionBusiness: DartCompetitionBusiness; + constructor( + ) { + this.dartCompetitionBusiness = new DartCompetitionBusiness(new DartCompetitionDatabase()) + } + + createDart = async (req: Request, res: Response) => { + const { competition, athlete, valor, unity } = req.body + + const input: InputDTO = { + competition, + athlete, + valor, + unity + } + + try { + await this.dartCompetitionBusiness.insertDart(input) + res.send({ message: `Atleta inscrito na competição ' ${competition}'` }) + + } catch (error: any) { + res.statusCode = 400 + let message = error.sqlMessage || error.message + res.send({ message }) + } + } + + finishDart = async (req: Request, res: Response) => { + const { competition } = req.body + + try { + await this.dartCompetitionBusiness.finishDart(competition) + res.send({ message: "Competição finalizada." }) + + } catch (error: any) { + res.statusCode = 400 + let message = error.sqlMessage || error.message + res.send({ message }) + } + } + + rankingDart = async (req: Request, res: Response) => { + const { competition } = req.body + + try { + const ranking = await this.dartCompetitionBusiness.getRanking(competition) + res.send({ message: "Resultado da competição", ranking }) + + } catch (error: any) { + res.statusCode = 400 + let message = error.sqlMessage || error.message + res.send({ message }) + } + } + +} \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Controller/RunnerController.ts b/modulo5/case-estante-coe/src/Controller/RunnerController.ts new file mode 100644 index 0000000..9754df2 --- /dev/null +++ b/modulo5/case-estante-coe/src/Controller/RunnerController.ts @@ -0,0 +1,62 @@ +import { RunnerCompetitionBusiness } from "../Business/RunnerCompetition/RunnerBusiness"; +import { InputDTO } from "../Model/Competition"; +import { Request, Response } from "express"; +import { RunnerDatabase } from "../Data/RunnerDatabase"; + +export class RunnerCompetitionController { + private runnerCompetitionBusiness: RunnerCompetitionBusiness; + constructor( + ) { + this.runnerCompetitionBusiness = new RunnerCompetitionBusiness(new RunnerDatabase()) + } + + createRunner = async (req: Request, res: Response) => { + const { competition, athlete, valor, unity } = req.body + + const input: InputDTO = { + competition, + athlete, + valor, + unity + } + + try { + await this.runnerCompetitionBusiness.insertRunner(input) + res.send({ message: "Atleta inscrito na competição" }) + + } catch (error: any) { + res.statusCode = 400 + let message = error.sqlMessage || error.message + res.send({ message }) + } + } + + finishRun = async (req: Request, res: Response) => { + const { competition } = req.body + + try { + await this.runnerCompetitionBusiness.finishRun(competition) + res.send({ message: "Competição finalizada." }) + + } catch (error: any) { + res.statusCode = 400 + let message = error.sqlMessage || error.message + res.send({ message }) + } + } + + rankingRun = async (req: Request, res: Response) => { + const { competition } = req.body + + try { + const ranking = await this.runnerCompetitionBusiness.getRanking(competition) + res.send({ message: "Resultado da competição", ranking }) + + } catch (error: any) { + res.statusCode = 400 + let message = error.sqlMessage || error.message + res.send({ message }) + } + } + +} \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Data/BaseDatabase.ts b/modulo5/case-estante-coe/src/Data/BaseDatabase.ts new file mode 100644 index 0000000..1d5f6bb --- /dev/null +++ b/modulo5/case-estante-coe/src/Data/BaseDatabase.ts @@ -0,0 +1,21 @@ +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, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_SCHEMA, + port: 3306, + multipleStatements: true + } + }) + public static async destroyConnection(): Promise { + await BaseDatabase.connection.destroy(); + } +} \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Data/DartCompetitionDatabase.ts b/modulo5/case-estante-coe/src/Data/DartCompetitionDatabase.ts new file mode 100644 index 0000000..373dc3f --- /dev/null +++ b/modulo5/case-estante-coe/src/Data/DartCompetitionDatabase.ts @@ -0,0 +1,111 @@ +import { DartCompetitionRepository } from '../Business/DartCompetition/DartCompetitionRepository'; +import { Competition, Progretion } from "../Model/Competition"; +import { BaseDatabase } from "./BaseDatabase"; + +export class DartCompetitionDatabase extends BaseDatabase implements DartCompetitionRepository { + + protected TABLE_NAME = "Dart_CoeCase" + + insert = async (competition: Competition) => { + try { + await BaseDatabase + .connection(this.TABLE_NAME) + .insert(competition) + + return competition + } catch (error: any) { + throw new Error("Erro ao criar atleta no banco de dados. Verifique se o atleta ja está inscrito ") + } + } + + getByAthlete = async (athlete: string) => { + try { + const result = await BaseDatabase.connection.raw(` + SELECT * FROM Dart_CoeCase WHERE athlete = '${athlete}'; + `) + + return result[0] + } catch (error: any) { + throw new Error("Atleta não encontrado") + } + } + + getCompetitionByName = async (competition: string) => { + try { + const result = await BaseDatabase + .connection(this.TABLE_NAME) + .select() + .where("competition", competition) + + return result[0] + } catch (error: any) { + throw new Error("Competição não encontrado.") + } + } + + getCompetitionAndAthlete = async (competition: string) => { + try { + const result = await BaseDatabase.connection.raw(` + SELECT athlete FROM FROM ${this.TABLE_NAME} WHERE competition = '${competition}'; + `) + + return result[0] + } catch (error: any) { + throw new Error("Erro ao buscar athlete por competição.") + } + } + + + getSituationByName = async (competition: string, situation: Progretion.FINISH) => { + try { + const result = await BaseDatabase.connection.raw(` + SELECT Progretion FROM ${this.TABLE_NAME} WHERE competition = '${competition}' and Progretion = '${situation}'; + `) + + return result[0] + } catch (error: any) { + throw new Error("'progress' não encontrado.") + } + } + + updateSituation = async (situation: Progretion.FINISH, competition: string) => { + try { + const result = await BaseDatabase.connection.raw(` + UPDATE ${this.TABLE_NAME} SET Progretion = '${situation}' WHERE competition = '${competition}'; + `) + + return result[0] + } catch (error: any) { + throw new Error("Erro ao atualizar competição. Verifique o nome e o progresso da competição.") + } + } + + getRanking = async (competition: string) => { + try { + const result = await BaseDatabase.connection.raw(` + SELECT competition, athlete, MAX(valor), unity FROM ${this.TABLE_NAME} + WHERE competition = '${competition}' + AND unity = "m" + GROUP BY athlete + ORDER BY MAX(valor) DESC; + `) + + return result[0] + } catch (error: any) { + throw new Error("Erro ao buscar ranking da competição no banco de dados.") + } + } + + getAthleteCount = async (athlete: string, competition: string) => { + try { + const result = await BaseDatabase.connection.raw(` + SELECT COUNT(athlete) as count FROM ${this.TABLE_NAME} WHERE athlete = '${athlete}' AND competition = '${competition}'; + `) + + return result[0][0].count + } catch (error: any) { + throw new Error("Erro ao buscar count do athlete no banco de dados.") + } + } + +} \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Data/RunnerDatabase.ts b/modulo5/case-estante-coe/src/Data/RunnerDatabase.ts new file mode 100644 index 0000000..65b9df9 --- /dev/null +++ b/modulo5/case-estante-coe/src/Data/RunnerDatabase.ts @@ -0,0 +1,108 @@ +import { Competition, Progretion } from "../Model/Competition"; +import { BaseDatabase } from "./BaseDatabase"; +import { RunnerCompetitionRepository } from '../Business/RunnerCompetition/RunnerCompetitionRepository'; + +export class RunnerDatabase extends BaseDatabase implements RunnerCompetitionRepository { + + protected TABLE_NAME = "Runner_CoeCase" + + insert = async (competition: Competition) => { + try { + + await BaseDatabase + .connection(this.TABLE_NAME) + .insert(competition) + + return competition + } catch (error: any) { + throw new Error("Erro ao criar atleta no banco de dados. Verifique se o atleta ja está inscrito ") + } + } + getByAthlete = async (athlete: string) => { + try { + const result = await BaseDatabase.connection.raw(` + SELECT * FROM Runner_CoeCase WHERE athlete = '${athlete}'; + `) + + return result[0] + } catch (error: any) { + throw new Error("Atleta não encontrado") + } + } + + getCompetitionByName = async (competition: string) => { + try { + const result = await BaseDatabase + .connection(this.TABLE_NAME) + .select() + .where("competition", competition) + + return result[0] + } catch (error: any) { + throw new Error("Competição não encontrado.") + } + } + + getCompetitionAndAthlete = async (competition: string) => { + try { + const result = await BaseDatabase.connection.raw(` + SELECT athlete FROM ${this.TABLE_NAME} WHERE competition = '${competition}'; + `) + + return result[0] + } catch (error: any) { + throw new Error("Erro ao buscar athlete por competição.") + } + } + + + getSituationByName = async (competition: string, progretion: Progretion.FINISH) => { + try { + const result = await BaseDatabase.connection.raw(` + SELECT Progretion FROM ${this.TABLE_NAME} WHERE competition = '${competition}' and Progretion = '${progretion}'; + `) + + return result[0] + } catch (error: any) { + throw new Error("'progress' não encontrado.") + } + } + + updateSituation = async (situation: Progretion.FINISH, competition: string) => { + try { + const result = await BaseDatabase.connection.raw(` + UPDATE ${this.TABLE_NAME} SET Progretion = '${situation}' WHERE competition = '${competition}'; + `) + + return result[0] + } catch (error: any) { + throw new Error("Erro ao atualizar competição. Verifique o nome e o progresso da competição.") + } + } + + getRanking = async (competition: string) => { + try { + const result = await BaseDatabase.connection.raw(` + SELECT * FROM ${this.TABLE_NAME} WHERE competition = '${competition}' ORDER BY valor ASC; + `) + + return result[0] + } catch (error: any) { + throw new Error("Erro ao buscar ranking da competição no banco de dados.") + } + } + + getAthleteCount = async (athlete: string, competition: string) => { + try { + const result = await BaseDatabase.connection.raw(` + SELECT COUNT(athlete) as count FROM ${this.TABLE_NAME} WHERE athlete = '${athlete}' AND competition = '${competition}'; + `) + + return result[0][0].count + } catch (error: any) { + throw new Error("Erro ao buscar count do atleta no banco de dados.") + } + } + + +} \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Model/Competition.ts b/modulo5/case-estante-coe/src/Model/Competition.ts new file mode 100644 index 0000000..cb09d8b --- /dev/null +++ b/modulo5/case-estante-coe/src/Model/Competition.ts @@ -0,0 +1,59 @@ +export class Competition { + + constructor( + private id: string, + private competition: string, + private athlete: string, + private valor: string, + private unity: "s" | "m", + private progretion: Progretion + + ) { } + + public getId() { + return this.id + } + + public getCompetition() { + return this.competition + } + + public getAthlete() { + return this.athlete + } + + public getValue() { + return this.valor + } + + public getUnity() { + return this.unity + } + + public getProgretion() { + return this.progretion + } + + static toCompetitionModel(data: any): Competition { + return new Competition(data.id, data.competition, data.athlete, data.valor, data.unity, data.progretion) + } + +} + +export type InputDTO = { + competition: string; + athlete: string; + valor: string; + unity: string; +} + +export enum Progretion { + PROGRESS = "IN PROGRESS", + FINISH = "FINISH" +} + +export type InputcompetitionDTO = { + competition: string +} + + diff --git a/modulo5/case-estante-coe/src/Routes/dartRouter.ts b/modulo5/case-estante-coe/src/Routes/dartRouter.ts new file mode 100644 index 0000000..847082a --- /dev/null +++ b/modulo5/case-estante-coe/src/Routes/dartRouter.ts @@ -0,0 +1,9 @@ +import express from "express"; +import DartCompetitionController from "../Controller/DartCompetitionController"; + +export const dartRouter = express.Router(); +const dartController = new DartCompetitionController(); + +dartRouter.post("/create", dartController.createDart) +dartRouter.post("/finish", dartController.finishDart) +dartRouter.get("/ranking", dartController.rankingDart) \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Routes/runnerRouter.ts b/modulo5/case-estante-coe/src/Routes/runnerRouter.ts new file mode 100644 index 0000000..c4f2e7d --- /dev/null +++ b/modulo5/case-estante-coe/src/Routes/runnerRouter.ts @@ -0,0 +1,9 @@ +import express from "express"; +import { RunnerCompetitionController } from "../Controller/RunnerController"; + +export const runnerRouter = express.Router(); +const runnerController = new RunnerCompetitionController(); + +runnerRouter.post("/create", runnerController.createRunner) +runnerRouter.post("/finish", runnerController.finishRun) +runnerRouter.get("/ranking", runnerController.rankingRun) \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Services/Authenticator.ts b/modulo5/case-estante-coe/src/Services/Authenticator.ts new file mode 100644 index 0000000..4a859a0 --- /dev/null +++ b/modulo5/case-estante-coe/src/Services/Authenticator.ts @@ -0,0 +1,32 @@ +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, + } + ); + 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; + } +} + +export interface AuthenticationData { + id: string; + role?: string; +} \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Services/HashManager.ts b/modulo5/case-estante-coe/src/Services/HashManager.ts new file mode 100644 index 0000000..45704fb --- /dev/null +++ b/modulo5/case-estante-coe/src/Services/HashManager.ts @@ -0,0 +1,17 @@ +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); + } + +} \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/Services/IdGeneretor.ts b/modulo5/case-estante-coe/src/Services/IdGeneretor.ts new file mode 100644 index 0000000..3392265 --- /dev/null +++ b/modulo5/case-estante-coe/src/Services/IdGeneretor.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/case-estante-coe/src/Services/TokenGenerator.ts b/modulo5/case-estante-coe/src/Services/TokenGenerator.ts new file mode 100644 index 0000000..1e212ba --- /dev/null +++ b/modulo5/case-estante-coe/src/Services/TokenGenerator.ts @@ -0,0 +1,34 @@ +import * as jwt from "jsonwebtoken"; +import dotenv from "dotenv"; + +dotenv.config(); + +export class TokenGenerator { + private static expiresIn: number = 1200; + + public generate = (input: AuthenticationData): string => { + const newToken = jwt.sign( + { + id: input.id, + role: input.role, + }, + process.env.JWT_KEY as string, + { + expiresIn: TokenGenerator.expiresIn, + } + ); + return newToken; + }; + + public verify(token: string) { + const payload = jwt.verify(token, process.env.JWT_KEY as string) as any; + const result = { id: payload.id, role: payload.role }; + return result; + } +} + +export interface AuthenticationData { + id: string; + role: string; +} + diff --git a/modulo5/case-estante-coe/src/index.ts b/modulo5/case-estante-coe/src/index.ts new file mode 100644 index 0000000..ed80c31 --- /dev/null +++ b/modulo5/case-estante-coe/src/index.ts @@ -0,0 +1,24 @@ +import dotenv from "dotenv"; +import { AddressInfo } from "net"; +import express from "express"; +import { runnerRouter } from "./Routes/runnerRouter"; +import { dartRouter } from "./Routes/dartRouter"; +import cors from "cors" + +dotenv.config(); +const app = express(); + +app.use(express.json()); +app.use(cors()) + +app.use("/runner", runnerRouter); +app.use("/dart", dartRouter); + +const server = app.listen(3003, () => { + 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.`); + } +}); \ No newline at end of file diff --git a/modulo5/case-estante-coe/src/tables/tablessql b/modulo5/case-estante-coe/src/tables/tablessql new file mode 100644 index 0000000..6904084 --- /dev/null +++ b/modulo5/case-estante-coe/src/tables/tablessql @@ -0,0 +1,18 @@ + Runner_CoeCase ( + id VARCHAR(255) PRIMARY KEY, + competition VARCHAR(255) , + athlete VARCHAR(255) UNIQUE NOT NULL, + valor VARCHAR(255) NOT NULL, + unity VARCHAR(255) NOT NULL , + Progretion VARCHAR(255) NOT NULL + ); + + + Dart_CoeCase ( + id VARCHAR(255) PRIMARY KEY, + competition VARCHAR(255) , + athlete VARCHAR(255) UNIQUE NOT NULL, + valor VARCHAR(255) NOT NULL, + unity VARCHAR(255) NOT NULL , + Progretion VARCHAR(255) NOT NULL + ); \ No newline at end of file diff --git a/modulo5/case-estante-coe/tsconfig.json b/modulo5/case-estante-coe/tsconfig.json new file mode 100644 index 0000000..73a1d3d --- /dev/null +++ b/modulo5/case-estante-coe/tsconfig.json @@ -0,0 +1,101 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Projects */ + // "incremental": true, /* Enable incremental compilation */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ + // "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. */ + + /* Modules */ + "module": "commonjs", /* Specify what module code is generated. */ + "rootDir": "./", /* 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. */ + // "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, /* Type catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when a 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, /* Include 'undefined' in index signature results */ + // "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. */ + } + } \ No newline at end of file diff --git a/modulo5/case/.gitignore b/modulo5/case/.gitignore new file mode 100644 index 0000000..9ee73e0 --- /dev/null +++ b/modulo5/case/.gitignore @@ -0,0 +1,4 @@ +.env +node_modules +package-lock.json +build \ No newline at end of file diff --git a/modulo5/case/Migrations.ts b/modulo5/case/Migrations.ts new file mode 100644 index 0000000..30d4d19 --- /dev/null +++ b/modulo5/case/Migrations.ts @@ -0,0 +1,36 @@ +import {BaseDatabase} from "./src/data/BaseDatabase" + +export class Migrations extends BaseDatabase { + + migrations = async ( + + ) => { + await this.connection + .raw(` + CREATE TABLE IF NOT EXISTS Banda_Lama ( + id VARCHAR(255) PRIMARY KEY, + name VARCHAR(255) UNIQUE NOT NULL, + music_genre VARCHAR(255) NOT NULL, + responsible VARCHAR(255) UNIQUE NOT NULL + ); + CREATE TABLE IF NOT EXISTS Show_Lama ( + id VARCHAR(255) PRIMARY KEY, + week_day VARCHAR(255) NOT NULL, + start_time INT NOT NULL, + end_time INT NOT NULL, + band_id VARCHAR(255) NOT NULL, + FOREIGN KEY(band_id) REFERENCES NOME_TABELA_BANDAS(id) + ); + CREATE TABLE IF NOT EXISTS User_Lama ( + id VARCHAR(255) PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + password VARCHAR(255) NOT NULL, + role VARCHAR(255) NOT NULL DEFAULT "NORMAL" + ); + `) + .then(console.log) + .catch(console.log) +} + connection: any +} \ No newline at end of file diff --git a/modulo5/case/README.md b/modulo5/case/README.md new file mode 100644 index 0000000..8019076 --- /dev/null +++ b/modulo5/case/README.md @@ -0,0 +1,41 @@ +# Labenu Music Awards + +### Documentação postman: https://documenter.getpostman.com/view/19296396/UyxhonZD + +### Descrição: + +*Labenu Musical Awards*, um festival com várias bandas famosas para a formatura da turma e, no final, pode eleger a banda que mais gostaram! Permite o gerenciamento completo desses shows. +Utilizamos o LiveShare no VsCode para fazer todo o projeto. +Para verifica-lo deve-se baixar o arquivo atraves do git clone e instalar as dependencias atraves do npm i + +### Funcionalidades: + +1. Cadastro +2. Login +3. Endpoint de registrar banda +4. Endpoint de visualização de detalhes sobre a banda +5. Endpoint de adicionar um show a um dia +6. Endpoint de pegar todos os shows de uma data + +## 🤝 Colaboradores + + + + + + + + Grazielle Martins + + + + + + + + Victor Xavier + + + + + diff --git a/modulo5/case/jest.config.js b/modulo5/case/jest.config.js new file mode 100644 index 0000000..969c9b1 --- /dev/null +++ b/modulo5/case/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + roots: ["/tests"], + transform: { + "^.+\\.tsx?$": "ts-jest", + }, + testRegex: "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", + moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"], + }; \ No newline at end of file diff --git a/modulo5/case/package.json b/modulo5/case/package.json new file mode 100644 index 0000000..be82a07 --- /dev/null +++ b/modulo5/case/package.json @@ -0,0 +1,46 @@ +{ + "name": "vaughan-lama1", + "version": "1.0.0", + "description": "Como você deve saber muito bem, o nosso querido chefinho Astrodev é uma pessoa com Networking incrível e ele conhece vários artistas estrelados. Além disso, ele também é um grande ~~megalomaníaco~~ visionário e está planejando fazer um grande evento: o **LAMA**, *Labenu Musical Awards*, um festival com várias bandas famosas para a formatura da sua turma e, no final, vocês podem eleger a banda que mais gostaram! Entretanto, na opinião dele, vocês só serão merecedores se entregarem um sistema impecável que permita o gerenciamento completo desses shows.", + "main": "index.js", + "scripts": { + "start:dev": "ts-node-dev ./src/index.ts", + "start": "tsc && node ./build/index.js", + "build": "tsc", + "dev": "ts-node-dev ./src/index.ts" + + }, + "repository": { + "type": "git", + "url": "git+https://github.com/future4code/vaughan-Victor-Xavier.git" + }, + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/future4code/vaughan-Victor-Xavier/issues" + }, + "homepage": "https://github.com/future4code/vaughan-Victor-Xavier#readme", + "dependencies": { + "@types/express": "^4.17.13", + "@types/jest": "^27.5.1", + "@types/jsonwebtoken": "^8.5.8", + "@types/knex": "^0.16.1", + "@types/node": "^17.0.32", + "bcryptjs": "^2.4.3", + "dotenv": "^16.0.1", + "express": "^4.18.1", + "jest": "^28.1.0", + "jsonwebtoken": "^8.5.1", + "knex": "^2.0.0", + "mysql": "^2.18.1", + "ts-jest": "^28.0.2", + "ts-node": "^10.7.0", + "ts-node-dev": "^1.1.8", + "typescript": "^4.6.4", + "uuid": "^8.3.2" + }, + "devDependencies": { + "@types/bcryptjs": "^2.4.2", + "@types/uuid": "^8.3.4" + } +} diff --git a/modulo5/case/src/business/GetDetailsBusiness.ts b/modulo5/case/src/business/GetDetailsBusiness.ts new file mode 100644 index 0000000..d6fc59a --- /dev/null +++ b/modulo5/case/src/business/GetDetailsBusiness.ts @@ -0,0 +1,24 @@ +import { BandDatabase } from "../data/BandDatabase"; +import { Authenticator } from "../services/Authenticator"; +import { HashManager } from "../services/HashManager"; + + +const bandDB = new BandDatabase(); + + +export class GetRunnerBusiness { + getBand = async (id: string) => { + try { + + const band = await bandDB.getBand(id) + if (!band) { + throw new Error("ID inválido") + } + return band + + + } catch (error: any) { + throw new Error(error.message); + } + } +} \ No newline at end of file diff --git a/modulo5/case/src/business/GetShowBusiness.ts b/modulo5/case/src/business/GetShowBusiness.ts new file mode 100644 index 0000000..2b50aa8 --- /dev/null +++ b/modulo5/case/src/business/GetShowBusiness.ts @@ -0,0 +1,25 @@ + +import { ShowDatabase } from "../data/ShowDatabase"; + + +const showDB = new ShowDatabase(); + + +export class GetShowBusiness { + getShow = async (show: string) => { + try { + + const getshow = await showDB.getShow(show) + + + if (getshow === undefined) { + throw new Error("Data inválida") + } + return getshow + + + } catch (error: any) { + throw new Error(error.message); + } + } +} \ No newline at end of file diff --git a/modulo5/case/src/business/RunnerBusiness.ts b/modulo5/case/src/business/RunnerBusiness.ts new file mode 100644 index 0000000..e2031a8 --- /dev/null +++ b/modulo5/case/src/business/RunnerBusiness.ts @@ -0,0 +1,42 @@ +import { IdGenerator } from "../services/IdGenerator" +import { RunnerInputDTO } from "../model/Runner" +import { HashManager } from "../services/HashManager"; +import { BandDatabase } from "../data/BandDatabase"; +import { Authenticator } from "../services/Authenticator"; +import { RunnerDatabase } from "../data/RunnerDatabase"; +import { TokenGenerator } from "../services/TokenGenerator"; + + +export class RunnerBusiness { + constructor( + private runnerDatabase: RunnerDatabase, + private idGenerator: IdGenerator, + private tokenGenerator: TokenGenerator, + private hashGenerator: HashManager + + ) { } + + async createRunner(band: RunnerInputDTO, token: string) { + + + + + const idGenerator = new IdGenerator(); + const id = idGenerator.generate(); + + const authenticator = new Authenticator(); + const tokenData = authenticator.getData(token); + + if (tokenData.role === "NORMAL") { + throw new Error("Role invalido"); + } + const bandDatabase = new BandDatabase(); + await bandDatabase.createBand(id, band.name, band.music_genre, band.responsible); + + + return tokenData; + } + + + +} \ No newline at end of file diff --git a/modulo5/case/src/business/ShowBusiness.ts b/modulo5/case/src/business/ShowBusiness.ts new file mode 100644 index 0000000..9df6a5e --- /dev/null +++ b/modulo5/case/src/business/ShowBusiness.ts @@ -0,0 +1,59 @@ +import { ShowDatabase } from "../data/ShowDatabase"; +import { ShowInputDTO } from "../model/Show"; +import { showRouter } from "../routes/showRouter"; +import { Authenticator } from "../services/Authenticator"; +import { HashManager } from "../services/HashManager"; +import { IdGenerator } from "../services/IdGenerator"; +import { TokenGenerator } from "../services/TokenGenerator"; + +export class ShowBusiness { + constructor( + private showDatabase: ShowDatabase, + private idGenerator: IdGenerator, + private tokenGenerator: TokenGenerator, + private hashGenerator: HashManager + + ) { } + + + + public async createShow(show: ShowInputDTO) { + + const arrayDias = ["SEXTA", "SABADO", "DOMINGO"] + + if (!arrayDias.includes(show.week_day)) { + throw new Error("Necessita inserir o dia entre sexta e domingo"); + } + + if (show.start_time < 8 || show.start_time > 23) { + throw new Error("Horário indisponível"); + } + if (!show.start_time) { + throw new Error("Precisa de um horário"); + } + if (!show.band_id) { + throw new Error("Id da banda inválido"); + } + if (!show.end_time) { + throw new Error("Horário indisponível"); + } + + const idGenerator = new IdGenerator(); + const id = idGenerator.generate(); + + const authenticator = new Authenticator(); + const tokenData = authenticator.generateToken({ id }); + + const showDatabase = new ShowDatabase(); + const newshow = showDatabase.findByDayAndHours(show.week_day, show.start_time); + if ( (await newshow).length > 0) { + throw new Error("Dia ou Horário já utilizado") + } + + + await showDatabase.createShow(id, show.week_day, show.start_time, show.end_time, show.band_id) + + + return tokenData; + } +} diff --git a/modulo5/case/src/business/UserBusiness.ts b/modulo5/case/src/business/UserBusiness.ts new file mode 100644 index 0000000..617ec2e --- /dev/null +++ b/modulo5/case/src/business/UserBusiness.ts @@ -0,0 +1,75 @@ +// import { UserInputDTO, LoginInputDTO } from "../model/Athlete"; +// import { RunnerDatabase } from "../data/RunnerDatabase"; +// import { IdGenerator } from "../services/IdGenerator"; +// import { HashManager } from "../services/HashManager"; +// import { Authenticator } from "../services/Authenticator"; +// import { TokenGenerator } from "../services/TokenGenerator"; + +// export class UserBusiness { + +// constructor( +// private runnerDatabase: RunnerDatabase, +// private idGenerator:IdGenerator, +// private tokenGenerator: TokenGenerator, +// private hashGenerator: HashManager + +// ){} + +// public async createUser(user: UserInputDTO) { + +// // if (!name) { +// // throw new Error("Preencha o campo 'name'"); +// // } +// // if (!email) { +// // throw new Error("Preencha o campo 'email'"); +// // } +// // if (!password) { +// // throw new Error("Preencha o campo 'password'"); +// // } +// // if (!role) { +// // throw new Error("Preencha o campo 'role'"); +// // } + +// if (user.password.length < 6) { +// throw new Error("A senha deve conter no minimo 6 caracteres") +// } +// if (user.email.indexOf("@") === -1) { +// throw new Error("Email invalido"); +// } + + + +// const idGenerator = new IdGenerator(); +// const id = idGenerator.generate(); + +// const hashManager = new HashManager(); +// const hashPassword = await hashManager.hash(user.password); + +// const userDatabase = new RunnerDatabase(); +// await userDatabase.createRunner(id, user.email, user.name, hashPassword, user.role); + +// const authenticator = new Authenticator(); +// const accessToken = authenticator.generateToken({ id, role: user.role }); + +// return accessToken; +// } + +// public 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 Error("Invalid Password!"); +// } + +// return accessToken; +// } +// } +// export default UserBusiness; \ No newline at end of file diff --git a/modulo5/case/src/controller/GetDetailsController.ts b/modulo5/case/src/controller/GetDetailsController.ts new file mode 100644 index 0000000..2c6fca6 --- /dev/null +++ b/modulo5/case/src/controller/GetDetailsController.ts @@ -0,0 +1,24 @@ + +import { Request, Response } from 'express'; +import { GetRunnerBusiness} from '../business/GetDetailsBusiness'; + +const getbandBusiness = new GetRunnerBusiness() +export class GetRunnerController { + getBand = async (req: Request, res: Response) => { + + + try { + + const id = req.params.id + + + const band = await getbandBusiness.getBand(id); + + res.status(200).send({ band }); + + } catch (error: any) { + res.status(400).send({ error: error.message }); + } + + } +} \ No newline at end of file diff --git a/modulo5/case/src/controller/GetShowController.ts b/modulo5/case/src/controller/GetShowController.ts new file mode 100644 index 0000000..6ebb051 --- /dev/null +++ b/modulo5/case/src/controller/GetShowController.ts @@ -0,0 +1,24 @@ + +import { Request, Response } from 'express'; +import { GetShowBusiness } from '../business/GetShowBusiness'; + +const getshowBusiness = new GetShowBusiness() +export class GetShowController { + getShow = async (req: Request, res: Response) => { + + + try { + + const dia = req.query.dia as string + + + const show = await getshowBusiness.getShow(dia); + + res.status(200).send({ show: show }); + + } catch (error: any) { + res.status(400).send({ error: error.message }); + } + + } +} \ No newline at end of file diff --git a/modulo5/case/src/controller/RunnerController.ts b/modulo5/case/src/controller/RunnerController.ts new file mode 100644 index 0000000..f923ab1 --- /dev/null +++ b/modulo5/case/src/controller/RunnerController.ts @@ -0,0 +1,40 @@ +import { RunnerBusiness } from './../business/RunnerBusiness'; +import { Request, Response } from "express"; +import { RunnerDatabase } from "../data/RunnerDatabase"; +import { RunnerInputDTO } from "../model/Runner"; +import { HashManager } from "../services/HashManager"; +import { IdGenerator } from "../services/IdGenerator"; +import { TokenGenerator } from "../services/TokenGenerator"; +import { BaseDatabase } from '../data/BaseDatabase'; + + +const runnerController = new RunnerBusiness( + new RunnerDatabase(), + new IdGenerator(), + new TokenGenerator(), + new HashManager(), + +) +export class RunnerController { + async signupRunner(req: Request, res: Response) { + try { + const token = req.headers.authorization as string + const input: RunnerInputDTO = { + name: req.body.name, + music_genre: req.body.music_genre, + responsible: req.body.responsible + } + + + const runner = await runnerController.createRunner(input ,token) + + res.status(200).send({message: "Banda cadastrada!", token: token }); + + } catch (error: any) { + res.status(400).send({ error: error.message }); + } + + await BaseDatabase.destroyConnection(); + } + +} \ No newline at end of file diff --git a/modulo5/case/src/controller/ShowController.ts b/modulo5/case/src/controller/ShowController.ts new file mode 100644 index 0000000..b17d9b3 --- /dev/null +++ b/modulo5/case/src/controller/ShowController.ts @@ -0,0 +1,40 @@ +import { Request, Response } from "express" +import { ShowInputDTO } from "../model/Show" +import { BaseError } from "../error/BaseError" +import { ShowBusiness } from "../business/ShowBusiness" +import { IdGenerator } from "../services/IdGenerator" +import {TokenGenerator} from "../services/TokenGenerator" +import { HashManager } from "../services/HashManager" +import { ShowDatabase } from "../data/ShowDatabase" + + +const showBusiness = new ShowBusiness( + + new ShowDatabase(), + new IdGenerator(), + new TokenGenerator(), + new HashManager(), + +) +export class ShowController { + async create(req: Request, res: Response) { + try { + + const input: ShowInputDTO = { + week_day: req.body.week_day, + start_time: req.body.start_time, + end_time: req.body.end_time, + band_id: req.body.band_id + } + + const result = await showBusiness.createShow(input) + res.send({ menssage: "Show Criado" }) + + + } catch (error) { + const err = error as BaseError + res.status(400).send({ error: err.message }) + + } + } +} \ No newline at end of file diff --git a/modulo5/case/src/controller/UserController.ts b/modulo5/case/src/controller/UserController.ts new file mode 100644 index 0000000..b3c769d --- /dev/null +++ b/modulo5/case/src/controller/UserController.ts @@ -0,0 +1,63 @@ +// import { Request, Response } from "express"; +// import { UserInputDTO, LoginInputDTO } from "../model/Athlete"; + +// import {BaseDatabase} from "../data/BaseDatabase"; +// import { IdGenerator } from "../services/IdGenerator"; +// import { HashManager } from "../services/HashManager"; +// import { Authenticator } from "../services/Authenticator"; +// import { RunnerDatabase } from "../data/RunnerDatabase"; +// import { TokenGenerator } from "../services/TokenGenerator"; + +// const runnerBusiness = new RunnerBusiness( +// new RunnerDatabase(), +// new IdGenerator(), +// new TokenGenerator(), +// new HashManager(), + +// ) +// export class RunnerController { +// async signup(req: Request, res: Response) { +// try { + +// const input: UserInputDTO = { +// email: req.body.email, +// name: req.body.name, +// password: req.body.password, +// role: req.body.role +// } + + +// const token = await runnerBusiness.createUser(input); + +// res.status(200).send({message: "Usuário cadastrado!", token: token }); + +// } catch (error: any) { +// res.status(400).send({ error: error.message }); +// } + +// await BaseDatabase.destroyConnection(); +// } + +// async login(req: Request, res: Response) { + +// try { + +// const loginData: LoginInputDTO = { +// email: req.body.email, +// password: req.body.password +// }; + + +// const token = await runnerBusiness.getUserByEmail(loginData); + +// res.status(200).send({ token }); + +// } catch (error: any) { +// res.status(400).send({ error: error.message }); +// } + +// await BaseDatabase.destroyConnection(); +// } + +// } +// export default new UserController() \ No newline at end of file diff --git a/modulo5/case/src/data/BandDatabase.ts b/modulo5/case/src/data/BandDatabase.ts new file mode 100644 index 0000000..af86ff7 --- /dev/null +++ b/modulo5/case/src/data/BandDatabase.ts @@ -0,0 +1,52 @@ + +import { Athlete } from "../model/Athlete"; +import { BaseDatabase } from "./BaseDatabase"; +import { Band } from "../model/Runner"; + +export class BandDatabase extends BaseDatabase { + + private static TABLE_NAME = "Banda_Lama"; + + public async createBand( + id: string, + name: string, + music_genre: string, + responsible: string, + + ): Promise { + try { + await BaseDatabase.connection() + .insert({ + id, + name, + music_genre, + responsible + }) + .into(BandDatabase.TABLE_NAME); + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + } + + public async getBandByEmail(email: string): Promise { + const result = await BaseDatabase.connection() + .select("*") + .from(BandDatabase.TABLE_NAME) + .where({ email }); + + return Band.toUserModel(result[0]); + } + + getBand = async ( + id: string + ) => { + const post = await BaseDatabase.connection('Banda_Lama').select( + "id", + "name", + "music_genre", + "responsible" + ).where({ id }) + return post[0] + } + +} \ No newline at end of file diff --git a/modulo5/case/src/data/BaseDatabase.ts b/modulo5/case/src/data/BaseDatabase.ts new file mode 100644 index 0000000..1d5f6bb --- /dev/null +++ b/modulo5/case/src/data/BaseDatabase.ts @@ -0,0 +1,21 @@ +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, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_SCHEMA, + port: 3306, + multipleStatements: true + } + }) + public static async destroyConnection(): Promise { + await BaseDatabase.connection.destroy(); + } +} \ No newline at end of file diff --git a/modulo5/case/src/data/RunnerDatabase.ts b/modulo5/case/src/data/RunnerDatabase.ts new file mode 100644 index 0000000..ac50d1b --- /dev/null +++ b/modulo5/case/src/data/RunnerDatabase.ts @@ -0,0 +1,42 @@ + +import { Athlete } from "../model/Athlete"; +import { BaseDatabase } from "./BaseDatabase"; + +export class RunnerDatabase extends BaseDatabase { + + private static TABLE_NAME = "Athlete_CoeCase "; + + public async createAthlete( + id: string, + competition: string, + athlete: string, + value: string, + unity: string, + role: string + ): Promise { + try { + await BaseDatabase.connection() + .insert({ + id, + competition, + athlete, + value, + unity, + role + }) + .into(RunnerDatabase.TABLE_NAME); + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + } + + public async getUserBycompetition(competition: string): Promise { + const result = await BaseDatabase.connection() + .select("*") + .from(RunnerDatabase.TABLE_NAME) + .where({ competition }); + + return Athlete.toUserModel(result[0]); + } + +} \ No newline at end of file diff --git a/modulo5/case/src/data/ShowDatabase.ts b/modulo5/case/src/data/ShowDatabase.ts new file mode 100644 index 0000000..10a12a2 --- /dev/null +++ b/modulo5/case/src/data/ShowDatabase.ts @@ -0,0 +1,54 @@ +import { Band } from "../model/Runner"; +import { Show } from "../model/Show"; +import { BaseDatabase } from "./BaseDatabase"; + +export class ShowDatabase extends BaseDatabase { + + private static TABLE_NAME = "Show_Lama" + private static TABLE_SHOWS_BAND = "Banda_Lama" + public async createShow( + id: string, + week_day: string, + start_time: number, + end_time: number, + band_id: string + ): Promise { + try { + await BaseDatabase.connection() + .insert({ + id, + week_day, + start_time, + end_time, + band_id + }) + .into(ShowDatabase.TABLE_NAME); + } catch (error: any) { + throw new Error(error.sqlMessage || error.message); + } + } + + public async findByDayAndHours(week_day: string, start_time: number): Promise { + + const shows = await BaseDatabase.connection(ShowDatabase.TABLE_NAME) + .select('*') + .where({ week_day: week_day, start_time: start_time }) + + return shows.map((show) => Show.toShowModel(show)); + } + public getShow = async ( + week_day: string + ) => { + const show = await BaseDatabase.connection('Show_Lama').select( + "Banda_Lama.name", "Banda_Lama.music_genre" + ) + .from(ShowDatabase.TABLE_NAME) + .join(ShowDatabase.TABLE_SHOWS_BAND, "Banda_Lama.id", "Show_Lama.band_id" ) + .orderBy("Show_Lama.start_time") + .where( "week_day", `${week_day}`) + + return show[0] + } + + +} \ No newline at end of file diff --git a/modulo5/case/src/error/BaseError.ts b/modulo5/case/src/error/BaseError.ts new file mode 100644 index 0000000..2974de7 --- /dev/null +++ b/modulo5/case/src/error/BaseError.ts @@ -0,0 +1,5 @@ +export abstract class BaseError extends Error { + constructor(message: string, public code: number) { + super(message); + } + } \ No newline at end of file diff --git a/modulo5/case/src/index.ts b/modulo5/case/src/index.ts new file mode 100644 index 0000000..7ebcf8c --- /dev/null +++ b/modulo5/case/src/index.ts @@ -0,0 +1,23 @@ +import dotenv from "dotenv"; +import {AddressInfo} from "net"; +import express from "express"; +import { userRouter } from "./routes/userRouter"; +import { bandRouter } from "./routes/runnerRouter"; +import { showRouter } from "./routes/showRouter"; +dotenv.config(); +const app = express(); + +app.use(express.json()); + +app.use("/user", userRouter); +app.use("/band", bandRouter); +app.use("/show", showRouter); + +const server = app.listen(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.`); + } + }); \ No newline at end of file diff --git a/modulo5/case/src/model/Athlete.ts b/modulo5/case/src/model/Athlete.ts new file mode 100644 index 0000000..6bc3517 --- /dev/null +++ b/modulo5/case/src/model/Athlete.ts @@ -0,0 +1,90 @@ +export class Athlete{ + constructor( + private id: string, + private competition: string, + private athlete: string, + private value: string, + private unity: string, + private role: AthleteRole + ){} + + getId(){ + return this.id; + } + + getName(){ + return this.competition + } + + getEmail(){ + return this.athlete; + } + + getPassword(){ + return this.value; + } + getUnity(){ + return this.unity; + } + + getRole(){ + return this.role; + } + + setId(id: string){ + this.id = id; + } + + setName(competition: string){ + this.competition = competition; + } + + setEmail(athlete: string){ + this.athlete = athlete; + } + + setPassword(value: string){ + this.value = value; + } + setUnity(unity: string){ + this.unity = unity; + } + + setRole(role: AthleteRole){ + this.role = role; + } + + static stringToAthleteRole(input: string): AthleteRole{ + switch (input) { + case "RUNNER": + return AthleteRole.RUNNER; + case "DART": + return AthleteRole.DART; + default: + throw new Error("Invalid user role"); + } + } + + static toUserModel(athlete: any): Athlete { + return new Athlete(athlete.id, athlete.competition, athlete.athlete, athlete.value,athlete.unity, athlete.stringToAthleteRole(athlete.role)); + } + + +} + +export interface UserInputDTO{ + email: string; + password: string; + name: string; + role: string; +} + +export interface LoginInputDTO{ + email: string; + password: string; +} + +export enum AthleteRole{ + RUNNER = "RUNNER", + DART = "DART" +} \ No newline at end of file diff --git a/modulo5/case/src/model/Runner.ts b/modulo5/case/src/model/Runner.ts new file mode 100644 index 0000000..b2efc91 --- /dev/null +++ b/modulo5/case/src/model/Runner.ts @@ -0,0 +1,53 @@ +export class Band { + constructor( + private id: string, + private name: string, + private music_genre: string, + private responsible: string + + ) { } + + getId() { + return this.id + } + + getMusicGender() { + return this.music_genre + } + + getName() { + return this.name + } + + getResponsible() { + return this.responsible + + } + + setId(id: string) { + this.id = id + } + + setName(name: string) { + this.name = name + } + + setMusicGenre(music_genre: string) { + this.music_genre = music_genre + } + + setResponsible(responsible: string) { + this.responsible = responsible + } + + static toUserModel(band: any): Band { + return new Band(band.id, band.name, band.music_genre, band.responsible); + } +} + +export interface RunnerInputDTO{ + name: string; + music_genre: string; + responsible: string; +} + diff --git a/modulo5/case/src/model/Show.ts b/modulo5/case/src/model/Show.ts new file mode 100644 index 0000000..f587a3c --- /dev/null +++ b/modulo5/case/src/model/Show.ts @@ -0,0 +1,49 @@ + +export interface ShowInputDTO { + + week_day: WeekDay, + start_time: number, + end_time: number, + band_id: string + + +} + +export enum WeekDay { + SEXTA = "SEXTA", + SABADO = "SABADO", + DOMINGO = "DOMINGO" +} + + +export class Show { + constructor( + + private id: string, + private week_day: string, + private start_time: number, + private end_time: number, + private band_id: string + + ) { } + getId() { + return this.id + } + + getWeekDay() { + return this.week_day + } + getStartTime() { + return this.start_time + } + + getEndTime() { + return this.end_time + } + getBandId() { + return this.band_id + } + static toShowModel(data: any): Show { + return new Show(data.id, data.week_day, data.start_time, data.end_time, data.band_id); + }; +} diff --git a/modulo5/case/src/routes/runnerRouter.ts b/modulo5/case/src/routes/runnerRouter.ts new file mode 100644 index 0000000..7c05554 --- /dev/null +++ b/modulo5/case/src/routes/runnerRouter.ts @@ -0,0 +1,12 @@ +import { RunnerController } from './../controller/RunnerController'; +import express from "express"; +import { GetRunnerController } from "../controller/GetDetailsController"; + + +export const bandRouter = express.Router(); + +const runnerController = new RunnerController(); +const runnerController2 = new GetRunnerController(); + +bandRouter.post("/signup/runner", runnerController.signupRunner); +bandRouter.get("/details/:id", runnerController2.getBand); \ No newline at end of file diff --git a/modulo5/case/src/routes/showRouter.ts b/modulo5/case/src/routes/showRouter.ts new file mode 100644 index 0000000..4833dc2 --- /dev/null +++ b/modulo5/case/src/routes/showRouter.ts @@ -0,0 +1,12 @@ +import express from "express"; +import { GetShowController } from "../controller/GetShowController"; +import { ShowController } from "../controller/ShowController"; + + +export const showRouter = express.Router(); + +const showController = new ShowController(); +const getshowController = new GetShowController(); + +showRouter.post("/signup", showController.create); +showRouter.get("/details", getshowController.getShow); \ No newline at end of file diff --git a/modulo5/case/src/routes/userRouter.ts b/modulo5/case/src/routes/userRouter.ts new file mode 100644 index 0000000..ccd2b7b --- /dev/null +++ b/modulo5/case/src/routes/userRouter.ts @@ -0,0 +1,10 @@ +import express from "express"; +import { RunnerController } from "../controller/RunnerController"; + + +export const userRouter = express.Router(); + +const userController = new RunnerController(); + +userRouter.post("/signup", userController.signupRunner); +// userRouter.post("/login", userController.login); \ No newline at end of file diff --git a/modulo5/case/src/services/Authenticator.ts b/modulo5/case/src/services/Authenticator.ts new file mode 100644 index 0000000..4a859a0 --- /dev/null +++ b/modulo5/case/src/services/Authenticator.ts @@ -0,0 +1,32 @@ +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, + } + ); + 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; + } +} + +export interface AuthenticationData { + id: string; + role?: string; +} \ No newline at end of file diff --git a/modulo5/case/src/services/HashManager.ts b/modulo5/case/src/services/HashManager.ts new file mode 100644 index 0000000..45704fb --- /dev/null +++ b/modulo5/case/src/services/HashManager.ts @@ -0,0 +1,17 @@ +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); + } + +} \ No newline at end of file diff --git a/modulo5/case/src/services/IdGenerator.ts b/modulo5/case/src/services/IdGenerator.ts new file mode 100644 index 0000000..8dc2b8a --- /dev/null +++ b/modulo5/case/src/services/IdGenerator.ts @@ -0,0 +1,8 @@ +import { v4 } from "uuid"; + +export class IdGenerator{ + + generate(): string{ + return v4(); + } +} \ No newline at end of file diff --git a/modulo5/case/src/services/TokenGenerator.ts b/modulo5/case/src/services/TokenGenerator.ts new file mode 100644 index 0000000..1e212ba --- /dev/null +++ b/modulo5/case/src/services/TokenGenerator.ts @@ -0,0 +1,34 @@ +import * as jwt from "jsonwebtoken"; +import dotenv from "dotenv"; + +dotenv.config(); + +export class TokenGenerator { + private static expiresIn: number = 1200; + + public generate = (input: AuthenticationData): string => { + const newToken = jwt.sign( + { + id: input.id, + role: input.role, + }, + process.env.JWT_KEY as string, + { + expiresIn: TokenGenerator.expiresIn, + } + ); + return newToken; + }; + + public verify(token: string) { + const payload = jwt.verify(token, process.env.JWT_KEY as string) as any; + const result = { id: payload.id, role: payload.role }; + return result; + } +} + +export interface AuthenticationData { + id: string; + role: string; +} + diff --git a/modulo5/case/tests/band.test.ts b/modulo5/case/tests/band.test.ts new file mode 100644 index 0000000..6565360 --- /dev/null +++ b/modulo5/case/tests/band.test.ts @@ -0,0 +1,55 @@ +// import { BandBusiness } from "../src/business/RunnerBusiness"; +// import { BandInputDTO } from "../src/model/Runner"; +// import { HashGeneratorMock } from "./mocks/hashGeneratorMock"; +// import { IdGeneratorMock } from "./mocks/idGeneratorMock"; +// import { TokenGeneratorMock } from "./mocks/tokenGeneratorMock"; +// import { UserDatabaseMock } from "./mocks/userDatabaseMock"; + +// const bandBusinessMock = new BandBusiness( +// new UserDatabaseMock() as any, +// new IdGeneratorMock(), +// new TokenGeneratorMock(), +// new HashGeneratorMock() +// ) +// export const validarBanda = (input: BandInputDTO): boolean => { +// if (!input.name === undefined || input.music_genre === undefined || input.responsible === undefined) { +// return false +// } + +// return true +// }; +// describe("Testando o cadastro da banda sem erro", () => { + +// test("com nome", async () => { + +// expect.assertions +// try { +// const novaBanda = validarBanda({ +// name: "abc", +// music_genre:"aaa", +// responsible: "ssss" +// }); +// expect(novaBanda).toBe(true); +// } catch (error: any) { + +// expect(error.message).toBe("Banda inválida") +// } +// }) + +// }); + +// describe("Testando o cadastro da banda com erro", () => { + +// test("sem nome", async () => { + +// expect.assertions + +// const novaBanda = validarBanda({ +// name: "aewew", +// music_genre:"", +// responsible: "ssss" +// }); +// expect(novaBanda).toBeDefined(); + +// }) +// }); diff --git a/modulo5/case/tsconfig.json b/modulo5/case/tsconfig.json new file mode 100644 index 0000000..0807d5d --- /dev/null +++ b/modulo5/case/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "sourceMap": true, + "outDir": "./build", + "rootDir": "./", + "removeComments": true, + "strict": true, + "noImplicitAny": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true + + } + } \ No newline at end of file