diff --git a/README.MD b/README.MD deleted file mode 100644 index 60ab2bd..0000000 --- a/README.MD +++ /dev/null @@ -1,68 +0,0 @@ -# API FullStackOverflow Developer - -In API FullStackOverflow Developer people can freely post and answer questions. -Each question can have only one answer or none (if it hasn't been answered yet) - -Try it out now at https://back-fullstackdeveloper.herokuapp.com/ - -## About - -This is a API web app that lets you post and answer questions. - -- Post a new question -- Register a new user to answer questions -- Answer questions -- See answered and unanswered questions - -## Technologies -The following tools and frameworks were used in the construction of the project:
- - ![NodeJS](https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white)  - ![Express](https://img.shields.io/badge/Express.js-000000?style=for-the-badge&logo=express&logoColor=white)  - ![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)  - ![Jest](https://img.shields.io/badge/Jest-C21325?style=for-the-badge&logo=jest&logoColor=white)  - ![PostgresSQL](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white)  - ![Heroku](https://img.shields.io/badge/Heroku-430098?style=for-the-badge&logo=heroku&logoColor=white)  - - ## How to run - -1. Clone this repository -```bash -git clone https://github.com/thipereira02/API_FullStackOverflow-Developer -``` - -2. Create a Database using the ``dump.sql`` file inside the ``dump`` folder by following these steps: - - 2.1 Open your terminal. **Important: the terminal must be opened in the same path as the ``dump.sql`` file is located.** - - 2.2 Access PostgreSQL using the command ``sudo su postgres`` and enter your password when prompted. - - 2.3 Next, type ``psql postgres`` and hit enter. - - 2.4 Create a database by typing ``CREATE DATABASE fullstack;`` and hitting enter. - - 2.5 Type ``\q`` and hit enter. - - 2.6 Finally, type ```psql fullstack < dump.sql``` and hit enter. Your database should be ready after this step. -2. Set the environment variables by following these steps: - - 3.1 Create a ``.env`` file in the folder root - - 3.2 Copy the content of the ``.env.example`` into it - - 3.3 Set the ``DATABASE_URL`` in this format: "postgres://user:password@host:port/fullstack" - - 3.4 Set the ``PORT`` for 4000 -3. In your terminal, go back to the root folder and install the dependencies -```bash -npm i -``` -5. Also in the root folder, run the back-end with -```bash -npm start -``` -6. Your server should be running now. -7. After that, you can optionally test the project following these steps: - - 7.1 Open your terminal. - - 7.2 Access PostgreSQL using the command ``sudo su postgres`` and enter your password when prompted. - - 7.3 Next, type ``psql postgres`` and hit enter. - - 7.4 Create a test database by typing ``CREATE DATABASE fullstack_test TEMPLATE fullstack;`` and hitting enter. Your database test should be ready after this step. - - 7.5 Set the enviroment variable following the step 5 again, with the following changes: - - 7.5.1 The file must be called ``.env.test`` - - 7.5.2 The ``DATABASE_URL`` must be in this format: "postgres://user:password@host:port/fullstack_test" - - Important: the tests assume that some tables are pre-populated. Therefore, it is important to use the dump provided to create the test database. - -8. In your terminal, go to the root folder and run the tests with: -```bash -npm run test -``` \ No newline at end of file diff --git a/dump.sql b/dump.sql deleted file mode 100644 index b93d455..0000000 --- a/dump.sql +++ /dev/null @@ -1,250 +0,0 @@ --- --- PostgreSQL database dump --- - --- Dumped from database version 12.9 (Ubuntu 12.9-0ubuntu0.20.04.1) --- Dumped by pg_dump version 12.9 (Ubuntu 12.9-0ubuntu0.20.04.1) - -SET statement_timeout = 0; -SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; -SET client_encoding = 'UTF8'; -SET standard_conforming_strings = on; -SELECT pg_catalog.set_config('search_path', '', false); -SET check_function_bodies = false; -SET xmloption = content; -SET client_min_messages = warning; -SET row_security = off; - -SET default_tablespace = ''; - -SET default_table_access_method = heap; - --- --- Name: classes; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.classes ( - id integer NOT NULL, - name character varying(2) NOT NULL -); - - --- --- Name: classes_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -CREATE SEQUENCE public.classes_id_seq - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - --- --- Name: classes_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - --- - -ALTER SEQUENCE public.classes_id_seq OWNED BY public.classes.id; - - --- --- Name: questions; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.questions ( - id integer NOT NULL, - question text NOT NULL, - "classId" integer NOT NULL, - student character varying(255) NOT NULL, - tags text NOT NULL, - "submitAt" character varying(255) NOT NULL, - answered boolean DEFAULT false NOT NULL, - "answeredAt" character varying(255), - "answeredBy" integer, - answer text -); - - --- --- Name: questions_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -CREATE SEQUENCE public.questions_id_seq - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - --- --- Name: questions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - --- - -ALTER SEQUENCE public.questions_id_seq OWNED BY public.questions.id; - - --- --- Name: students; Type: TABLE; Schema: public; Owner: - --- - -CREATE TABLE public.students ( - id integer NOT NULL, - name character varying(255) NOT NULL, - token text NOT NULL, - "classId" integer NOT NULL -); - - --- --- Name: students_id_seq; Type: SEQUENCE; Schema: public; Owner: - --- - -CREATE SEQUENCE public.students_id_seq - AS integer - START WITH 1 - INCREMENT BY 1 - NO MINVALUE - NO MAXVALUE - CACHE 1; - - --- --- Name: students_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - --- - -ALTER SEQUENCE public.students_id_seq OWNED BY public.students.id; - - --- --- Name: classes id; Type: DEFAULT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.classes ALTER COLUMN id SET DEFAULT nextval('public.classes_id_seq'::regclass); - - --- --- Name: questions id; Type: DEFAULT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.questions ALTER COLUMN id SET DEFAULT nextval('public.questions_id_seq'::regclass); - - --- --- Name: students id; Type: DEFAULT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.students ALTER COLUMN id SET DEFAULT nextval('public.students_id_seq'::regclass); - - --- --- Data for Name: classes; Type: TABLE DATA; Schema: public; Owner: - --- - - - --- --- Data for Name: questions; Type: TABLE DATA; Schema: public; Owner: - --- - - - --- --- Data for Name: students; Type: TABLE DATA; Schema: public; Owner: - --- - - - --- --- Name: classes_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - --- - -SELECT pg_catalog.setval('public.classes_id_seq', 1, false); - - --- --- Name: questions_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - --- - -SELECT pg_catalog.setval('public.questions_id_seq', 1, false); - - --- --- Name: students_id_seq; Type: SEQUENCE SET; Schema: public; Owner: - --- - -SELECT pg_catalog.setval('public.students_id_seq', 1, false); - - --- --- Name: classes classes_name_key; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.classes - ADD CONSTRAINT classes_name_key UNIQUE (name); - - --- --- Name: classes classes_pk; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.classes - ADD CONSTRAINT classes_pk PRIMARY KEY (id); - - --- --- Name: questions questions_pk; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.questions - ADD CONSTRAINT questions_pk PRIMARY KEY (id); - - --- --- Name: students students_pk; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.students - ADD CONSTRAINT students_pk PRIMARY KEY (id); - - --- --- Name: students students_token_key; Type: CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.students - ADD CONSTRAINT students_token_key UNIQUE (token); - - --- --- Name: questions questions_fk0; Type: FK CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.questions - ADD CONSTRAINT questions_fk0 FOREIGN KEY ("classId") REFERENCES public.classes(id); - - --- --- Name: questions questions_fk1; Type: FK CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.questions - ADD CONSTRAINT questions_fk1 FOREIGN KEY ("answeredBy") REFERENCES public.students(id); - - --- --- Name: students students_fk0; Type: FK CONSTRAINT; Schema: public; Owner: - --- - -ALTER TABLE ONLY public.students - ADD CONSTRAINT students_fk0 FOREIGN KEY ("classId") REFERENCES public.classes(id); - - --- --- PostgreSQL database dump complete --- - diff --git a/jest.config.js b/jest.config.js deleted file mode 100644 index 4a5b465..0000000 --- a/jest.config.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', -}; diff --git a/package.json b/package.json deleted file mode 100644 index 14bd3aa..0000000 --- a/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "name": "typescript-back-template", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "npx jest -i", - "build": "npx tsc --outDir dist", - "start": "node dist/src/server.js", - "postinstall": "npm run build", - "dev": "npx nodemon --watch \"src/**\" --ext \"ts,json\" --exec \"ts-node src/server.ts\"" - }, - "keywords": [], - "author": "", - "license": "ISC", - "devDependencies": { - "@types/cors": "^2.8.12", - "@types/express": "^4.17.13", - "@types/faker": "^5.5.9", - "@types/jest": "^26.0.24", - "@types/joi": "^17.2.3", - "@types/node": "^16.3.3", - "@types/pg": "^8.6.1", - "@types/supertest": "^2.0.11", - "@typescript-eslint/eslint-plugin": "^5.6.0", - "@typescript-eslint/parser": "^5.6.0", - "eslint": "^8.4.1", - "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.25.3", - "jest": "^27.0.6", - "nodemon": "^2.0.12", - "supertest": "^6.1.3", - "ts-jest": "^27.0.3", - "ts-node": "^10.1.0", - "typescript": "^4.3.5" - }, - "dependencies": { - "cors": "^2.8.5", - "dayjs": "^1.10.7", - "dotenv": "^10.0.0", - "express": "^4.17.1", - "faker": "^5.5.3", - "joi": "^17.5.0", - "pg": "^8.6.0" - } -} diff --git a/src/app.ts b/src/app.ts deleted file mode 100644 index 8aa8b51..0000000 --- a/src/app.ts +++ /dev/null @@ -1,14 +0,0 @@ -import express from 'express'; -import cors from 'cors'; - -import usersRoute from './routers/usersRoute'; -import questionsRoute from './routers/questionsRoute'; - -const app = express(); -app.use(cors()); -app.use(express.json()); - -app.use(questionsRoute); -app.use(usersRoute); - -export default app; diff --git a/src/controllers/questionsController.ts b/src/controllers/questionsController.ts deleted file mode 100644 index 987f32e..0000000 --- a/src/controllers/questionsController.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable consistent-return */ -import { Response, Request } from 'express'; - -import * as questionsService from '../services/questionsService'; -import { NewQuestionInterface } from '../interfaces/newQuestionInterface'; -import { AnswerQuestionInterface } from '../interfaces/answerQuestionInterface'; - -export async function newQuestion(req: Request, res: Response) { - try { - const { question, student, userClass, tags } = req.body as NewQuestionInterface; - - const addNewQuestion = await questionsService.createQuestion(question, student, userClass, tags); - if (!addNewQuestion) return res.sendStatus(400); - - return res.send(addNewQuestion); - } catch (err) { - console.log(err); - res.sendStatus(500); - } -} - -export async function getUnansweredQuestions(req: Request, res: Response) { - try { - const questions = await questionsService.getQuestions(); - if (!questions) return res.sendStatus(404); - - return res.send(questions); - } catch (err) { - console.log(err); - res.sendStatus(500); - } -} - -export async function answerQuestion(req: Request, res: Response) { - try { - const { answer } = req.body as AnswerQuestionInterface; - const questionId = Number(req.params.id); - const authorization = req.header('Authorization'); - const token = authorization?.replace('Bearer ', ''); - - const answerAQuestion = await questionsService.answerAQuestion(answer, questionId, token); - if (answerAQuestion === false) return res.sendStatus(404); - if (answerAQuestion === null) return res.sendStatus(400); - - return res.sendStatus(201); - } catch (err) { - console.log(err); - res.sendStatus(500); - } -} - -export async function getQuestionById(req: Request, res: Response) { - try { - const questionId = Number(req.params.id); - - const question = await questionsService.getQuestionById(questionId); - if (!question) return res.sendStatus(404); - - return res.send(question); - } catch (err) { - console.log(err); - res.sendStatus(500); - } -} diff --git a/src/controllers/usersController.ts b/src/controllers/usersController.ts deleted file mode 100644 index f77125b..0000000 --- a/src/controllers/usersController.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* eslint-disable consistent-return */ -import { Response, Request } from 'express'; - -import { UserInterface } from '../interfaces/userInterface'; -import * as usersService from '../services/usersService'; - -export async function newUser(req: Request, res: Response) { - try { - const { name, userClass } = req.body as UserInterface; - - const createUser = await usersService.create(name, userClass); - if (createUser === false) return res.sendStatus(400); - if (createUser === null) return res.sendStatus(409); - - res.status(201).send(createUser); - } catch (err) { - console.log(err); - res.sendStatus(500); - } -} diff --git a/src/database.ts b/src/database.ts deleted file mode 100644 index 541dc9c..0000000 --- a/src/database.ts +++ /dev/null @@ -1,13 +0,0 @@ -import './setup'; -import pg from 'pg'; - -const { Pool } = pg; - -const connection = new Pool({ - connectionString: process.env.DATABASE_URL, - ssl: { - rejectUnauthorized: false, - }, -}); - -export default connection; diff --git a/src/interfaces/answerQuestionInterface.ts b/src/interfaces/answerQuestionInterface.ts deleted file mode 100644 index b7acaea..0000000 --- a/src/interfaces/answerQuestionInterface.ts +++ /dev/null @@ -1,5 +0,0 @@ -interface AnswerQuestionInterface{ - answer: string, -} - -export { AnswerQuestionInterface }; diff --git a/src/interfaces/newQuestionInterface.ts b/src/interfaces/newQuestionInterface.ts deleted file mode 100644 index 0e782e0..0000000 --- a/src/interfaces/newQuestionInterface.ts +++ /dev/null @@ -1,8 +0,0 @@ -interface NewQuestionInterface{ - question: string, - student: string, - userClass: string, - tags: string -} - -export { NewQuestionInterface }; diff --git a/src/interfaces/userInterface.ts b/src/interfaces/userInterface.ts deleted file mode 100644 index aacd138..0000000 --- a/src/interfaces/userInterface.ts +++ /dev/null @@ -1,6 +0,0 @@ -interface UserInterface{ - name: string, - userClass: string, -} - -export { UserInterface }; diff --git a/src/repositories/questionsRepository.ts b/src/repositories/questionsRepository.ts deleted file mode 100644 index 5207a45..0000000 --- a/src/repositories/questionsRepository.ts +++ /dev/null @@ -1,107 +0,0 @@ -import '../setup'; -import dayjs from 'dayjs'; -import connection from '../database'; - -export async function newQuestion(question: string, student: string, userClass: string, tags: string):Promise { - let classId; - const now = dayjs().format('YYYY-MM-DD HH:mm'); - - const classAlreadyRegistered = await connection.query(` - SELECT * - FROM classes - WHERE name=$1 - `, [userClass]); - - if (classAlreadyRegistered.rowCount === 0) { - const result = await connection.query(` - INSERT INTO classes - (name) - VALUES ($1) - RETURNING id - `, [userClass]); - classId = result.rows[0].id; - } else { - const result = await connection.query(` - SELECT id - FROM classes - WHERE name=$1 - `, [userClass]); - classId = result.rows[0].id; - } - - const questionId = await connection.query(` - INSERT INTO questions - (question, "classId", student, tags, "submitAt") - VALUES ($1, $2, $3, $4, $5) - RETURNING id - `, [question, classId, student, tags, now]); - return questionId.rows[0].id; -} - -export async function getUnansweredQuestions() { - const result = await connection.query(` - SELECT questions.id, questions.question, questions.student, questions."submitAt", - classes.name AS class - FROM questions - JOIN classes - ON questions."classId"=classes.id - WHERE answered=false - `); - if (result.rowCount === 0) return false; - return result.rows; -} - -export async function checkQuestionId(questionId: number):Promise { - const result = await connection.query(` - SELECT * - FROM questions - WHERE id=$1 - `, [questionId]); - if (result.rowCount === 0) return false; - return true; -} - -export async function answerQuestion(answer: string, questionId: number, token: string): Promise { - const whoAnswer = await connection.query(` - SELECT * - FROM students - WHERE token=$1 - `, [token]); - if (whoAnswer.rowCount === 0) return false; - - const answeredBy = whoAnswer.rows[0].id; - const now = dayjs().format('YYYY-MM-DD HH:mm'); - - await connection.query(` - UPDATE questions - SET answered=true, "answeredAt"=$1, "answeredBy"=$2, answer=$3 - WHERE id=$4 - `, [now, answeredBy, answer, questionId]); - return true; -} - -export async function getQuestion(questionId: number) { - const answered = await connection.query(` - SELECT questions.*, - classes.name AS class, - students.name AS "answeredBy" - FROM questions - JOIN classes - ON questions."classId"=classes.id - JOIN students - ON questions."answeredBy"=students.id - WHERE questions.id=$1 - `, [questionId]); - if (answered.rowCount !== 0) return answered.rows[0]; - - const unanswered = await connection.query(` - SELECT questions.*, - classes.name AS class - FROM questions - JOIN classes - ON questions."classId"=classes.id - WHERE questions.id=$1 - AND answered=false - `, [questionId]); - return unanswered.rows[0]; -} diff --git a/src/repositories/usersRepository.ts b/src/repositories/usersRepository.ts deleted file mode 100644 index 3714089..0000000 --- a/src/repositories/usersRepository.ts +++ /dev/null @@ -1,42 +0,0 @@ -import '../setup'; -import connection from '../database'; - -export async function checkName(name: string):Promise { - const check = await connection.query(` - SELECT * - FROM students - WHERE name=$1 - `, [name]); - if (check.rowCount === 0) return true; - return false; -} - -export async function createUser(name: string, userClass: string, token: string):Promise { - let classId; - - const classAlreadyRegistered = await connection.query(` - SELECT * - FROM classes - WHERE name=$1 - `, [userClass]); - - if (classAlreadyRegistered.rowCount === 0) { - const result = await connection.query(` - INSERT INTO classes - (name) - VALUES ($1) - RETURNING id - `, [userClass]); - classId = result.rows[0].id; - } else { - classId = classAlreadyRegistered.rows[0].id; - } - - const userToken = await connection.query(` - INSERT INTO students - (name, "classId", token) - VALUES ($1, $2, $3) - RETURNING token - `, [name, classId, token]); - return userToken.rows[0].token; -} diff --git a/src/routers/questionsRoute.ts b/src/routers/questionsRoute.ts deleted file mode 100644 index 62d306f..0000000 --- a/src/routers/questionsRoute.ts +++ /dev/null @@ -1,12 +0,0 @@ -import express from 'express'; - -import * as questionsController from '../controllers/questionsController'; - -const router = express.Router(); - -router.post('/questions', questionsController.newQuestion); -router.get('/questions', questionsController.getUnansweredQuestions); -router.post('/questions/:id', questionsController.answerQuestion); -router.get('/questions/:id', questionsController.getQuestionById); - -export default router; diff --git a/src/routers/usersRoute.ts b/src/routers/usersRoute.ts deleted file mode 100644 index eff3e76..0000000 --- a/src/routers/usersRoute.ts +++ /dev/null @@ -1,9 +0,0 @@ -import express from 'express'; - -import * as usersController from '../controllers/usersController'; - -const router = express.Router(); - -router.post('/users', usersController.newUser); - -export default router; diff --git a/src/schemas/AnswerSchema.ts b/src/schemas/AnswerSchema.ts deleted file mode 100644 index e649c6a..0000000 --- a/src/schemas/AnswerSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -import joi from 'joi'; - -export const answerSchema = joi.object({ - answer: joi.string().trim().required(), -}); diff --git a/src/schemas/QuestionSchema.ts b/src/schemas/QuestionSchema.ts deleted file mode 100644 index 222339d..0000000 --- a/src/schemas/QuestionSchema.ts +++ /dev/null @@ -1,8 +0,0 @@ -import joi from 'joi'; - -export const questionSchema = joi.object({ - question: joi.string().trim().required(), - student: joi.string().trim().required(), - userClass: joi.string().max(2).pattern(/^[A-Z]{1}[1-9]{1}$/).required(), - tags: joi.string().trim().required(), -}); diff --git a/src/schemas/UserSchema.ts b/src/schemas/UserSchema.ts deleted file mode 100644 index a65174c..0000000 --- a/src/schemas/UserSchema.ts +++ /dev/null @@ -1,6 +0,0 @@ -import joi from 'joi'; - -export const userSchema = joi.object({ - name: joi.string().trim().required(), - userClass: joi.string().max(2).pattern(/^[A-Z]{1}[1-9]{1}$/).required(), -}); diff --git a/src/server.ts b/src/server.ts deleted file mode 100644 index 3ffcafb..0000000 --- a/src/server.ts +++ /dev/null @@ -1,6 +0,0 @@ -import './setup'; -import app from './app'; - -app.listen(Number(process.env.PORT), () => { - console.log(`Server is listening on port ${Number(process.env.PORT)}.`); -}); diff --git a/src/services/questionsService.ts b/src/services/questionsService.ts deleted file mode 100644 index 5a07750..0000000 --- a/src/services/questionsService.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as questionsRepository from '../repositories/questionsRepository'; -import { answerSchema } from '../schemas/AnswerSchema'; -import { questionSchema } from '../schemas/QuestionSchema'; - -export async function createQuestion(question: string, student: string, userClass: string, tags: string) { - const isValid = questionSchema.validate({ question, student, userClass, tags }); - if (isValid.error !== undefined) return false; - - const questionId = await questionsRepository.newQuestion(question, student, userClass, tags); - return { - id: questionId, - }; -} - -export async function getQuestions() { - const questions = await questionsRepository.getUnansweredQuestions(); - if (!questions) return false; - return questions; -} - -export async function answerAQuestion(answer: string, questionId: number, token: string) { - const checkIfAnwersIsValid = answerSchema.validate({ answer }); - if (checkIfAnwersIsValid.error !== undefined) return null; - - const checkIfQuestionExists = await questionsRepository.checkQuestionId(questionId); - if (!checkIfQuestionExists) return false; - - const answerTheQuestion = await questionsRepository.answerQuestion(answer, questionId, token); - if (!answerTheQuestion) return false; - - return true; -} - -export async function getQuestionById(questionId: number) { - const question = await questionsRepository.getQuestion(questionId); - if (!question) return false; - if (question.answered === true) { - return { - question: question.question, - student: question.student, - class: question.class, - tags: question.tags, - answered: question.answered, - submitAt: question.submitAt, - answeredAt: question.answeredAt, - answeredBy: question.answeredBy, - answer: question.answer, - }; - } - - return { - question: question.question, - student: question.student, - class: question.class, - tags: question.tags, - answered: question.answered, - submitAt: question.submitAt, - }; -} diff --git a/src/services/usersService.ts b/src/services/usersService.ts deleted file mode 100644 index 09fc8a4..0000000 --- a/src/services/usersService.ts +++ /dev/null @@ -1,19 +0,0 @@ -import faker from 'faker'; - -import * as usersRepository from '../repositories/usersRepository'; -import { userSchema } from '../schemas/UserSchema'; - -export async function create(name: string, userClass: string) { - const isValid = userSchema.validate({ name, userClass }); - if (isValid.error !== undefined) return false; - - const nameIsAvailable = await usersRepository.checkName(name); - if (!nameIsAvailable) return null; - - const token = faker.datatype.uuid(); - - const userToken = await usersRepository.createUser(name, userClass, token); - return { - token: userToken, - }; -} diff --git a/src/setup.ts b/src/setup.ts deleted file mode 100644 index d6e605d..0000000 --- a/src/setup.ts +++ /dev/null @@ -1,7 +0,0 @@ -import dotenv from 'dotenv'; - -const envFile = process.env.NODE_ENV === 'test' ? '.env.test' : '.env'; - -dotenv.config({ - path: envFile, -}); diff --git a/src/tests/factories/questionsFactory.ts b/src/tests/factories/questionsFactory.ts deleted file mode 100644 index fd4fe48..0000000 --- a/src/tests/factories/questionsFactory.ts +++ /dev/null @@ -1,37 +0,0 @@ -import faker from 'faker'; -import connection from '../../database'; - -export async function insertStudent(classId: number) { - const token = faker.datatype.uuid(); - await connection.query(` - INSERT INTO students - (name, "classId", token) - VALUES ($1, $2, $3) - RETURNING token - `, ['Veegata', classId, token]); - return { token }; -} - -export async function insertClass() { - const newClass = await connection.query(` - INSERT INTO classes - (name) - VALUES ('T3') - RETURNING id - `); - return { classId: newClass.rows[0].id }; -} - -export async function insertQuestion() { - const { classId } = await insertClass(); - const newQuestion = await connection.query(` - INSERT INTO questions - (question, "classId", student, tags, "submitAt") - VALUES ('Uki ta contecendo?', ${classId}, 'Vegata', 'typescript, vida, javascript, java?', '2021-12-12 19:56') - RETURNING id - `); - return { - questionId: newQuestion.rows[0].id, - classId, - }; -} diff --git a/src/tests/integration/questions.test.ts b/src/tests/integration/questions.test.ts deleted file mode 100644 index fd6a22d..0000000 --- a/src/tests/integration/questions.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -/* eslint-disable no-undef */ -import '../../setup'; - -import { insertClass, insertQuestion, insertStudent } from '../factories/questionsFactory'; -import { agent, clearDatabase, closeConnection } from '../utils/database'; - -beforeEach(async () => { - await clearDatabase(); -}); - -afterAll(async () => { - await closeConnection(); -}); - -describe('POST /questions', () => { - it('should answer with status 400 when question is empty', async () => { - const body = { - question: '', - student: 'Vegata', - userClass: 'T3', - tags: 'typescript, vida, javascript, java?', - }; - const response = await agent.post('/questions').send(body); - expect(response.status).toEqual(400); - }); - - it('should answer with status 400 when student is empty', async () => { - const body = { - question: 'Uki ta contecendo?', - student: '', - userClass: 'T3', - tags: 'typescript, vida, javascript, java?', - }; - const response = await agent.post('/questions').send(body); - expect(response.status).toEqual(400); - }); - - it('should answer with status 400 when userClass is empty', async () => { - const body = { - question: 'Uki ta contecendo?', - student: 'Vegata', - userClass: '', - tags: 'typescript, vida, javascript, java?', - }; - const response = await agent.post('/questions').send(body); - expect(response.status).toEqual(400); - }); - - it('should answer with status 400 when tags is empty', async () => { - const body = { - question: 'Uki ta contecendo?', - student: 'Vegata', - userClass: 'T3', - tags: '', - }; - const response = await agent.post('/questions').send(body); - expect(response.status).toEqual(400); - }); - - it('should answer with status 400 when userClass is invalid', async () => { - const body = { - question: 'Uki ta contecendo?', - student: 'Vegata', - userClass: 'T33', - tags: 'typescript, vida, javascript, java?', - }; - const response = await agent.post('/questions').send(body); - expect(response.status).toEqual(400); - }); -}); - -describe('GET /questions', () => { - it('should answer with status 404 when there is no unanswered questions to get', async () => { - const response = await agent.get('/questions'); - expect(response.status).toEqual(404); - }); - - it('should answer with status 200 and when there is no unanswered questions to get', async () => { - await insertQuestion(); - - const response = await agent.get('/questions'); - expect(response.status).toEqual(200); - }); -}); - -describe('POST /questions/:id', () => { - it('should answer with status 201 when the answer is posted', async () => { - const body = { - answer: 'Ok', - }; - const { questionId, classId } = await insertQuestion(); - - const { token } = await insertStudent(classId); - - const response = await agent.post(`/questions/${questionId}`).send(body).set('Authorization', `Bearer ${token}`); - expect(response.status).toEqual(201); - }); - - it('should answer with status 404 when the question doesnt exists', async () => { - const body = { - answer: 'Ok', - }; - const { classId } = await insertClass(); - - const { token } = await insertStudent(classId); - - const response = await agent.post('/questions/0').send(body).set('Authorization', `Bearer ${token}`); - expect(response.status).toEqual(404); - }); - - it('should answer with status 400 when the answer is empty', async () => { - const body = { - answer: '', - }; - const { questionId, classId } = await insertQuestion(); - - const { token } = await insertStudent(classId); - - const response = await agent.post(`/questions/${questionId}`).send(body).set('Authorization', `Bearer ${token}`); - expect(response.status).toEqual(400); - }); -}); - -describe('GET /questions/:id', () => { - it('should answer with status 200 when question is returned', async () => { - const { questionId, classId } = await insertQuestion(); - - const { token } = await insertStudent(classId); - - const response = await agent.get(`/questions/${questionId}`).set('Authorization', `Bearer ${token}`); - expect(response.status).toEqual(200); - }); - - it('should answer with status 404 when there is not question to return', async () => { - const { classId } = await insertClass(); - - const { token } = await insertStudent(classId); - - const response = await agent.get('/questions/1').set('Authorization', `Bearer ${token}`); - expect(response.status).toEqual(404); - }); -}); diff --git a/src/tests/integration/users.test.ts b/src/tests/integration/users.test.ts deleted file mode 100644 index 4bdae1c..0000000 --- a/src/tests/integration/users.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint-disable no-undef */ -import '../../setup'; - -import { agent, clearDatabase, closeConnection } from '../utils/database'; - -beforeEach(async () => { - await clearDatabase(); -}); - -afterAll(async () => { - await closeConnection(); -}); - -describe('POST /users', () => { - it('should answer with status 201 when user is created', async () => { - const body = { - name: 'Vegata', - userClass: 'T3', - }; - const response = await agent.post('/users').send(body); - expect(response.status).toEqual(201); - }); - - it('should answer with status 409 when user name is already used', async () => { - const body = { - name: 'Vegata', - userClass: 'T3', - }; - await agent.post('/users').send(body); - const response = await agent.post('/users').send(body); - expect(response.status).toEqual(409); - }); - - it('should answer with status 400 when name is empty', async () => { - const body = { - name: '', - userClass: 'T3', - }; - await agent.post('/users').send(body); - const response = await agent.post('/users').send(body); - expect(response.status).toEqual(400); - }); - - it('should answer with status 400 when userClass is invalid', async () => { - const body = { - name: 'Vegata', - userClass: 'T35', - }; - await agent.post('/users').send(body); - const response = await agent.post('/users').send(body); - expect(response.status).toEqual(400); - }); -}); diff --git a/src/tests/units/QuestionsService.test.ts b/src/tests/units/QuestionsService.test.ts deleted file mode 100644 index b6235bd..0000000 --- a/src/tests/units/QuestionsService.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -/* eslint-disable no-undef */ -import faker from 'faker'; - -import * as questionsService from '../../services/questionsService'; -import * as questionsRepository from '../../repositories/questionsRepository'; - -describe('Questions Service', () => { - it('Should return id when question is created', async () => { - const question = faker.random.words(); - const student = faker.name.firstName(); - const userClass = 'T1'; - const tags = faker.random.word(); - - jest.spyOn(questionsRepository, 'newQuestion').mockImplementationOnce(async () => 1); - - const result = await questionsService.createQuestion(question, student, userClass, tags); - expect(result).toBeTruthy(); - }); - - it('Should return falsy when question is empty', async () => { - const question = ''; - const student = faker.name.firstName(); - const userClass = 'T1'; - const tags = faker.random.word(); - - jest.spyOn(questionsRepository, 'newQuestion').mockImplementationOnce(async () => 1); - - const result = await questionsService.createQuestion(question, student, userClass, tags); - expect(result).toBeFalsy(); - }); - - it('Should return falsy when student is empty', async () => { - const question = faker.random.words(); - const student = ''; - const userClass = 'T1'; - const tags = faker.random.word(); - - jest.spyOn(questionsRepository, 'newQuestion').mockImplementationOnce(async () => 1); - - const result = await questionsService.createQuestion(question, student, userClass, tags); - expect(result).toBeFalsy(); - }); - - it('Should return falsy when userClass is empty', async () => { - const question = faker.random.words(); - const student = faker.name.firstName(); - const userClass = ''; - const tags = faker.random.word(); - - jest.spyOn(questionsRepository, 'newQuestion').mockImplementationOnce(async () => 1); - - const result = await questionsService.createQuestion(question, student, userClass, tags); - expect(result).toBeFalsy(); - }); - - it('Should return falsy when userClass is invalid', async () => { - const question = faker.random.words(); - const student = faker.name.firstName(); - const userClass = 'T44'; - const tags = faker.random.word(); - - jest.spyOn(questionsRepository, 'newQuestion').mockImplementationOnce(async () => 1); - - const result = await questionsService.createQuestion(question, student, userClass, tags); - expect(result).toBeFalsy(); - }); - - it('Should return falsy when tags is empty', async () => { - const question = faker.random.words(); - const student = faker.name.firstName(); - const userClass = 'T44'; - const tags = ''; - - jest.spyOn(questionsRepository, 'newQuestion').mockImplementationOnce(async () => 1); - - const result = await questionsService.createQuestion(question, student, userClass, tags); - expect(result).toBeFalsy(); - }); - - it('Should return falsy when there is no unanswered questions to get', async () => { - jest.spyOn(questionsRepository, 'getUnansweredQuestions').mockImplementationOnce(async () => false); - - const result = await questionsService.getQuestions(); - expect(result).toBeFalsy(); - }); - - it('Should return falsy when unanswered questions are returned', async () => { - jest.spyOn(questionsRepository, 'getUnansweredQuestions').mockImplementationOnce(async () => []); - - const result = await questionsService.getQuestions(); - expect(result).toBeTruthy(); - }); - - it('Should return true when a question is answered', async () => { - const answer = faker.random.words(); - const questionId = Number(faker.datatype.number()); - const token = faker.datatype.uuid(); - - jest.spyOn(questionsRepository, 'checkQuestionId').mockImplementationOnce(async () => true); - jest.spyOn(questionsRepository, 'answerQuestion').mockImplementationOnce(async () => true); - - const result = await questionsService.answerAQuestion(answer, questionId, token); - expect(result).toBe(true); - }); - - it('Should return false when an invalid questionId is passed', async () => { - const answer = faker.random.words(); - const questionId = 0; - const token = faker.datatype.uuid(); - - jest.spyOn(questionsRepository, 'checkQuestionId').mockImplementationOnce(async () => false); - jest.spyOn(questionsRepository, 'answerQuestion').mockImplementationOnce(async () => true); - - const result = await questionsService.answerAQuestion(answer, questionId, token); - expect(result).toBe(false); - }); - - it('Should return falsy when answer is empty', async () => { - const answer = ''; - const questionId = Number(faker.datatype.number()); - const token = faker.datatype.uuid(); - - jest.spyOn(questionsRepository, 'checkQuestionId').mockImplementationOnce(async () => true); - jest.spyOn(questionsRepository, 'answerQuestion').mockImplementationOnce(async () => false); - - const result = await questionsService.answerAQuestion(answer, questionId, token); - expect(result).toBeFalsy(); - }); - - it('Should return false when questionId doesnt exists', async () => { - const questionId = Number(faker.datatype.number()); - - jest.spyOn(questionsRepository, 'getQuestion').mockImplementationOnce(async () => false); - - const result = await questionsService.getQuestionById(questionId); - expect(result).toBe(false); - }); - - it('Should return truthy when the question is returned', async () => { - const questionId = Number(faker.datatype.number()); - - jest.spyOn(questionsRepository, 'getQuestion').mockImplementationOnce(async () => true); - - const result = await questionsService.getQuestionById(questionId); - expect(result).toBeTruthy(); - }); -}); diff --git a/src/tests/units/UsersService.test.ts b/src/tests/units/UsersService.test.ts deleted file mode 100644 index c7e3ace..0000000 --- a/src/tests/units/UsersService.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* eslint-disable no-undef */ -import faker from 'faker'; - -import * as usersService from '../../services/usersService'; -import * as usersRepository from '../../repositories/usersRepository'; - -describe('Users Service', () => { - it('Should return token when user is created', async () => { - const name = faker.name.firstName(); - const userClass = 'T1'; - const token = faker.datatype.uuid(); - - jest.spyOn(usersRepository, 'checkName').mockImplementationOnce(async () => true); - jest.spyOn(usersRepository, 'createUser').mockImplementationOnce(async () => token); - - const result = await usersService.create(name, userClass); - expect(result).toBeTruthy(); - }); - - it('Should return falsy when name is empty', async () => { - const name = faker.name.firstName(); - const userClass = 'T1'; - const token = faker.datatype.uuid(); - - jest.spyOn(usersRepository, 'checkName').mockImplementationOnce(async () => false); - jest.spyOn(usersRepository, 'createUser').mockImplementationOnce(async () => token); - - const result = await usersService.create(name, userClass); - expect(result).toBeFalsy(); - }); - - it('Should return falsy when userClass is empty', async () => { - const name = faker.name.firstName(); - const userClass = ''; - const token = faker.datatype.uuid(); - - jest.spyOn(usersRepository, 'checkName').mockImplementationOnce(async () => false); - jest.spyOn(usersRepository, 'createUser').mockImplementationOnce(async () => token); - - const result = await usersService.create(name, userClass); - expect(result).toBeFalsy(); - }); - - it('Should return falsy when userClass is invalid', async () => { - const name = faker.name.firstName(); - const userClass = 'T44'; - const token = faker.datatype.uuid(); - - jest.spyOn(usersRepository, 'checkName').mockImplementationOnce(async () => false); - jest.spyOn(usersRepository, 'createUser').mockImplementationOnce(async () => token); - - const result = await usersService.create(name, userClass); - expect(result).toBeFalsy(); - }); -}); diff --git a/src/tests/utils/database.ts b/src/tests/utils/database.ts deleted file mode 100644 index f5add5b..0000000 --- a/src/tests/utils/database.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable import/no-extraneous-dependencies */ -import supertest from 'supertest'; -import connection from '../../database'; -import app from '../../app'; - -export const agent = supertest(app); - -export async function clearDatabase() { - await connection.query('TRUNCATE TABLE questions, students, classes RESTART IDENTITY'); -} - -export async function closeConnection() { - await connection.end(); -} diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index e728be7..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "compilerOptions": { - "noImplicitAny": true, - "esModuleInterop": true - } -} \ No newline at end of file