├── frontend ├── src │ ├── stylesheets │ │ ├── Header.css │ │ ├── index.css │ │ ├── FormView.css │ │ ├── App.css │ │ ├── QuizView.css │ │ └── Question.css │ ├── index.js │ ├── App.test.js │ ├── components │ │ ├── Search.js │ │ ├── Header.js │ │ ├── Question.js │ │ ├── FormView.js │ │ ├── QuestionView.js │ │ └── QuizView.js │ └── App.js ├── public │ ├── delete.png │ ├── favicon.ico │ ├── manifest.json │ ├── index.html │ ├── art.svg │ ├── geography.svg │ ├── sports.svg │ ├── entertainment.svg │ ├── history.svg │ └── science.svg ├── package.json └── README.md ├── .gitignore ├── backend ├── requirements.txt ├── test_flaskr.py ├── models.py ├── flaskr │ └── __init__.py ├── README.md └── trivia.psql └── README.md /frontend/src/stylesheets/Header.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/public/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alejandroq12/Full-Stack-API/HEAD/frontend/public/delete.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Alejandroq12/Full-Stack-API/HEAD/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | .form-view > label { 2 | padding: 0px 0px 10px; 3 | } 4 | 5 | #add-form { 6 | text-align: center; 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './stylesheets/index.css'; 4 | import App from './App'; 5 | 6 | ReactDOM.render(, document.getElementById('root')); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | __pycache__ 3 | venv 4 | 5 | # OS generated files # 6 | ###################### 7 | .DS_Store 8 | .DS_Store? 9 | ._* 10 | .Spotlight-V100 11 | .Trashes 12 | ehthumbs.db 13 | Thumbs.db 14 | env/ 15 | frontend/node_modules/ -------------------------------------------------------------------------------- /frontend/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | aniso8601==6.0.0 2 | Click==7.0 3 | Flask==1.0.3 4 | Flask-Cors==3.0.7 5 | Flask-RESTful==0.3.7 6 | Flask-SQLAlchemy==2.4.0 7 | itsdangerous==1.1.0 8 | Jinja2==2.10.1 9 | MarkupSafe==1.1.1 10 | psycopg2-binary==2.8.2 11 | pytz==2019.1 12 | six==1.12.0 13 | SQLAlchemy==1.3.4 14 | Werkzeug==0.15.5 -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | React App 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App-header { 2 | background-color: #222; 3 | color: white; 4 | display: flex; 5 | justify-content: space-between; 6 | height: 75px; 7 | padding: 20px; 8 | padding-right: 40%; 9 | padding-left: 5%; 10 | } 11 | 12 | .App-header > h1 { 13 | color: dodgerblue; 14 | } 15 | 16 | .App-intro { 17 | font-size: large; 18 | } 19 | 20 | h2 { 21 | text-align: center; 22 | } 23 | 24 | li { 25 | text-align: center; 26 | margin: 5px 0px; 27 | } 28 | 29 | ul { 30 | list-style-type: none; 31 | padding-inline-start: 0px; 32 | } 33 | 34 | form { 35 | display: flex; 36 | flex-direction: column; 37 | width: 80%; 38 | margin: auto; 39 | } -------------------------------------------------------------------------------- /frontend/src/components/Search.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class Search extends Component { 4 | state = { 5 | query: '', 6 | }; 7 | 8 | getInfo = (event) => { 9 | event.preventDefault(); 10 | this.props.submitSearch(this.state.query); 11 | }; 12 | 13 | handleInputChange = () => { 14 | this.setState({ 15 | query: this.search.value, 16 | }); 17 | }; 18 | 19 | render() { 20 | return ( 21 |
22 | (this.search = input)} 25 | onChange={this.handleInputChange} 26 | /> 27 | 28 |
29 | ); 30 | } 31 | } 32 | 33 | export default Search; 34 | -------------------------------------------------------------------------------- /frontend/src/stylesheets/QuizView.css: -------------------------------------------------------------------------------- 1 | .quiz-play-holder { 2 | width: 80%; 3 | margin-left: 10%; 4 | text-align: center; 5 | margin-top: 40px; 6 | } 7 | 8 | .category-holder { 9 | margin-top: 16px; 10 | font-size: 24px; 11 | } 12 | 13 | .choose-header { 14 | font-size: 34px; 15 | font-weight: bold; 16 | } 17 | 18 | .play-category:hover { 19 | color: dodgerblue; 20 | } 21 | 22 | .button { 23 | width: 100px; 24 | margin-top: 5px; 25 | background-color: dodgerblue; 26 | color: white; 27 | padding: 3px 5px; 28 | align-self: center; 29 | } 30 | 31 | div.button { 32 | margin-top: 20px; 33 | margin-left: calc(50% - 50px); 34 | } 35 | 36 | .quiz-question { 37 | margin: 12px; 38 | font-size: 24px; 39 | } 40 | 41 | .correct { 42 | color: green; 43 | } 44 | 45 | .wrong { 46 | color: red; 47 | } -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; 3 | import './stylesheets/App.css'; 4 | import FormView from './components/FormView'; 5 | import QuestionView from './components/QuestionView'; 6 | import Header from './components/Header'; 7 | import QuizView from './components/QuizView'; 8 | 9 | class App extends Component { 10 | render() { 11 | return ( 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | ); 24 | } 25 | } 26 | 27 | export default App; 28 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trivia-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "proxy": "http://127.0.0.1:5000/", 6 | "dependencies": { 7 | "corsproxy": "^1.5.0", 8 | "jquery": "^3.4.1", 9 | "react": "^16.8.6", 10 | "react-dom": "^16.8.6", 11 | "react-router-dom": "^5.0.0", 12 | "react-scripts": "3.0.1" 13 | }, 14 | "scripts": { 15 | "start": "node node_modules/react-scripts/scripts/start.js", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": "react-app" 22 | }, 23 | "browserslist": { 24 | "production": [ 25 | ">0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /frontend/src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import '../stylesheets/Header.css'; 3 | 4 | class Header extends Component { 5 | navTo(uri) { 6 | window.location.href = window.location.origin + uri; 7 | } 8 | 9 | render() { 10 | return ( 11 |
12 |

{ 14 | this.navTo(''); 15 | }} 16 | > 17 | Udacitrivia 18 |

19 |

{ 21 | this.navTo(''); 22 | }} 23 | > 24 | List 25 |

26 |

{ 28 | this.navTo('/add'); 29 | }} 30 | > 31 | Add 32 |

33 |

{ 35 | this.navTo('/play'); 36 | }} 37 | > 38 | Play 39 |

