51 | );
52 | }
53 | }
54 |
55 | export default Question;
56 |
--------------------------------------------------------------------------------
/frontend/public/art.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
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 |
45 |
--------------------------------------------------------------------------------
/frontend/public/sports.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
63 |
--------------------------------------------------------------------------------
/frontend/public/entertainment.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
296 |
297 |
298 |
299 | ## 🙏 Acknowledgments
300 |
301 | > Give credit to everyone who inspired your codebase.
302 |
303 | I would like to thank...
304 |
305 |
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 |