diff --git a/src/app.ts b/src/app.ts deleted file mode 100644 index a315f12..0000000 --- a/src/app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import express from 'express'; -import cors from 'cors'; -import router from './router'; -import errorMiddleware from './middlewares/errorMiddleware'; - -const app = express(); - -app.use(cors()); -app.use(express.json()); -app.use(router); -app.use(errorMiddleware); - -export default app; diff --git a/src/connection/database.ts b/src/connection/database.ts deleted file mode 100644 index 156bdbe..0000000 --- a/src/connection/database.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Pool, PoolConfig } from 'pg'; - -let connectionData: PoolConfig = { - user: process.env.DB_USER, - password: process.env.DB_PASS, - port: Number(process.env.DB_PORT), - host: process.env.DB_HOST, - database: process.env.DB_NAME, -}; - -if (process.env.NODE_ENV === 'prod') { - connectionData = { - connectionString: process.env.DATABASE_URL, - ssl: { - rejectUnauthorized: false, - }, - }; -} - -const connection: Pool = new Pool(connectionData); - -export default connection; diff --git a/src/controllers/questionsController.ts b/src/controllers/questionsController.ts deleted file mode 100644 index 669b748..0000000 --- a/src/controllers/questionsController.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Request, Response, NextFunction } from 'express'; -import { createAnswerSchema, createQuestionSchema } from '../schemas/questionsSchemas'; -import Invalid from '../errors/Invalid'; -import httpStatusCode from '../enums/httpStatusCode'; -import * as questionsService from '../services/questionsService'; -import Conflict from '../errors/Conflict'; -import { Question } from '../protocols/Question'; -import RequestAuthentication from '../protocols/RequestAuthentication'; -import { Answer } from '../protocols/Answer'; -import NotFound from '../errors/NotFound'; - -export async function createQuestion(req: Request, res: Response, next: NextFunction): Promise>> { - try { - const questionBody: Question = req.body; - - const { error: invalidBody } = createQuestionSchema.validate(questionBody); - if (invalidBody) { - throw new Invalid(invalidBody.message); - } - - const result = await questionsService.create(questionBody); - return res.status(httpStatusCode.CREATED).send(result); - } catch (error) { - if (error instanceof Invalid) return res.status(httpStatusCode.BAD_REQUEST).send(error.message); - if (error instanceof Conflict) return res.status(httpStatusCode.CONFLICT).send(error.message); - return next(); - } -} - -export async function getUnsolvedQuestions(req: Request, res: Response, next: NextFunction) { - try { - const questions = await questionsService.getUnsolved(); - - return res.status(httpStatusCode.OK).send(questions); - } catch (error) { - return next(); - } -} - -export async function getQuestionById(req: Request, res: Response, next: NextFunction) { - try { - const questionId = Number(req.params.id); - - if (!Number.isInteger(questionId) || questionId < 1) { - throw new Invalid('Invalid Question Id'); - } - - const question = await questionsService.getById(questionId); - - return res.status(httpStatusCode.OK).send(question); - } catch (error) { - if (error instanceof Invalid) return res.status(httpStatusCode.BAD_REQUEST).send(error.message); - if (error instanceof NotFound) return res.status(httpStatusCode.NOT_FOUND).send(error.message); - return next(); - } -} - -export async function postQuestionAnswer(req: RequestAuthentication, res: Response, next: NextFunction) { - try { - const questionId = Number(req.params.id); - - if (!Number.isInteger(questionId) || questionId < 1) { - throw new Invalid('Invalid Question Id'); - } - - const { error: invalidBody } = createAnswerSchema.validate(req.body); - if (invalidBody) { - throw new Invalid(invalidBody.message); - } - - const answer: Answer = { - userId: req.userId, - questionId, - answer: req.body.answer, - }; - - const answerId = await questionsService.createAnswer(answer); - - return res.status(httpStatusCode.CREATED).send({ - message: `Successfully Answered - Answer Id: ${answerId}.`, - }); - } catch (error) { - if (error instanceof Invalid) return res.status(httpStatusCode.BAD_REQUEST).send(error.message); - if (error instanceof NotFound) return res.status(httpStatusCode.NOT_FOUND).send(error.message); - return next(); - } -} - -export async function putVoteQuestion(req: Request, res: Response, next: NextFunction) { - try { - const questionId = Number(req.params.id); - - if (!Number.isInteger(questionId) || questionId < 1) { - throw new Invalid('Invalid Question Id'); - } - - const voteType = req.url.includes('up-vote') ? '+' : '-'; - await questionsService.vote(questionId, voteType); - - return res.status(httpStatusCode.OK).send({ - message: `Voted ${voteType}1 Successfully`, - }); - } catch (error) { - if (error instanceof Invalid) return res.status(httpStatusCode.BAD_REQUEST).send(error.message); - if (error instanceof NotFound) return res.status(httpStatusCode.NOT_FOUND).send(error.message); - return next(error); - } -} \ No newline at end of file diff --git a/src/controllers/usersController.ts b/src/controllers/usersController.ts deleted file mode 100644 index 9768755..0000000 --- a/src/controllers/usersController.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Request, Response, NextFunction } from 'express'; -import { User } from '../protocols/User'; -import { createUserSchema } from '../schemas/usersSchemas'; -import Invalid from '../errors/Invalid'; -import httpStatusCode from '../enums/httpStatusCode'; -import * as usersService from '../services/usersService'; -import Conflict from '../errors/Conflict'; - -export async function createUser(req: Request, res: Response, next: NextFunction): Promise>> { - try { - const createUserBody: User = req.body; - - const { error: invalidBody } = createUserSchema.validate(createUserBody); - if (invalidBody) { - throw new Invalid(invalidBody.message); - } - - const token = await usersService.create(createUserBody); - - return res.status(httpStatusCode.CREATED).send({ token }); - } catch (error) { - if (error instanceof Invalid) return res.status(httpStatusCode.BAD_REQUEST).send(error.message); - if (error instanceof Conflict) return res.status(httpStatusCode.CONFLICT).send(error.message); - return next(); - } -} - -export async function getUserRanking(req: Request, res: Response, next: NextFunction): Promise>> { - try { - const ranking = await usersService.getRanking(); - return res.status(httpStatusCode.OK).send(ranking); - } catch (error) { - if (error instanceof Invalid) return res.status(httpStatusCode.BAD_REQUEST).send(error.message); - if (error instanceof Conflict) return res.status(httpStatusCode.CONFLICT).send(error.message); - return next(); - } -} \ No newline at end of file diff --git a/src/enums/httpStatusCode.ts b/src/enums/httpStatusCode.ts deleted file mode 100644 index ac475db..0000000 --- a/src/enums/httpStatusCode.ts +++ /dev/null @@ -1,10 +0,0 @@ -export default Object.freeze({ - OK: 200, - CREATED: 201, - NO_CONTENT: 204, - BAD_REQUEST: 400, - UNAUTHORIZED: 401, - NOT_FOUND: 404, - CONFLICT: 409, - INTERNAL_SERVER_ERROR: 500, -}); diff --git a/src/errors/Conflict.ts b/src/errors/Conflict.ts deleted file mode 100644 index 64e575d..0000000 --- a/src/errors/Conflict.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default class Conflict extends Error { - constructor(message: string) { - super(message); - this.name = 'Conflict'; - } -} diff --git a/src/errors/Invalid.ts b/src/errors/Invalid.ts deleted file mode 100644 index 692fe9f..0000000 --- a/src/errors/Invalid.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default class Invalid extends Error { - constructor(message: string) { - super(message); - this.name = 'Invalid'; - } -} diff --git a/src/errors/NotFound.ts b/src/errors/NotFound.ts deleted file mode 100644 index 4a2a178..0000000 --- a/src/errors/NotFound.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default class NotFound extends Error { - constructor(message: string) { - super(message); - this.name = 'NotFound'; - } -} diff --git a/src/middlewares/authenticationMiddleware.ts b/src/middlewares/authenticationMiddleware.ts deleted file mode 100644 index e6ed90d..0000000 --- a/src/middlewares/authenticationMiddleware.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Response, NextFunction } from 'express'; -import jwt from 'jsonwebtoken'; -import httpStatusCode from '../enums/httpStatusCode'; -import RequestAuthentication from '../protocols/RequestAuthentication'; - -export default async function authenticationMiddleware(req: RequestAuthentication, res: Response, next: NextFunction) { - const authorization = req.header('Authorization'); - const token = authorization?.replace('Bearer ', ''); - - jwt.verify(token, process.env.JWT_SECRET, (err, decoded) => { - if (err) return res.sendStatus(httpStatusCode.UNAUTHORIZED); - req.userId = decoded.userId; - return next(); - }); -} \ No newline at end of file diff --git a/src/middlewares/errorMiddleware.ts b/src/middlewares/errorMiddleware.ts deleted file mode 100644 index 3ec86f8..0000000 --- a/src/middlewares/errorMiddleware.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Request, Response, NextFunction } from "express"; -import httpStatusCode from "../enums/httpStatusCode"; - -export default function errorMiddleware(err: Error, req: Request, res: Response, next: NextFunction) { - res.status(httpStatusCode.INTERNAL_SERVER_ERROR).send(`Error - ${err.message}`); - return next(); -} diff --git a/src/protocols/Answer.ts b/src/protocols/Answer.ts deleted file mode 100644 index cff3b5d..0000000 --- a/src/protocols/Answer.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface Answer { - userId?: number, - questionId?: number, - answer: string, - answeredAt?: string, -} diff --git a/src/protocols/Question.ts b/src/protocols/Question.ts deleted file mode 100644 index 8c4512b..0000000 --- a/src/protocols/Question.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Answer } from "./Answer"; - -export interface Question { - id?: number, - userId?: number, - question: string, - student: string, - class: string, - points?: number, - tags: string, - answered?: boolean, - submitAt?: string, - answers?: Answer[], -} diff --git a/src/protocols/RequestAuthentication.ts b/src/protocols/RequestAuthentication.ts deleted file mode 100644 index 9e4e249..0000000 --- a/src/protocols/RequestAuthentication.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Request } from 'express'; - -export default interface RequestAuthentication extends Request { - userId: number, -} diff --git a/src/protocols/User.ts b/src/protocols/User.ts deleted file mode 100644 index 3352fc9..0000000 --- a/src/protocols/User.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface User { - id?: number, - name: string, - class: string, - answers?: number; - points?: number, -} diff --git a/src/repositories/questionsRepository.ts b/src/repositories/questionsRepository.ts deleted file mode 100644 index cfc2cd4..0000000 --- a/src/repositories/questionsRepository.ts +++ /dev/null @@ -1,75 +0,0 @@ -import '../setup'; -import connection from '../connection/database'; -import { Question } from '../protocols/Question'; -import { Answer } from '../protocols/Answer'; - -export async function create(userId: number, question: string, tags: string): Promise { - const result = await connection.query( - 'INSERT INTO questions (user_id, question, tags) VALUES ($1, $2, $3) RETURNING id;', - [userId, question, tags], - ); - - return result.rows[0].id; -} - -export async function getUnsolved(): Promise { - const result = await connection.query( - `SELECT questions.id, questions.question, users.name AS student, users.class, questions.submit_at AS "submitAt" - FROM questions - JOIN users - ON questions.user_id = users.id - WHERE answered = false;`, - ); - - return result.rows; -} - -export async function getById(questionId: number): Promise { - const result = await connection.query( - `SELECT - questions.question, users.name AS student, users.class, - questions.tags, questions.answered, questions.submit_at AS "submitAt" - FROM questions - JOIN users - ON questions.user_id = users.id - WHERE questions.id = $1;`, - [questionId] - ); - - return result.rows[0]; -} - -export async function getAnswersByQuestionId(questionId: number): Promise { - const result = await connection.query( - `SELECT users.name AS student, users.class, answer, answers."answeredAt" - FROM answers - JOIN users - ON answers.user_id = users.id - WHERE question_id = $1;`, - [questionId] - ); - - return result.rows; -} - -export async function createAnswer({ userId, questionId, answer }: Answer): Promise { - const result = await connection.query( - 'INSERT INTO answers (user_id, question_id, answer) VALUES ($1, $2, $3) RETURNING id;', - [userId, questionId, answer], - ); - - await connection.query( - 'UPDATE questions SET answered = true WHERE id = $1;', - [questionId], - ); - - return result.rows[0].id; -} - -export async function vote(questionId: number, voteType: string) { - const result = await connection.query( - `UPDATE questions SET points = points ${voteType} 1 WHERE id = $1 RETURNING *;`, - [questionId], - ); - return result.rows[0]; -} diff --git a/src/repositories/usersRepository.ts b/src/repositories/usersRepository.ts deleted file mode 100644 index af8ac20..0000000 --- a/src/repositories/usersRepository.ts +++ /dev/null @@ -1,38 +0,0 @@ -import '../setup'; -import connection from '../connection/database'; -import { User } from '../protocols/User'; - -export async function findName(name: string): Promise { - const result = await connection.query( - 'SELECT * FROM users WHERE name = $1;', - [name], - ); - return result.rows[0]; -} - -export async function create(name: string, classname: string): Promise { - const result = await connection.query( - 'INSERT INTO users (name, class) VALUES ($1, $2) RETURNING id;', - [name, classname], - ); - - return result.rows[0].id; -} - -export async function getRanking(): Promise { - const result = await connection.query( - `SELECT - users.name, - SUM(questions.points) AS points, - COUNT(users.id) AS anwers - FROM answers - JOIN users - ON answers.user_id = users.id - JOIN questions - ON answers.question_id = questions.id - GROUP BY users.id - ORDER BY SUM(questions.points) DESC;`, - ); - - return result.rows; -} \ No newline at end of file diff --git a/src/router.ts b/src/router.ts deleted file mode 100644 index e050704..0000000 --- a/src/router.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Router } from 'express'; -import usersRouter from './routers/usersRouter'; -import questionsRouter from './routers/questionsRouter'; -import rankingRouter from './routers/rankingRouter'; - -const router: Router = Router(); - -router.use('/users', usersRouter); -router.use('/questions', questionsRouter); -router.use('/ranking', rankingRouter); - -export default router; diff --git a/src/routers/questionsRouter.ts b/src/routers/questionsRouter.ts deleted file mode 100644 index a55987f..0000000 --- a/src/routers/questionsRouter.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Router } from 'express'; -import * as questionsController from '../controllers/questionsController'; -import authenticationMiddleware from '../middlewares/authenticationMiddleware'; - -const router: Router = Router(); - -router.post('/', questionsController.createQuestion); -router.get('/', questionsController.getUnsolvedQuestions); -router.post('/:id', authenticationMiddleware, questionsController.postQuestionAnswer); -router.get('/:id', questionsController.getQuestionById); -router.put('/:id/up-vote', questionsController.putVoteQuestion); -router.put('/:id/down-vote', questionsController.putVoteQuestion); - -export default router; \ No newline at end of file diff --git a/src/routers/rankingRouter.ts b/src/routers/rankingRouter.ts deleted file mode 100644 index 2c27240..0000000 --- a/src/routers/rankingRouter.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Router } from 'express'; -import * as usersController from '../controllers/usersController'; - -const router: Router = Router(); - -export default router.get('/', usersController.getUserRanking); diff --git a/src/routers/usersRouter.ts b/src/routers/usersRouter.ts deleted file mode 100644 index c1bdf5f..0000000 --- a/src/routers/usersRouter.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Router } from 'express'; -import * as usersController from '../controllers/usersController'; - -const router: Router = Router(); - -export default router.post('/', usersController.createUser); diff --git a/src/schemas/questionsSchemas.ts b/src/schemas/questionsSchemas.ts deleted file mode 100644 index 562c1b2..0000000 --- a/src/schemas/questionsSchemas.ts +++ /dev/null @@ -1,12 +0,0 @@ -import joi from 'joi'; - -export const createQuestionSchema = joi.object({ - question: joi.string().min(5).required(), - student: joi.string().min(3).required(), - class: joi.string().pattern(/[T]{1}\d{1,}/).required(), - tags: joi.string().min(3).required(), -}); - -export const createAnswerSchema = joi.object({ - answer: joi.string().min(3).required(), -}); \ No newline at end of file diff --git a/src/schemas/usersSchemas.ts b/src/schemas/usersSchemas.ts deleted file mode 100644 index 5ac7409..0000000 --- a/src/schemas/usersSchemas.ts +++ /dev/null @@ -1,6 +0,0 @@ -import joi from 'joi'; - -export const createUserSchema = joi.object({ - name: joi.string().min(3).max(50).required(), - class: joi.string().pattern(/[T]{1}\d{1,}/).required() -}); \ No newline at end of file diff --git a/src/server.ts b/src/server.ts deleted file mode 100644 index 87d974e..0000000 --- a/src/server.ts +++ /dev/null @@ -1,7 +0,0 @@ -import app from './app'; -import './setup'; - -app.listen(process.env.PORT, () => { - // eslint-disable-next-line no-console - console.log(`Server is running on port ${process.env.PORT}`); -}); diff --git a/src/services/questionsService.ts b/src/services/questionsService.ts deleted file mode 100644 index 87b0ea9..0000000 --- a/src/services/questionsService.ts +++ /dev/null @@ -1,77 +0,0 @@ -import jwt from 'jsonwebtoken'; -import { Question } from '../protocols/Question'; -import * as usersRepository from '../repositories/usersRepository'; -import * as questionsRepository from '../repositories/questionsRepository'; -import { Answer } from '../protocols/Answer'; -import formatDate from '../utils/formatDate'; -import NotFound from '../errors/NotFound'; - -export async function create(questionBody: Question): Promise { - const { question, student, tags, class: classname } = questionBody; - - const existsUser = await usersRepository.findName(student); - - let userId = existsUser?.id; - let token; - if (!userId) { - userId = await usersRepository.create(student, classname); - token = jwt.sign({ userId }, process.env.JWT_SECRET); - } - - const questionId = await questionsRepository.create(userId, question, tags); - return { - questionId, - userToken: token, - }; -} - -export async function getUnsolved(): Promise { - const questions = await questionsRepository.getUnsolved(); - - questions.forEach((question) => { - question.submitAt = formatDate(question.submitAt); - }); - - return questions; -} - -export async function getById(questionId: number): Promise { - const question = await questionsRepository.getById(questionId); - - if (!question) { - throw new NotFound('Question Not Found'); - } - - if (question.answered) { - const answers = await questionsRepository.getAnswersByQuestionId(questionId); - - answers.forEach((answer) => { - answer.answeredAt = formatDate(answer.answeredAt); - }); - - question.answers = answers; - } - question.submitAt = formatDate(question.submitAt); - - return question; -} - -export async function createAnswer(answer: Answer): Promise { - const question = await questionsRepository.getById(answer.questionId); - if (!question) { - throw new NotFound('Question Not Found'); - } - - const answerId = await questionsRepository.createAnswer(answer); - return answerId; -} - -export async function vote(questionId: number, voteType: string): Promise { - const question = await questionsRepository.getById(questionId); - if (!question) { - throw new NotFound('Question Not Found'); - } - - const votedQuestion = await questionsRepository.vote(questionId, voteType); - return votedQuestion; -} \ No newline at end of file diff --git a/src/services/usersService.ts b/src/services/usersService.ts deleted file mode 100644 index 772e123..0000000 --- a/src/services/usersService.ts +++ /dev/null @@ -1,25 +0,0 @@ -import jwt from 'jsonwebtoken'; -import Conflict from '../errors/Conflict'; -import * as usersRepository from '../repositories/usersRepository'; -import { User } from '../protocols/User'; -import Invalid from '../errors/Invalid'; - -export async function create(createUserBody: User): Promise { - const { name, class: classname } = createUserBody; - - const user = await usersRepository.findName(name); - if (user) throw new Conflict('User already exists'); - - const userId = await usersRepository.create(name, classname); - if (userId) { - const token = jwt.sign({ userId }, process.env.JWT_SECRET); - return token; - } - - throw new Invalid('Invalid data, unable to create user'); -} - -export async function getRanking() { - const ranking = await usersRepository.getRanking(); - return ranking; -} diff --git a/src/setup.ts b/src/setup.ts deleted file mode 100644 index b0f92a4..0000000 --- a/src/setup.ts +++ /dev/null @@ -1,13 +0,0 @@ -import dotenv from 'dotenv'; - -let envFile = '.env'; - -if (process.env.NODE_ENV === 'dev') { - envFile = '.env.dev'; -} - -const setup = dotenv.config({ - path: envFile, -}); - -export default setup; diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts deleted file mode 100644 index a7ad16b..0000000 --- a/src/utils/formatDate.ts +++ /dev/null @@ -1,3 +0,0 @@ -import dayjs from 'dayjs'; - -export default (date: string) => `${dayjs(date).format('YYYY-MM-DD HH:mm')}` \ No newline at end of file