40 |
41 | ); 42 | } 43 | } 44 | 45 | export default Header; 46 | -------------------------------------------------------------------------------- /backend/test_flaskr.py: -------------------------------------------------------------------------------- 1 | import os 2 | import unittest 3 | import json 4 | from flask_sqlalchemy import SQLAlchemy 5 | 6 | from flaskr import create_app 7 | from models import setup_db, Question, Category 8 | 9 | 10 | class TriviaTestCase(unittest.TestCase): 11 | """This class represents the trivia test case""" 12 | 13 | def setUp(self): 14 | """Define test variables and initialize app.""" 15 | self.app = create_app() 16 | self.client = self.app.test_client 17 | self.database_name = "trivia_test" 18 | self.database_path = "postgres://{}/{}".format('localhost:5432', self.database_name) 19 | setup_db(self.app, self.database_path) 20 | 21 | # binds the app to the current context 22 | with self.app.app_context(): 23 | self.db = SQLAlchemy() 24 | self.db.init_app(self.app) 25 | # create all tables 26 | self.db.create_all() 27 | 28 | def tearDown(self): 29 | """Executed after reach test""" 30 | pass 31 | 32 | """ 33 | TODO 34 | Write at least one test for each test for successful operation and for expected errors. 35 | """ 36 | 37 | 38 | # Make the tests conveniently executable 39 | if __name__ == "__main__": 40 | unittest.main() -------------------------------------------------------------------------------- /frontend/src/stylesheets/Question.css: -------------------------------------------------------------------------------- 1 | .question-view { 2 | display: flex; 3 | flex-direction: row; 4 | } 5 | 6 | .categories-list { 7 | flex-basis: 25%; 8 | flex-shrink: 2; 9 | } 10 | 11 | .categories-list > form { 12 | width: 60%; 13 | } 14 | 15 | .questions-list { 16 | flex-basis: 75%; 17 | text-align: center; 18 | } 19 | 20 | .Question-holder { 21 | padding: 10px 20px; 22 | border: 2px black solid; 23 | text-align: left; 24 | margin: 7px; 25 | } 26 | 27 | .Question-status { 28 | display: flex; 29 | flex-direction: row; 30 | height: 30px; 31 | width: 200px; 32 | align-items: center; 33 | justify-content: space-between; 34 | } 35 | 36 | .Question { 37 | font-size: 21px; 38 | width: 100%; 39 | } 40 | 41 | .answer-holder { 42 | font-size: 25px; 43 | height: 25px; 44 | margin: 5px 0px; 45 | } 46 | 47 | .show-answer { 48 | width: 100px; 49 | margin-top: 5px; 50 | background-color: dodgerblue; 51 | color: white; 52 | padding: 3px 5px; 53 | margin-left: 0px !important; 54 | } 55 | 56 | span.page-num { 57 | margin: 5px; 58 | padding-bottom: 50px; 59 | /* color: blue; */ 60 | font-weight: bolder; 61 | text-decoration: underline; 62 | font-size: 16px; 63 | } 64 | 65 | span.page-num.active { 66 | color: blue; 67 | } 68 | 69 | img.category { 70 | width: 30px; 71 | } 72 | 73 | img.delete { 74 | width: 20px; 75 | } -------------------------------------------------------------------------------- /frontend/src/components/Question.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import '../stylesheets/Question.css'; 3 | 4 | class Question extends Component { 5 | constructor() { 6 | super(); 7 | this.state = { 8 | visibleAnswer: false, 9 | }; 10 | } 11 | 12 | flipVisibility() { 13 | this.setState({ visibleAnswer: !this.state.visibleAnswer }); 14 | } 15 | 16 | render() { 17 | const { question, answer, category, difficulty } = this.props; 18 | return ( 19 |
20 |
{question}
21 |
22 | {`${category.toLowerCase()}`} 27 |
Difficulty: {difficulty}
28 | delete this.props.questionAction('DELETE')} 33 | /> 34 |
35 |
this.flipVisibility()} 38 | > 39 | {this.state.visibleAnswer ? 'Hide' : 'Show'} Answer 40 |
41 |
42 | 47 | Answer: {answer} 48 | 49 |
50 |
51 | ); 52 | } 53 | } 54 | 55 | export default Question; 56 | -------------------------------------------------------------------------------- /frontend/public/art.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /backend/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | from sqlalchemy import Column, String, Integer, create_engine 3 | from flask_sqlalchemy import SQLAlchemy 4 | import json 5 | 6 | database_name = 'trivia' 7 | database_path = 'postgresql://{}/{}'.format('localhost:5432', database_name) 8 | 9 | db = SQLAlchemy() 10 | 11 | """ 12 | setup_db(app) 13 | binds a flask application and a SQLAlchemy service 14 | """ 15 | def setup_db(app, database_path=database_path): 16 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 17 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 18 | db.app = app 19 | db.init_app(app) 20 | db.create_all() 21 | 22 | """ 23 | Question 24 | 25 | """ 26 | class Question(db.Model): 27 | __tablename__ = 'questions' 28 | 29 | id = Column(Integer, primary_key=True) 30 | question = Column(String) 31 | answer = Column(String) 32 | category = Column(String) 33 | difficulty = Column(Integer) 34 | 35 | def __init__(self, question, answer, category, difficulty): 36 | self.question = question 37 | self.answer = answer 38 | self.category = category 39 | self.difficulty = difficulty 40 | 41 | def insert(self): 42 | db.session.add(self) 43 | db.session.commit() 44 | 45 | def update(self): 46 | db.session.commit() 47 | 48 | def delete(self): 49 | db.session.delete(self) 50 | db.session.commit() 51 | 52 | def format(self): 53 | return { 54 | 'id': self.id, 55 | 'question': self.question, 56 | 'answer': self.answer, 57 | 'category': self.category, 58 | 'difficulty': self.difficulty 59 | } 60 | 61 | """ 62 | Category 63 | 64 | """ 65 | class Category(db.Model): 66 | __tablename__ = 'categories' 67 | 68 | id = Column(Integer, primary_key=True) 69 | type = Column(String) 70 | 71 | def __init__(self, type): 72 | self.type = type 73 | 74 | def format(self): 75 | return { 76 | 'id': self.id, 77 | 'type': self.type 78 | } 79 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Frontend - Trivia API 2 | 3 | ## Getting Setup 4 | 5 | > _tip_: this frontend is designed to work with [Flask-based Backend](../backend) so it will not load successfully if the backend is not working or not connected. We recommend that you **stand up the backend first**, test using Postman or curl, update the endpoints in the frontend, and then the frontend should integrate smoothly. 6 | 7 | ### Installing Dependencies 8 | 9 | 1. **Installing Node and NPM** 10 | This project depends on Nodejs and Node Package Manager (NPM). Before continuing, you must download and install Node (the download includes NPM) from [https://nodejs.com/en/download](https://nodejs.org/en/download/). 11 | 12 | 2. **Installing project dependencies** 13 | This project uses NPM to manage software dependencies. NPM Relies on the package.json file located in the `frontend` directory of this repository. After cloning, open your terminal and run: 14 | 15 | ```bash 16 | npm install 17 | ``` 18 | 19 | > _tip_: `npm i`is shorthand for `npm install`` 20 | 21 | ## Required Tasks 22 | 23 | ### Running Your Frontend in Dev Mode 24 | 25 | The frontend app was built using create-react-app. In order to run the app in development mode use `npm start`. You can change the script in the `package.json` file. 26 | 27 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. The page will reload if you make edits. 28 | 29 | ```bash 30 | npm start 31 | ``` 32 | 33 | ### Request Formatting 34 | 35 | The frontend should be fairly straightforward and disgestible. I'll primarily work within the `components` folder in order to understand. While working on my backend request handling and response formatting, I can reference the frontend to view how it parses the responses. 36 | 37 | After I complete my endpoints, I must ensure I return to the frontend to confirm my API handles requests and responses appropriately: 38 | 39 | - Endpoints defined as expected by the frontend 40 | - Response body provided as expected by the frontend 41 | 42 | ### Optional: Game Play Mechanics 43 | 44 | Currently, when a user plays the game they play up to five questions of the chosen category. If there are fewer than five questions in a category, the game will end when there are no more questions in that category. 45 | 46 | I can optionally update this game play to increase the number of questions or whatever other game mechanics I decide. I will make sure to specify the new mechanics of the game in the README. 47 | 48 | 49 | -------------------------------------------------------------------------------- /frontend/public/geography.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /frontend/public/sports.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /frontend/public/entertainment.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 9 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | import '../stylesheets/FormView.css'; 4 | 5 | class FormView extends Component { 6 | constructor(props) { 7 | super(); 8 | this.state = { 9 | question: '', 10 | answer: '', 11 | difficulty: 1, 12 | category: 1, 13 | categories: {}, 14 | }; 15 | } 16 | 17 | componentDidMount() { 18 | $.ajax({ 19 | url: `/categories`, //TODO: update request URL 20 | type: 'GET', 21 | success: (result) => { 22 | this.setState({ categories: result.categories }); 23 | return; 24 | }, 25 | error: (error) => { 26 | alert('Unable to load categories. Please try your request again'); 27 | return; 28 | }, 29 | }); 30 | } 31 | 32 | submitQuestion = (event) => { 33 | event.preventDefault(); 34 | $.ajax({ 35 | url: '/questions', //TODO: update request URL 36 | type: 'POST', 37 | dataType: 'json', 38 | contentType: 'application/json', 39 | data: JSON.stringify({ 40 | question: this.state.question, 41 | answer: this.state.answer, 42 | difficulty: this.state.difficulty, 43 | category: this.state.category, 44 | }), 45 | xhrFields: { 46 | withCredentials: true, 47 | }, 48 | crossDomain: true, 49 | success: (result) => { 50 | document.getElementById('add-question-form').reset(); 51 | return; 52 | }, 53 | error: (error) => { 54 | alert('Unable to add question. Please try your request again'); 55 | return; 56 | }, 57 | }); 58 | }; 59 | 60 | handleChange = (event) => { 61 | this.setState({ [event.target.name]: event.target.value }); 62 | }; 63 | 64 | render() { 65 | return ( 66 |
67 |

Add a New Trivia Question

68 |
73 | 77 | 81 | 91 | 103 | 104 |
105 |
106 | ); 107 | } 108 | } 109 | 110 | export default FormView; 111 | -------------------------------------------------------------------------------- /backend/flaskr/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from flask import Flask, request, abort, jsonify 4 | from flask_sqlalchemy import SQLAlchemy 5 | from flask_cors import CORS 6 | import random 7 | 8 | from models import setup_db, Question, Category 9 | 10 | QUESTIONS_PER_PAGE = 10 11 | 12 | def create_app(test_config=None): 13 | # create and configure the app 14 | app = Flask(__name__) 15 | setup_db(app) 16 | 17 | """ 18 | @TODO: Set up CORS. Allow '*' for origins. Delete the sample route after completing the TODOs 19 | """ 20 | CORS(app, resources={'/': {'origins': '*'}}) 21 | 22 | """ 23 | @TODO: Use the after_request decorator to set Access-Control-Allow 24 | """ 25 | 26 | """ 27 | @TODO: 28 | Create an endpoint to handle GET requests 29 | for all available categories. 30 | """ 31 | 32 | 33 | """ 34 | @TODO: 35 | Create an endpoint to handle GET requests for questions, 36 | including pagination (every 10 questions). 37 | This endpoint should return a list of questions, 38 | number of total questions, current category, categories. 39 | 40 | TEST: At this point, when you start the application 41 | you should see questions and categories generated, 42 | ten questions per page and pagination at the bottom of the screen for three pages. 43 | Clicking on the page numbers should update the questions. 44 | """ 45 | 46 | """ 47 | @TODO: 48 | Create an endpoint to DELETE question using a question ID. 49 | 50 | TEST: When you click the trash icon next to a question, the question will be removed. 51 | This removal will persist in the database and when you refresh the page. 52 | """ 53 | 54 | """ 55 | @TODO: 56 | Create an endpoint to POST a new question, 57 | which will require the question and answer text, 58 | category, and difficulty score. 59 | 60 | TEST: When you submit a question on the "Add" tab, 61 | the form will clear and the question will appear at the end of the last page 62 | of the questions list in the "List" tab. 63 | """ 64 | 65 | """ 66 | @TODO: 67 | Create a POST endpoint to get questions based on a search term. 68 | It should return any questions for whom the search term 69 | is a substring of the question. 70 | 71 | TEST: Search by any phrase. The questions list will update to include 72 | only question that include that string within their question. 73 | Try using the word "title" to start. 74 | """ 75 | 76 | """ 77 | @TODO: 78 | Create a GET endpoint to get questions based on category. 79 | 80 | TEST: In the "List" tab / main screen, clicking on one of the 81 | categories in the left column will cause only questions of that 82 | category to be shown. 83 | """ 84 | 85 | """ 86 | @TODO: 87 | Create a POST endpoint to get questions to play the quiz. 88 | This endpoint should take category and previous question parameters 89 | and return a random questions within the given category, 90 | if provided, and that is not one of the previous questions. 91 | 92 | TEST: In the "Play" tab, after a user selects "All" or a category, 93 | one question at a time is displayed, the user is allowed to answer 94 | and shown whether they were correct or not. 95 | """ 96 | 97 | """ 98 | @TODO: 99 | Create error handlers for all expected errors 100 | including 404 and 422. 101 | """ 102 | 103 | return app 104 | -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Backend - Trivia API 2 | 3 | ## Setting up the Backend 4 | 5 | ### Install Dependencies 6 | 7 | 1. **Python 3.7** - Follow instructions to install the latest version of python for your platform in the [python docs](https://docs.python.org/3/using/unix.html#getting-and-installing-the-latest-version-of-python) 8 | 9 | 2. **Virtual Environment** - I recommend working within a virtual environment whenever using Python for projects. This keeps dependencies for each project separate and organized. Instructions for setting up a virual environment for my platform can be found in the [python docs](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/) 10 | 11 | 3. **PIP Dependencies** - Once your virtual environment is setup and running, install the required dependencies by navigating to the `/backend` directory and running: 12 | 13 | ```bash 14 | pip install -r requirements.txt 15 | ``` 16 | 17 | #### Key Pip Dependencies 18 | 19 | - [Flask](http://flask.pocoo.org/) is a lightweight backend microservices framework. Flask is required to handle requests and responses. 20 | 21 | - [SQLAlchemy](https://www.sqlalchemy.org/) is the Python SQL toolkit and ORM we'll use to handle the lightweight SQL database. You'll primarily work in `app.py`and can reference `models.py`. 22 | 23 | - [Flask-CORS](https://flask-cors.readthedocs.io/en/latest/#) is the extension we'll use to handle cross-origin requests from our frontend server. 24 | 25 | ### Set up the Database 26 | 27 | With Postgres running, create a `trivia` database: 28 | 29 | ```bash 30 | createdb trivia 31 | ``` 32 | 33 | Populate the database using the `trivia.psql` file provided. From the `backend` folder in terminal run: 34 | 35 | ```bash 36 | psql trivia < trivia.psql 37 | ``` 38 | 39 | ### Run the Server 40 | 41 | From within the `./src` directory first ensure you are working using your created virtual environment. 42 | 43 | To run the server, execute: 44 | 45 | ```bash 46 | flask run --reload 47 | ``` 48 | 49 | The `--reload` flag will detect file changes and restart the server automatically. 50 | 51 | ## To Do Tasks 52 | 53 | These are the files I'd want to edit in the backend: 54 | 55 | 1. `backend/flaskr/__init__.py` 56 | 2. `backend/test_flaskr.py` 57 | 58 | For each endpoint, I am expected to define the endpoint and response data. 59 | 60 | 1. Use Flask-CORS to enable cross-domain requests and set response headers. 61 | 2. Create an endpoint to handle `GET` requests for questions, including pagination (every 10 questions). This endpoint should return a list of questions, number of total questions, current category, categories. 62 | 3. Create an endpoint to handle `GET` requests for all available categories. 63 | 4. Create an endpoint to `DELETE` a question using a question `ID`. 64 | 5. Create an endpoint to `POST` a new question, which will require the question and answer text, category, and difficulty score. 65 | 6. Create a `POST` endpoint to get questions based on category. 66 | 7. Create a `POST` endpoint to get questions based on a search term. It should return any questions for whom the search term is a substring of the question. 67 | 8. Create a `POST` endpoint to get questions to play the quiz. This endpoint should take a category and previous question parameters and return a random questions within the given category, if provided, and that is not one of the previous questions. 68 | 9. Create error handlers for all expected errors including 400, 404, 422, and 500. 69 | 70 | ## Documenting my Endpoints 71 | 72 | I will need to provide detailed documentation of my API endpoints including the URL, request parameters, and the response body. Use the example below as a reference. 73 | 74 | ### Documentation Example 75 | 76 | `GET '/api/v1.0/categories'` 77 | 78 | - Fetches a dictionary of categories in which the keys are the ids and the value is the corresponding string of the category 79 | - Request Arguments: None 80 | - Returns: An object with a single key, `categories`, that contains an object of `id: category_string` key: value pairs. 81 | 82 | ```json 83 | { 84 | "1": "Science", 85 | "2": "Art", 86 | "3": "Geography", 87 | "4": "History", 88 | "5": "Entertainment", 89 | "6": "Sports" 90 | } 91 | ``` 92 | 93 | ## Testing 94 | 95 | Write at least one test for the success and at least one error behavior of each endpoint using the unittest library. 96 | 97 | To deploy the tests, run 98 | 99 | ```bash 100 | dropdb trivia_test 101 | createdb trivia_test 102 | psql trivia_test < trivia.psql 103 | python test_flaskr.py 104 | ``` 105 | -------------------------------------------------------------------------------- /frontend/public/history.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 41 | 43 | 44 | 46 | image/svg+xml 47 | 49 | 50 | 51 | 52 | 53 | 57 | 62 | 68 | 74 | 80 | 86 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /frontend/src/components/QuestionView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import '../stylesheets/App.css'; 3 | import Question from './Question'; 4 | import Search from './Search'; 5 | import $ from 'jquery'; 6 | 7 | class QuestionView extends Component { 8 | constructor() { 9 | super(); 10 | this.state = { 11 | questions: [], 12 | page: 1, 13 | totalQuestions: 0, 14 | categories: {}, 15 | currentCategory: null, 16 | }; 17 | } 18 | 19 | componentDidMount() { 20 | this.getQuestions(); 21 | } 22 | 23 | getQuestions = () => { 24 | $.ajax({ 25 | url: `/questions?page=${this.state.page}`, //TODO: update request URL 26 | type: 'GET', 27 | success: (result) => { 28 | this.setState({ 29 | questions: result.questions, 30 | totalQuestions: result.total_questions, 31 | categories: result.categories, 32 | currentCategory: result.current_category, 33 | }); 34 | return; 35 | }, 36 | error: (error) => { 37 | alert('Unable to load questions. Please try your request again'); 38 | return; 39 | }, 40 | }); 41 | }; 42 | 43 | selectPage(num) { 44 | this.setState({ page: num }, () => this.getQuestions()); 45 | } 46 | 47 | createPagination() { 48 | let pageNumbers = []; 49 | let maxPage = Math.ceil(this.state.totalQuestions / 10); 50 | for (let i = 1; i <= maxPage; i++) { 51 | pageNumbers.push( 52 | { 56 | this.selectPage(i); 57 | }} 58 | > 59 | {i} 60 | 61 | ); 62 | } 63 | return pageNumbers; 64 | } 65 | 66 | getByCategory = (id) => { 67 | $.ajax({ 68 | url: `/categories/${id}/questions`, //TODO: update request URL 69 | type: 'GET', 70 | success: (result) => { 71 | this.setState({ 72 | questions: result.questions, 73 | totalQuestions: result.total_questions, 74 | currentCategory: result.current_category, 75 | }); 76 | return; 77 | }, 78 | error: (error) => { 79 | alert('Unable to load questions. Please try your request again'); 80 | return; 81 | }, 82 | }); 83 | }; 84 | 85 | submitSearch = (searchTerm) => { 86 | $.ajax({ 87 | url: `/questions`, //TODO: update request URL 88 | type: 'POST', 89 | dataType: 'json', 90 | contentType: 'application/json', 91 | data: JSON.stringify({ searchTerm: searchTerm }), 92 | xhrFields: { 93 | withCredentials: true, 94 | }, 95 | crossDomain: true, 96 | success: (result) => { 97 | this.setState({ 98 | questions: result.questions, 99 | totalQuestions: result.total_questions, 100 | currentCategory: result.current_category, 101 | }); 102 | return; 103 | }, 104 | error: (error) => { 105 | alert('Unable to load questions. Please try your request again'); 106 | return; 107 | }, 108 | }); 109 | }; 110 | 111 | questionAction = (id) => (action) => { 112 | if (action === 'DELETE') { 113 | if (window.confirm('are you sure you want to delete the question?')) { 114 | $.ajax({ 115 | url: `/questions/${id}`, //TODO: update request URL 116 | type: 'DELETE', 117 | success: (result) => { 118 | this.getQuestions(); 119 | }, 120 | error: (error) => { 121 | alert('Unable to load questions. Please try your request again'); 122 | return; 123 | }, 124 | }); 125 | } 126 | } 127 | }; 128 | 129 | render() { 130 | return ( 131 |
132 |
133 |

{ 135 | this.getQuestions(); 136 | }} 137 | > 138 | Categories 139 |

140 |
    141 | {Object.keys(this.state.categories).map((id) => ( 142 |
  • { 145 | this.getByCategory(id); 146 | }} 147 | > 148 | {this.state.categories[id]} 149 | {`${this.state.categories[id].toLowerCase()}`} 154 |
  • 155 | ))} 156 |
157 | 158 |
159 |
160 |

Questions

161 | {this.state.questions.map((q, ind) => ( 162 | 170 | ))} 171 |
{this.createPagination()}
172 |
173 |
174 | ); 175 | } 176 | } 177 | 178 | export default QuestionView; 179 | -------------------------------------------------------------------------------- /frontend/public/science.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.15, written by Peter Selinger 2001-2017 9 | 10 | 12 | 68 | 69 | 71 | 73 | 77 | 79 | 80 | 82 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /backend/trivia.psql: -------------------------------------------------------------------------------- 1 | -- 2 | -- PostgreSQL database dump 3 | -- 4 | 5 | -- Dumped from database version 11.3 6 | -- Dumped by pg_dump version 11.3 7 | 8 | SET statement_timeout = 0; 9 | SET lock_timeout = 0; 10 | SET idle_in_transaction_session_timeout = 0; 11 | SET client_encoding = 'UTF8'; 12 | SET standard_conforming_strings = on; 13 | SELECT pg_catalog.set_config('search_path', '', false); 14 | SET check_function_bodies = false; 15 | SET xmloption = content; 16 | SET client_min_messages = warning; 17 | SET row_security = off; 18 | 19 | SET default_tablespace = ''; 20 | 21 | SET default_with_oids = false; 22 | 23 | -- 24 | -- Name: categories; Type: TABLE; Schema: public; Owner: student 25 | -- 26 | 27 | CREATE TABLE public.categories ( 28 | id integer NOT NULL, 29 | type text 30 | ); 31 | 32 | 33 | ALTER TABLE public.categories OWNER TO student; 34 | 35 | -- 36 | -- Name: categories_id_seq; Type: SEQUENCE; Schema: public; Owner: student 37 | -- 38 | 39 | CREATE SEQUENCE public.categories_id_seq 40 | AS integer 41 | START WITH 1 42 | INCREMENT BY 1 43 | NO MINVALUE 44 | NO MAXVALUE 45 | CACHE 1; 46 | 47 | 48 | ALTER TABLE public.categories_id_seq OWNER TO student; 49 | 50 | -- 51 | -- Name: categories_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: student 52 | -- 53 | 54 | ALTER SEQUENCE public.categories_id_seq OWNED BY public.categories.id; 55 | 56 | 57 | -- 58 | -- Name: questions; Type: TABLE; Schema: public; Owner: student 59 | -- 60 | 61 | CREATE TABLE public.questions ( 62 | id integer NOT NULL, 63 | question text, 64 | answer text, 65 | difficulty integer, 66 | category integer 67 | ); 68 | 69 | 70 | ALTER TABLE public.questions OWNER TO student; 71 | 72 | -- 73 | -- Name: questions_id_seq; Type: SEQUENCE; Schema: public; Owner: student 74 | -- 75 | 76 | CREATE SEQUENCE public.questions_id_seq 77 | AS integer 78 | START WITH 1 79 | INCREMENT BY 1 80 | NO MINVALUE 81 | NO MAXVALUE 82 | CACHE 1; 83 | 84 | 85 | ALTER TABLE public.questions_id_seq OWNER TO student; 86 | 87 | -- 88 | -- Name: questions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: student 89 | -- 90 | 91 | ALTER SEQUENCE public.questions_id_seq OWNED BY public.questions.id; 92 | 93 | 94 | -- 95 | -- Name: categories id; Type: DEFAULT; Schema: public; Owner: student 96 | -- 97 | 98 | ALTER TABLE ONLY public.categories ALTER COLUMN id SET DEFAULT nextval('public.categories_id_seq'::regclass); 99 | 100 | 101 | -- 102 | -- Name: questions id; Type: DEFAULT; Schema: public; Owner: student 103 | -- 104 | 105 | ALTER TABLE ONLY public.questions ALTER COLUMN id SET DEFAULT nextval('public.questions_id_seq'::regclass); 106 | 107 | 108 | -- 109 | -- Data for Name: categories; Type: TABLE DATA; Schema: public; Owner: student 110 | -- 111 | 112 | COPY public.categories (id, type) FROM stdin; 113 | 1 Science 114 | 2 Art 115 | 3 Geography 116 | 4 History 117 | 5 Entertainment 118 | 6 Sports 119 | \. 120 | 121 | 122 | -- 123 | -- Data for Name: questions; Type: TABLE DATA; Schema: public; Owner: student 124 | -- 125 | 126 | COPY public.questions (id, question, answer, difficulty, category) FROM stdin; 127 | 5 Whose autobiography is entitled 'I Know Why the Caged Bird Sings'? Maya Angelou 2 4 128 | 9 What boxer's original name is Cassius Clay? Muhammad Ali 1 4 129 | 2 What movie earned Tom Hanks his third straight Oscar nomination, in 1996? Apollo 13 4 5 130 | 4 What actor did author Anne Rice first denounce, then praise in the role of her beloved Lestat? Tom Cruise 4 5 131 | 6 What was the title of the 1990 fantasy directed by Tim Burton about a young man with multi-bladed appendages? Edward Scissorhands 3 5 132 | 10 Which is the only team to play in every soccer World Cup tournament? Brazil 3 6 133 | 11 Which country won the first ever soccer World Cup in 1930? Uruguay 4 6 134 | 12 Who invented Peanut Butter? George Washington Carver 2 4 135 | 13 What is the largest lake in Africa? Lake Victoria 2 3 136 | 14 In which royal palace would you find the Hall of Mirrors? The Palace of Versailles 3 3 137 | 15 The Taj Mahal is located in which Indian city? Agra 2 3 138 | 16 Which Dutch graphic artist–initials M C was a creator of optical illusions? Escher 1 2 139 | 17 La Giaconda is better known as what? Mona Lisa 3 2 140 | 18 How many paintings did Van Gogh sell in his lifetime? One 4 2 141 | 19 Which American artist was a pioneer of Abstract Expressionism, and a leading exponent of action painting? Jackson Pollock 2 2 142 | 20 What is the heaviest organ in the human body? The Liver 4 1 143 | 21 Who discovered penicillin? Alexander Fleming 3 1 144 | 22 Hematology is a branch of medicine involving the study of what? Blood 4 1 145 | 23 Which dung beetle was worshipped by the ancient Egyptians? Scarab 4 4 146 | \. 147 | 148 | 149 | -- 150 | -- Name: categories_id_seq; Type: SEQUENCE SET; Schema: public; Owner: student 151 | -- 152 | 153 | SELECT pg_catalog.setval('public.categories_id_seq', 6, true); 154 | 155 | 156 | -- 157 | -- Name: questions_id_seq; Type: SEQUENCE SET; Schema: public; Owner: student 158 | -- 159 | 160 | SELECT pg_catalog.setval('public.questions_id_seq', 23, true); 161 | 162 | 163 | -- 164 | -- Name: categories categories_pkey; Type: CONSTRAINT; Schema: public; Owner: student 165 | -- 166 | 167 | ALTER TABLE ONLY public.categories 168 | ADD CONSTRAINT categories_pkey PRIMARY KEY (id); 169 | 170 | 171 | -- 172 | -- Name: questions questions_pkey; Type: CONSTRAINT; Schema: public; Owner: student 173 | -- 174 | 175 | ALTER TABLE ONLY public.questions 176 | ADD CONSTRAINT questions_pkey PRIMARY KEY (id); 177 | 178 | 179 | -- 180 | -- Name: questions category; Type: FK CONSTRAINT; Schema: public; Owner: student 181 | -- 182 | 183 | ALTER TABLE ONLY public.questions 184 | ADD CONSTRAINT category FOREIGN KEY (category) REFERENCES public.categories(id) ON UPDATE CASCADE ON DELETE SET NULL; 185 | 186 | 187 | -- 188 | -- PostgreSQL database dump complete 189 | -- 190 | 191 | -------------------------------------------------------------------------------- /frontend/src/components/QuizView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | import '../stylesheets/QuizView.css'; 4 | 5 | const questionsPerPlay = 5; 6 | 7 | class QuizView extends Component { 8 | constructor(props) { 9 | super(); 10 | this.state = { 11 | quizCategory: null, 12 | previousQuestions: [], 13 | showAnswer: false, 14 | categories: {}, 15 | numCorrect: 0, 16 | currentQuestion: {}, 17 | guess: '', 18 | forceEnd: false, 19 | }; 20 | } 21 | 22 | componentDidMount() { 23 | $.ajax({ 24 | url: `/categories`, //TODO: update request URL 25 | type: 'GET', 26 | success: (result) => { 27 | this.setState({ categories: result.categories }); 28 | return; 29 | }, 30 | error: (error) => { 31 | alert('Unable to load categories. Please try your request again'); 32 | return; 33 | }, 34 | }); 35 | } 36 | 37 | selectCategory = ({ type, id = 0 }) => { 38 | this.setState({ quizCategory: { type, id } }, this.getNextQuestion); 39 | }; 40 | 41 | handleChange = (event) => { 42 | this.setState({ [event.target.name]: event.target.value }); 43 | }; 44 | 45 | getNextQuestion = () => { 46 | const previousQuestions = [...this.state.previousQuestions]; 47 | if (this.state.currentQuestion.id) { 48 | previousQuestions.push(this.state.currentQuestion.id); 49 | } 50 | 51 | $.ajax({ 52 | url: '/quizzes', //TODO: update request URL 53 | type: 'POST', 54 | dataType: 'json', 55 | contentType: 'application/json', 56 | data: JSON.stringify({ 57 | previous_questions: previousQuestions, 58 | quiz_category: this.state.quizCategory, 59 | }), 60 | xhrFields: { 61 | withCredentials: true, 62 | }, 63 | crossDomain: true, 64 | success: (result) => { 65 | this.setState({ 66 | showAnswer: false, 67 | previousQuestions: previousQuestions, 68 | currentQuestion: result.question, 69 | guess: '', 70 | forceEnd: result.question ? false : true, 71 | }); 72 | return; 73 | }, 74 | error: (error) => { 75 | alert('Unable to load question. Please try your request again'); 76 | return; 77 | }, 78 | }); 79 | }; 80 | 81 | submitGuess = (event) => { 82 | event.preventDefault(); 83 | let evaluate = this.evaluateAnswer(); 84 | this.setState({ 85 | numCorrect: !evaluate ? this.state.numCorrect : this.state.numCorrect + 1, 86 | showAnswer: true, 87 | }); 88 | }; 89 | 90 | restartGame = () => { 91 | this.setState({ 92 | quizCategory: null, 93 | previousQuestions: [], 94 | showAnswer: false, 95 | numCorrect: 0, 96 | currentQuestion: {}, 97 | guess: '', 98 | forceEnd: false, 99 | }); 100 | }; 101 | 102 | renderPrePlay() { 103 | return ( 104 |
105 |
Choose Category
106 |
107 |
108 | ALL 109 |
110 | {Object.keys(this.state.categories).map((id) => { 111 | return ( 112 |
117 | this.selectCategory({ type: this.state.categories[id], id }) 118 | } 119 | > 120 | {this.state.categories[id]} 121 |
122 | ); 123 | })} 124 |
125 |
126 | ); 127 | } 128 | 129 | renderFinalScore() { 130 | return ( 131 |
132 |
133 | Your Final Score is {this.state.numCorrect} 134 |
135 |
136 | Play Again? 137 |
138 |
139 | ); 140 | } 141 | 142 | evaluateAnswer = () => { 143 | const formatGuess = this.state.guess 144 | // eslint-disable-next-line 145 | .replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g, '') 146 | .toLowerCase(); 147 | const answerArray = this.state.currentQuestion.answer 148 | .toLowerCase() 149 | .split(' '); 150 | return answerArray.every((el) => formatGuess.includes(el)); 151 | }; 152 | 153 | renderCorrectAnswer() { 154 | let evaluate = this.evaluateAnswer(); 155 | return ( 156 |
157 |
158 | {this.state.currentQuestion.question} 159 |
160 |
161 | {evaluate ? 'You were correct!' : 'You were incorrect'} 162 |
163 |
{this.state.currentQuestion.answer}
164 |
165 | {' '} 166 | Next Question{' '} 167 |
168 |
169 | ); 170 | } 171 | 172 | renderPlay() { 173 | return this.state.previousQuestions.length === questionsPerPlay || 174 | this.state.forceEnd ? ( 175 | this.renderFinalScore() 176 | ) : this.state.showAnswer ? ( 177 | this.renderCorrectAnswer() 178 | ) : ( 179 |
180 |
181 | {this.state.currentQuestion.question} 182 |
183 |
184 | 185 | 190 |
191 |
192 | ); 193 | } 194 | 195 | render() { 196 | return this.state.quizCategory ? this.renderPlay() : this.renderPrePlay(); 197 | } 198 | } 199 | 200 | export default QuizView; 201 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # API Development and Documentation Final Project 2 | 3 | ## Trivia App 4 | 5 | A bunch of team members got the idea to hold trivia on a regular basis and created a webpage to manage the trivia app and play the game, but their API experience is limited and still needs to be built out. 6 | 7 | That's where I come in! Help them finish the trivia app so they can start holding trivia and seeing who's the most knowledgeable of the bunch. The application must: 8 | 9 | 1. Display questions - both all questions and by category. Questions should show the question, category and difficulty rating by default and can show/hide the answer. 10 | 2. Delete questions. 11 | 3. Add questions and require that they include question and answer text. 12 | 4. Search for questions based on a text query string. 13 | 5. Play the quiz game, randomizing either all questions or within a specific category. 14 | 15 | Completing this trivia app will give me the ability to structure plan, implement, and test an API - skills essential for enabling your future applications to communicate with others. 16 | 17 | ## About the Stack 18 | 19 | ### Backend 20 | 21 | The [backend](./backend/README.md) directory contains a partially completed Flask and SQLAlchemy server. I will work primarily in `__init__.py` to define my endpoints and can reference models.py for DB and SQLAlchemy setup. These are the files I'd want to edit in the backend: 22 | 23 | 1. `backend/flaskr/__init__.py` 24 | 2. `backend/test_flaskr.py` 25 | 26 | > View the [Backend README](./backend/README.md) for more details. 27 | 28 | ### Frontend 29 | 30 | The [frontend](./frontend/README.md) directory contains a complete React frontend to consume the data from the Flask server. 31 | 32 | 1. What are the end points and HTTP methods the frontend is expecting to consume? 33 | 2. How are the requests from the frontend formatted? Are they expecting certain parameters or payloads? 34 | 35 | I must pay special attention to what data the frontend is expecting from each API response to help guide how to format my API specially here: 36 | 37 | 1. `frontend/src/components/QuestionView.js` 38 | 2. `frontend/src/components/FormView.js` 39 | 3. `frontend/src/components/QuizView.js` 40 | 41 | 42 | > View the [Frontend README](./frontend/README.md) for more details. 43 | 44 | 45 | 46 | 70 | 71 |
72 | 73 | logo 74 |
75 | 76 |

Microverse README Template

77 | 78 |
79 | 80 | 81 | 82 | # 📗 Table of Contents 83 | 84 | - [📖 About the Project](#about-project) 85 | - [🛠 Built With](#built-with) 86 | - [Tech Stack](#tech-stack) 87 | - [Key Features](#key-features) 88 | - [🚀 Live Demo](#live-demo) 89 | - [💻 Getting Started](#getting-started) 90 | - [Setup](#setup) 91 | - [Prerequisites](#prerequisites) 92 | - [Install](#install) 93 | - [Usage](#usage) 94 | - [Run tests](#run-tests) 95 | - [Deployment](#triangular_flag_on_post-deployment) 96 | - [👥 Authors](#authors) 97 | - [🔭 Future Features](#future-features) 98 | - [🤝 Contributing](#contributing) 99 | - [⭐️ Show your support](#support) 100 | - [🙏 Acknowledgements](#acknowledgements) 101 | - [❓ FAQ (OPTIONAL)](#faq) 102 | - [📝 License](#license) 103 | 104 | 105 | 106 | # 📖 [your_project_name] 107 | 108 | > Describe your project in 1 or 2 sentences. 109 | 110 | **[your_project__name]** is a... 111 | 112 | ## 🛠 Built With 113 | 114 | ### Tech Stack 115 | 116 | > Describe the tech stack and include only the relevant sections that apply to your project. 117 | 118 |
119 | Client 120 | 123 |
124 | 125 |
126 | Server 127 | 130 |
131 | 132 |
133 | Database 134 | 137 |
138 | 139 | 140 | 141 | ### Key Features 142 | 143 | > Describe between 1-3 key features of the application. 144 | 145 | - **[key_feature_1]** 146 | - **[key_feature_2]** 147 | - **[key_feature_3]** 148 | 149 |

(back to top)

150 | 151 | 152 | 153 | ## 🚀 Live Demo 154 | 155 | > Add a link to your deployed project. 156 | 157 | - [Live Demo Link](https://yourdeployedapplicationlink.com) 158 | 159 |

(back to top)

160 | 161 | 162 | 163 | ## 💻 Getting Started 164 | 165 | > Describe how a new developer could make use of your project. 166 | 167 | To get a local copy up and running, follow these steps. 168 | 169 | ### Prerequisites 170 | 171 | In order to run this project you need: 172 | 173 | 180 | 181 | ### Setup 182 | 183 | Clone this repository to your desired folder: 184 | 185 | 193 | 194 | ### Install 195 | 196 | Install this project with: 197 | 198 | 206 | 207 | ### Usage 208 | 209 | To run the project, execute the following command: 210 | 211 | 218 | 219 | ### Run tests 220 | 221 | To run tests, run the following command: 222 | 223 | 230 | 231 | ### Deployment 232 | 233 | You can deploy this project using: 234 | 235 | 242 | 243 |

(back to top)

244 | 245 | 246 | 247 | ## 👥 Authors 248 | 249 | > Mention all of the collaborators of this project. 250 | 251 | 👤 **Author1** 252 | 253 | - GitHub: [@githubhandle](https://github.com/githubhandle) 254 | - Twitter: [@twitterhandle](https://twitter.com/twitterhandle) 255 | - LinkedIn: [LinkedIn](https://linkedin.com/in/linkedinhandle) 256 | 257 | 👤 **Author2** 258 | 259 | - GitHub: [@githubhandle](https://github.com/githubhandle) 260 | - Twitter: [@twitterhandle](https://twitter.com/twitterhandle) 261 | - LinkedIn: [LinkedIn](https://linkedin.com/in/linkedinhandle) 262 | 263 |

(back to top)

264 | 265 | 266 | 267 | ## 🔭 Future Features 268 | 269 | > Describe 1 - 3 features you will add to the project. 270 | 271 | - [ ] **[new_feature_1]** 272 | - [ ] **[new_feature_2]** 273 | - [ ] **[new_feature_3]** 274 | 275 |

(back to top)

276 | 277 | 278 | 279 | ## 🤝 Contributing 280 | 281 | Contributions, issues, and feature requests are welcome! 282 | 283 | Feel free to check the [issues page](../../issues/). 284 | 285 |

(back to top)

286 | 287 | 288 | 289 | ## ⭐️ Show your support 290 | 291 | > Write a message to encourage readers to support your project 292 | 293 | If you like this project... 294 | 295 |

(back to top)

296 | 297 | 298 | 299 | ## 🙏 Acknowledgments 300 | 301 | > Give credit to everyone who inspired your codebase. 302 | 303 | I would like to thank... 304 | 305 |

(back to top)

306 | 307 | 308 | 309 | ## ❓ FAQ (OPTIONAL) 310 | 311 | > Add at least 2 questions new developers would ask when they decide to use your project. 312 | 313 | - **[Question_1]** 314 | 315 | - [Answer_1] 316 | 317 | - **[Question_2]** 318 | 319 | - [Answer_2] 320 | 321 |

(back to top)

322 | 323 | 324 | 325 | ## 📝 License 326 | 327 | This project is [MIT](./LICENSE) licensed. 328 | 329 | _NOTE: we recommend using the [MIT license](https://choosealicense.com/licenses/mit/) - you can set it up quickly by [using templates available on GitHub](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-license-to-a-repository). You can also use [any other license](https://choosealicense.com/licenses/) if you wish._ 330 | 331 |

(back to top)

332 | 333 | --------------------------------------------------------------------------------