├── .github └── workflows │ └── manual.yml ├── .gitignore ├── 1_Requests_Review ├── backend │ ├── flaskr │ │ └── __init__.py │ └── models.py └── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Book.js │ └── FormView.js │ ├── cover.jpg │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── star-black.png │ ├── star.png │ ├── stylesheets │ ├── App.css │ ├── Book.css │ ├── FormView.css │ └── index.css │ └── trash.png ├── 1_Requests_Starter ├── README.md ├── backend │ ├── books.psql │ ├── flaskr │ │ └── __init__.py │ ├── models.py │ ├── requirements.txt │ └── setup.sql ├── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.js │ │ ├── App.test.js │ │ ├── components │ │ ├── Book.js │ │ └── FormView.js │ │ ├── cover.jpg │ │ ├── index.js │ │ ├── logo.svg │ │ ├── serviceWorker.js │ │ ├── star-black.png │ │ ├── star.png │ │ ├── stylesheets │ │ ├── App.css │ │ ├── Book.css │ │ ├── FormView.css │ │ └── index.css │ │ └── trash.png └── setup.sh ├── 2_Errors_Review ├── backend │ ├── flaskr │ │ └── __init__.py │ └── models.py └── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Book.js │ └── FormView.js │ ├── cover.jpg │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── star-black.png │ ├── star.png │ ├── stylesheets │ ├── App.css │ ├── Book.css │ ├── FormView.css │ └── index.css │ └── trash.png ├── 2_Errors_Starter ├── README.md ├── backend │ ├── books.psql │ ├── flaskr │ │ └── __init__.py │ ├── models.py │ ├── requirements.txt │ └── setup.sql └── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Book.js │ └── FormView.js │ ├── cover.jpg │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── star-black.png │ ├── star.png │ ├── stylesheets │ ├── App.css │ ├── Book.css │ ├── FormView.css │ └── index.css │ └── trash.png ├── 3_Testing_Review ├── backend │ ├── flaskr │ │ ├── __init__.py │ │ └── __init__.pyc │ ├── models.py │ └── test_flaskr.py └── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Book.js │ └── FormView.js │ ├── cover.jpg │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── star-black.png │ ├── star.png │ ├── stylesheets │ ├── App.css │ ├── Book.css │ ├── FormView.css │ └── index.css │ └── trash.png ├── 3_Testing_Starter ├── README.md ├── backend │ ├── books.psql │ ├── flaskr │ │ └── __init__.py │ ├── models.py │ ├── setup.sql │ └── test_flaskr.py └── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Book.js │ └── FormView.js │ ├── cover.jpg │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── star-black.png │ ├── star.png │ └── stylesheets │ ├── App.css │ ├── Book.css │ ├── FormView.css │ └── index.css ├── 4_TDD_Review ├── backend │ ├── flaskr │ │ └── __init__.py │ ├── models.py │ ├── requirements.txt │ └── test_flaskr.py └── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Book.js │ └── FormView.js │ ├── cover.jpg │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── star-black.png │ ├── star.png │ ├── stylesheets │ ├── App.css │ ├── Book.css │ ├── FormView.css │ └── index.css │ └── trash.png ├── 4_TDD_Starter ├── README.md ├── backend │ ├── books.psql │ ├── flaskr │ │ └── __init__.py │ ├── models.py │ ├── requirements.txt │ ├── setup.sql │ └── test_flaskr.py └── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Book.js │ └── FormView.js │ ├── cover.jpg │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── star-black.png │ ├── star.png │ ├── stylesheets │ ├── App.css │ ├── Book.css │ ├── FormView.css │ └── index.css │ └── trash.png ├── 5_API_Doc_Review ├── README.md ├── backend │ ├── flaskr │ │ └── __init__.py │ ├── models.py │ ├── requirements.txt │ └── test_flaskr.py └── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Book.js │ └── FormView.js │ ├── cover.jpg │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── star-black.png │ ├── star.png │ ├── stylesheets │ ├── App.css │ ├── Book.css │ ├── FormView.css │ └── index.css │ └── trash.png ├── 5_API_Doc_Starter ├── README.md ├── backend │ ├── flaskr │ │ └── __init__.py │ ├── models.py │ ├── requirements.txt │ └── test_flaskr.py └── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Book.js │ └── FormView.js │ ├── cover.jpg │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── star-black.png │ ├── star.png │ ├── stylesheets │ ├── App.css │ ├── Book.css │ ├── FormView.css │ └── index.css │ └── trash.png ├── 6_Final_Review ├── README.md ├── backend │ ├── flaskr │ │ └── __init__.py │ ├── models.py │ ├── requirements.txt │ └── test_flaskr.py └── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Book.js │ └── FormView.js │ ├── cover.jpg │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── star-black.png │ ├── star.png │ ├── stylesheets │ ├── App.css │ ├── Book.css │ ├── FormView.css │ └── index.css │ └── trash.png ├── 6_Final_Starter ├── README.md ├── backend │ ├── flaskr │ │ └── __init__.py │ ├── models.py │ ├── requirements.txt │ └── test_flaskr.py └── frontend │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── index.html │ └── manifest.json │ └── src │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Book.js │ └── FormView.js │ ├── cover.jpg │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── star-black.png │ ├── star.png │ ├── stylesheets │ ├── App.css │ ├── Book.css │ ├── FormView.css │ └── index.css │ └── trash.png ├── Bookshelf_database_files ├── books.psql └── setup.sql ├── CODEOWNERS ├── Examples_from_plants_database ├── FirstFlaskApp │ └── flaskr │ │ └── __init__.py ├── Flask-CORS-Example-0 │ └── flask-cors.ipynb ├── Flask-CORS-Example-1 │ ├── flaskr │ │ └── __init__.py │ └── models.py ├── plants.psql └── plantsdb_setup.sql ├── LICENSE.md └── README.md /.github/workflows/manual.yml: -------------------------------------------------------------------------------- 1 | # Workflow to ensure whenever a Github PR is submitted, 2 | # a JIRA ticket gets created automatically. 3 | name: Manual Workflow 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on pull request events but only for the master branch 8 | pull_request_target: 9 | types: [assigned, opened, reopened] 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | jobs: 15 | test-transition-issue: 16 | name: Convert Github Issue to Jira Issue 17 | runs-on: ubuntu-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@master 21 | 22 | - name: Login 23 | uses: atlassian/gajira-login@master 24 | env: 25 | JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} 26 | JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} 27 | JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} 28 | 29 | - name: Create NEW JIRA ticket 30 | id: create 31 | uses: atlassian/gajira-create@master 32 | with: 33 | project: CONUPDATE 34 | issuetype: Task 35 | summary: | 36 | Github PR cd0037 API Development and Documentation (Disaggregated Course)| Repo: ${{ github.repository }} | PR# ${{github.event.number}} 37 | description: | 38 | Repo link: https://github.com/${{ github.repository }} 39 | PR no. ${{ github.event.pull_request.number }} 40 | PR title: ${{ github.event.pull_request.title }} 41 | PR description: ${{ github.event.pull_request.description }} 42 | In addition, please resolve other issues, if any. 43 | fields: '{"components": [{"name":"cd0037 - API Development and Documentation (Disaggregated Course)"}], "customfield_16449":"https://classroom.udacity.com/", "customfield_16450":"Resolve the PR", "labels": ["github"], "priority":{"id": "4"}}' 44 | 45 | - name: Log created issue 46 | run: echo "Issue ${{ steps.create.outputs.issue }} was created" 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | venv 3 | .vscode 4 | __pycache__ -------------------------------------------------------------------------------- /1_Requests_Review/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 = "bookshelf" 7 | database_path = "postgresql://{}:{}@{}/{}".format( 8 | "student", "student", "localhost:5432", database_name 9 | ) 10 | 11 | db = SQLAlchemy() 12 | 13 | """ 14 | setup_db(app) 15 | binds a flask application and a SQLAlchemy service 16 | """ 17 | 18 | 19 | def setup_db(app, database_path=database_path): 20 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 21 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 22 | db.app = app 23 | db.init_app(app) 24 | db.create_all() 25 | 26 | 27 | """ 28 | Book 29 | 30 | """ 31 | 32 | 33 | class Book(db.Model): 34 | __tablename__ = "books" 35 | 36 | id = Column(Integer, primary_key=True) 37 | title = Column(String) 38 | author = Column(String) 39 | rating = Column(Integer) 40 | 41 | def __init__(self, title, author, rating): 42 | self.title = title 43 | self.author = author 44 | self.rating = rating 45 | 46 | def insert(self): 47 | db.session.add(self) 48 | db.session.commit() 49 | 50 | def update(self): 51 | db.session.commit() 52 | 53 | def delete(self): 54 | db.session.delete(self) 55 | db.session.commit() 56 | 57 | def format(self): 58 | return { 59 | "id": self.id, 60 | "title": self.title, 61 | "author": self.author, 62 | "rating": self.rating, 63 | } 64 | -------------------------------------------------------------------------------- /1_Requests_Review/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookshelf-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 | -------------------------------------------------------------------------------- /1_Requests_Review/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /1_Requests_Review/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 | -------------------------------------------------------------------------------- /1_Requests_Review/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 | -------------------------------------------------------------------------------- /1_Requests_Review/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /1_Requests_Review/frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | 4 | import '../stylesheets/FormView.css'; 5 | 6 | class FormView extends Component { 7 | constructor(props){ 8 | super(); 9 | this.state = { 10 | title: "", 11 | author: "", 12 | rating: 1, 13 | search: '', 14 | } 15 | } 16 | 17 | submitBook = (event) => { 18 | event.preventDefault(); 19 | $.ajax({ 20 | url: '/books', //TODO: update request URL 21 | type: "POST", 22 | dataType: 'json', 23 | contentType: 'application/json', 24 | data: JSON.stringify({ 25 | title: this.state.title, 26 | author: this.state.author, 27 | rating: this.state.rating, 28 | }), 29 | xhrFields: { 30 | withCredentials: true 31 | }, 32 | crossDomain: true, 33 | success: (result) => { 34 | document.getElementById("add-book-form").reset(); 35 | return; 36 | }, 37 | error: (error) => { 38 | alert('Unable to add book. Please try your request again') 39 | return; 40 | } 41 | }) 42 | } 43 | 44 | handleSearch = (event) => { 45 | event.preventDefault(); 46 | this.props.searchBooks(this.state.search); 47 | } 48 | 49 | handleChange = (event) => { 50 | this.setState({[event.target.name]: event.target.value}) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 |
57 |

Search

58 |
59 | 60 | 61 |
62 |
63 |

Add a New Book

64 |
65 | 69 | 73 | 83 | 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | export default FormView; 91 | -------------------------------------------------------------------------------- /1_Requests_Review/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/1_Requests_Review/frontend/src/cover.jpg -------------------------------------------------------------------------------- /1_Requests_Review/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /1_Requests_Review/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /1_Requests_Review/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/1_Requests_Review/frontend/src/star-black.png -------------------------------------------------------------------------------- /1_Requests_Review/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/1_Requests_Review/frontend/src/star.png -------------------------------------------------------------------------------- /1_Requests_Review/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /1_Requests_Review/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /1_Requests_Review/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /1_Requests_Review/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /1_Requests_Review/frontend/src/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/1_Requests_Review/frontend/src/trash.png -------------------------------------------------------------------------------- /1_Requests_Starter/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 = "bookshelf" 7 | database_path = "postgresql://{}:{}@{}/{}".format( 8 | "student", "student", "localhost:5432", database_name 9 | ) 10 | 11 | db = SQLAlchemy() 12 | 13 | """ 14 | setup_db(app) 15 | binds a flask application and a SQLAlchemy service 16 | """ 17 | 18 | 19 | def setup_db(app, database_path=database_path): 20 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 21 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 22 | db.app = app 23 | db.init_app(app) 24 | db.create_all() 25 | 26 | 27 | """ 28 | Book 29 | 30 | """ 31 | 32 | 33 | class Book(db.Model): 34 | __tablename__ = "books" 35 | 36 | id = Column(Integer, primary_key=True) 37 | title = Column(String) 38 | author = Column(String) 39 | rating = Column(Integer) 40 | 41 | def __init__(self, title, author, rating): 42 | self.title = title 43 | self.author = author 44 | self.rating = rating 45 | 46 | def insert(self): 47 | db.session.add(self) 48 | db.session.commit() 49 | 50 | def update(self): 51 | db.session.commit() 52 | 53 | def delete(self): 54 | db.session.delete(self) 55 | db.session.commit() 56 | 57 | def format(self): 58 | return { 59 | "id": self.id, 60 | "title": self.title, 61 | "author": self.author, 62 | "rating": self.rating, 63 | } 64 | -------------------------------------------------------------------------------- /1_Requests_Starter/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 15 | -------------------------------------------------------------------------------- /1_Requests_Starter/backend/setup.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS bookshelf; 2 | DROP DATABASE IF EXISTS bookshelf_test; 3 | DROP USER IF EXISTS student; 4 | CREATE DATABASE bookshelf; 5 | CREATE DATABASE bookshelf_test; 6 | CREATE USER student WITH ENCRYPTED PASSWORD 'student'; 7 | GRANT ALL PRIVILEGES ON DATABASE bookshelf TO student; 8 | GRANT ALL PRIVILEGES ON DATABASE bookshelf_test TO student; 9 | ALTER USER student CREATEDB; 10 | ALTER USER student WITH SUPERUSER; -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookshelf-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 | -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /1_Requests_Starter/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 | -------------------------------------------------------------------------------- /1_Requests_Starter/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 | -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | 4 | import '../stylesheets/FormView.css'; 5 | 6 | class FormView extends Component { 7 | constructor(props){ 8 | super(); 9 | this.state = { 10 | title: "", 11 | author: "", 12 | rating: 1, 13 | search: '', 14 | } 15 | } 16 | 17 | submitBook = (event) => { 18 | event.preventDefault(); 19 | $.ajax({ 20 | url: '/books', //TODO: update request URL 21 | type: "POST", 22 | dataType: 'json', 23 | contentType: 'application/json', 24 | data: JSON.stringify({ 25 | title: this.state.title, 26 | author: this.state.author, 27 | rating: this.state.rating, 28 | }), 29 | xhrFields: { 30 | withCredentials: true 31 | }, 32 | crossDomain: true, 33 | success: (result) => { 34 | document.getElementById("add-book-form").reset(); 35 | return; 36 | }, 37 | error: (error) => { 38 | alert('Unable to add book. Please try your request again') 39 | return; 40 | } 41 | }) 42 | } 43 | 44 | handleSearch = (event) => { 45 | event.preventDefault(); 46 | this.props.searchBooks(this.state.search); 47 | } 48 | 49 | handleChange = (event) => { 50 | this.setState({[event.target.name]: event.target.value}) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 |
57 |

Search

58 |
59 | 60 | 61 |
62 |
63 |

Add a New Book

64 |
65 | 69 | 73 | 83 | 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | export default FormView; 91 | -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/1_Requests_Starter/frontend/src/cover.jpg -------------------------------------------------------------------------------- /1_Requests_Starter/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/1_Requests_Starter/frontend/src/star-black.png -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/1_Requests_Starter/frontend/src/star.png -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /1_Requests_Starter/frontend/src/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/1_Requests_Starter/frontend/src/trash.png -------------------------------------------------------------------------------- /1_Requests_Starter/setup.sh: -------------------------------------------------------------------------------- 1 | pip3 install flask_sqlalchemy 2 | pip3 install flask_cors 3 | pip3 install flask --upgrade 4 | pip3 uninstall flask-socketio -y 5 | service postgresql start 6 | su - postgres bash -c "psql < /home/workspace/backend/setup.sql" 7 | su - postgres bash -c "psql bookshelf < /home/workspace/backend/books.psql" -------------------------------------------------------------------------------- /2_Errors_Review/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 = "bookshelf" 7 | database_path = "postgresql://{}:{}@{}/{}".format( 8 | "student", "student", "localhost:5432", database_name 9 | ) 10 | 11 | db = SQLAlchemy() 12 | 13 | """ 14 | setup_db(app) 15 | binds a flask application and a SQLAlchemy service 16 | """ 17 | 18 | 19 | def setup_db(app, database_path=database_path): 20 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 21 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 22 | db.app = app 23 | db.init_app(app) 24 | db.create_all() 25 | 26 | 27 | """ 28 | Book 29 | 30 | """ 31 | 32 | 33 | class Book(db.Model): 34 | __tablename__ = "books" 35 | 36 | id = Column(Integer, primary_key=True) 37 | title = Column(String) 38 | author = Column(String) 39 | rating = Column(Integer) 40 | 41 | def __init__(self, title, author, rating): 42 | self.title = title 43 | self.author = author 44 | self.rating = rating 45 | 46 | def insert(self): 47 | db.session.add(self) 48 | db.session.commit() 49 | 50 | def update(self): 51 | db.session.commit() 52 | 53 | def delete(self): 54 | db.session.delete(self) 55 | db.session.commit() 56 | 57 | def format(self): 58 | return { 59 | "id": self.id, 60 | "title": self.title, 61 | "author": self.author, 62 | "rating": self.rating, 63 | } 64 | -------------------------------------------------------------------------------- /2_Errors_Review/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 | -------------------------------------------------------------------------------- /2_Errors_Review/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /2_Errors_Review/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 | -------------------------------------------------------------------------------- /2_Errors_Review/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 | -------------------------------------------------------------------------------- /2_Errors_Review/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /2_Errors_Review/frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | 4 | import '../stylesheets/FormView.css'; 5 | 6 | class FormView extends Component { 7 | constructor(props){ 8 | super(); 9 | this.state = { 10 | title: "", 11 | author: "", 12 | rating: 1, 13 | search: '', 14 | } 15 | } 16 | 17 | submitBook = (event) => { 18 | event.preventDefault(); 19 | $.ajax({ 20 | url: '/books', //TODO: update request URL 21 | type: "POST", 22 | dataType: 'json', 23 | contentType: 'application/json', 24 | data: JSON.stringify({ 25 | title: this.state.title, 26 | author: this.state.author, 27 | rating: this.state.rating, 28 | }), 29 | xhrFields: { 30 | withCredentials: true 31 | }, 32 | crossDomain: true, 33 | success: (result) => { 34 | document.getElementById("add-book-form").reset(); 35 | return; 36 | }, 37 | error: (error) => { 38 | alert('Unable to add book. Please try your request again') 39 | return; 40 | } 41 | }) 42 | } 43 | 44 | handleSearch = (event) => { 45 | event.preventDefault(); 46 | this.props.searchBooks(this.state.search); 47 | } 48 | 49 | handleChange = (event) => { 50 | this.setState({[event.target.name]: event.target.value}) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 |
57 |

Search

58 |
59 | 60 | 61 |
62 |
63 |

Add a New Book

64 |
65 | 69 | 73 | 83 | 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | export default FormView; 91 | -------------------------------------------------------------------------------- /2_Errors_Review/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/2_Errors_Review/frontend/src/cover.jpg -------------------------------------------------------------------------------- /2_Errors_Review/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /2_Errors_Review/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /2_Errors_Review/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/2_Errors_Review/frontend/src/star-black.png -------------------------------------------------------------------------------- /2_Errors_Review/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/2_Errors_Review/frontend/src/star.png -------------------------------------------------------------------------------- /2_Errors_Review/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /2_Errors_Review/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /2_Errors_Review/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /2_Errors_Review/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /2_Errors_Review/frontend/src/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/2_Errors_Review/frontend/src/trash.png -------------------------------------------------------------------------------- /2_Errors_Starter/README.md: -------------------------------------------------------------------------------- 1 | # Errors Lab 2 | 3 | Now that we have our endpoints, we need to make sure we send back formatted responses when we hit an error. **Pre-requisites and dependencies** are same as the ones explained in the [first exercise's starter code](https://github.com/udacity/nd0044-c2-API-Development-and-Documentation-exercises/blob/master/1_Requests_Starter/README.md) 4 | 5 | ## ToDo 6 | In `__init__.py` use the `errorhandler` decorator to handle all of the errors used in the endpoints. *Before* you get started make sure you read over the endpoint code and inline comments. 7 | 8 | We have provided code to give you a starting point which you need to review in order to understand the code and errors you'll be working with. 9 | 10 | During this exercise, you'll also notice instructions to use `curl` to write requests. Take this opportunity to do so, such that you have practice before you need to use it in a professional setting! 11 | 12 | ## How to run the application 13 | ### Step 0: Start/Stop the PostgreSQL server 14 | ```bash 15 | # Start/stop 16 | pg_ctl -D /usr/local/var/postgres start 17 | pg_ctl -D /usr/local/var/postgres stop 18 | ``` 19 | If it shows that the *port already occupied* error, run: 20 | ```bash 21 | sudo su - 22 | ps -ef | grep postmaster | awk '{print $2}' 23 | kill 24 | ``` 25 | 26 | ### Step 1 - Create and Populate the database 27 | You will have your database already in place. In case you played around with table and data, you can anytime drop and recreate the database. Refer to the [first exercise's starter code](https://github.com/udacity/nd0044-c2-API-Development-and-Documentation-exercises/blob/master/1_Requests_Starter/README.md) again. 28 | 29 | ### Step 2 - Start the frontend server 30 | From the `frontend` folder, run the following commands to start the client: 31 | ``` 32 | npm install // only once to install dependencies 33 | npm start 34 | ``` 35 | By default, the frontend will run on `localhost:3000`. Close the terminal if you wish to stop the frontend server. 36 | 37 | ### Step 3 - Complete the ToDos 38 | Open the */backend/flaskr/__init__.py* file, and finish all the @TODOs. 39 | 40 | There is no change in the frontend this time. 41 | 42 | ### Step 4 - Start the backend server 43 | In a new terminal, start your (backend) Flask server by running the command below: 44 | ```bash 45 | cd backend 46 | export FLASK_APP=flaskr 47 | export FLASK_ENV=development 48 | flask run 49 | ``` 50 | 51 | ### Step 5 - Access the app 52 | Now, run the app and test both the services: 53 | 1. Backend (flask): http://127.0.0.1:5000/ (use CURL within the workspace) 54 | 55 | 2. Frontend: You can acces `http://localhost:3000` for the frontend. 56 | -------------------------------------------------------------------------------- /2_Errors_Starter/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 = "bookshelf" 7 | database_path = "postgresql://{}:{}@{}/{}".format( 8 | "student", "student", "localhost:5432", database_name 9 | ) 10 | 11 | db = SQLAlchemy() 12 | 13 | """ 14 | setup_db(app) 15 | binds a flask application and a SQLAlchemy service 16 | """ 17 | 18 | 19 | def setup_db(app, database_path=database_path): 20 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 21 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 22 | db.app = app 23 | db.init_app(app) 24 | db.create_all() 25 | 26 | 27 | """ 28 | Book 29 | 30 | """ 31 | 32 | 33 | class Book(db.Model): 34 | __tablename__ = "books" 35 | 36 | id = Column(Integer, primary_key=True) 37 | title = Column(String) 38 | author = Column(String) 39 | rating = Column(Integer) 40 | 41 | def __init__(self, title, author, rating): 42 | self.title = title 43 | self.author = author 44 | self.rating = rating 45 | 46 | def insert(self): 47 | db.session.add(self) 48 | db.session.commit() 49 | 50 | def update(self): 51 | db.session.commit() 52 | 53 | def delete(self): 54 | db.session.delete(self) 55 | db.session.commit() 56 | 57 | def format(self): 58 | return { 59 | "id": self.id, 60 | "title": self.title, 61 | "author": self.author, 62 | "rating": self.rating, 63 | } 64 | -------------------------------------------------------------------------------- /2_Errors_Starter/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.4 15 | -------------------------------------------------------------------------------- /2_Errors_Starter/backend/setup.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS bookshelf; 2 | DROP DATABASE IF EXISTS bookshelf_test; 3 | DROP USER IF EXISTS student; 4 | CREATE DATABASE bookshelf; 5 | CREATE DATABASE bookshelf_test; 6 | CREATE USER student WITH ENCRYPTED PASSWORD 'student'; 7 | GRANT ALL PRIVILEGES ON DATABASE bookshelf TO student; 8 | GRANT ALL PRIVILEGES ON DATABASE bookshelf_test TO student; 9 | ALTER USER student CREATEDB; 10 | ALTER USER student WITH SUPERUSER; -------------------------------------------------------------------------------- /2_Errors_Starter/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 | -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /2_Errors_Starter/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 | -------------------------------------------------------------------------------- /2_Errors_Starter/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 | -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | 4 | import '../stylesheets/FormView.css'; 5 | 6 | class FormView extends Component { 7 | constructor(props){ 8 | super(); 9 | this.state = { 10 | title: "", 11 | author: "", 12 | rating: 1, 13 | search: '', 14 | } 15 | } 16 | 17 | submitBook = (event) => { 18 | event.preventDefault(); 19 | $.ajax({ 20 | url: '/books', //TODO: update request URL 21 | type: "POST", 22 | dataType: 'json', 23 | contentType: 'application/json', 24 | data: JSON.stringify({ 25 | title: this.state.title, 26 | author: this.state.author, 27 | rating: this.state.rating, 28 | }), 29 | xhrFields: { 30 | withCredentials: true 31 | }, 32 | crossDomain: true, 33 | success: (result) => { 34 | document.getElementById("add-book-form").reset(); 35 | return; 36 | }, 37 | error: (error) => { 38 | alert('Unable to add book. Please try your request again') 39 | return; 40 | } 41 | }) 42 | } 43 | 44 | handleSearch = (event) => { 45 | event.preventDefault(); 46 | this.props.searchBooks(this.state.search); 47 | } 48 | 49 | handleChange = (event) => { 50 | this.setState({[event.target.name]: event.target.value}) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 |
57 |

Search

58 |
59 | 60 | 61 |
62 |
63 |

Add a New Book

64 |
65 | 69 | 73 | 83 | 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | export default FormView; 91 | -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/2_Errors_Starter/frontend/src/cover.jpg -------------------------------------------------------------------------------- /2_Errors_Starter/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/2_Errors_Starter/frontend/src/star-black.png -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/2_Errors_Starter/frontend/src/star.png -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /2_Errors_Starter/frontend/src/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/2_Errors_Starter/frontend/src/trash.png -------------------------------------------------------------------------------- /3_Testing_Review/backend/flaskr/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/3_Testing_Review/backend/flaskr/__init__.pyc -------------------------------------------------------------------------------- /3_Testing_Review/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 = "bookshelf" 7 | database_path = "postgresql://{}:{}@{}/{}".format( 8 | "postgres", "postgres", "localhost:5432", database_name 9 | ) 10 | 11 | db = SQLAlchemy() 12 | 13 | """ 14 | setup_db(app) 15 | binds a flask application and a SQLAlchemy service 16 | """ 17 | 18 | 19 | def setup_db(app, database_path=database_path): 20 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 21 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 22 | db.app = app 23 | db.init_app(app) 24 | db.create_all() 25 | 26 | 27 | """ 28 | Book 29 | 30 | """ 31 | 32 | 33 | class Book(db.Model): 34 | __tablename__ = "books" 35 | 36 | id = Column(Integer, primary_key=True) 37 | title = Column(String) 38 | author = Column(String) 39 | rating = Column(Integer) 40 | 41 | def __init__(self, title, author, rating): 42 | self.title = title 43 | self.author = author 44 | self.rating = rating 45 | 46 | def insert(self): 47 | db.session.add(self) 48 | db.session.commit() 49 | 50 | def update(self): 51 | db.session.commit() 52 | 53 | def delete(self): 54 | db.session.delete(self) 55 | db.session.commit() 56 | 57 | def format(self): 58 | return { 59 | "id": self.id, 60 | "title": self.title, 61 | "author": self.author, 62 | "rating": self.rating, 63 | } 64 | -------------------------------------------------------------------------------- /3_Testing_Review/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookshelf-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 | -------------------------------------------------------------------------------- /3_Testing_Review/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /3_Testing_Review/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 | -------------------------------------------------------------------------------- /3_Testing_Review/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 | -------------------------------------------------------------------------------- /3_Testing_Review/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /3_Testing_Review/frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | 4 | import '../stylesheets/FormView.css'; 5 | 6 | class FormView extends Component { 7 | constructor(props){ 8 | super(); 9 | this.state = { 10 | title: "", 11 | author: "", 12 | rating: 1, 13 | search: '', 14 | } 15 | } 16 | 17 | submitBook = (event) => { 18 | event.preventDefault(); 19 | $.ajax({ 20 | url: '/books', //TODO: update request URL 21 | type: "POST", 22 | dataType: 'json', 23 | contentType: 'application/json', 24 | data: JSON.stringify({ 25 | title: this.state.title, 26 | author: this.state.author, 27 | rating: this.state.rating, 28 | }), 29 | xhrFields: { 30 | withCredentials: true 31 | }, 32 | crossDomain: true, 33 | success: (result) => { 34 | document.getElementById("add-book-form").reset(); 35 | return; 36 | }, 37 | error: (error) => { 38 | alert('Unable to add book. Please try your request again') 39 | return; 40 | } 41 | }) 42 | } 43 | 44 | handleSearch = (event) => { 45 | event.preventDefault(); 46 | this.props.searchBooks(this.state.search); 47 | } 48 | 49 | handleChange = (event) => { 50 | this.setState({[event.target.name]: event.target.value}) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 |
57 |

Search

58 |
59 | 60 | 61 |
62 |
63 |

Add a New Book

64 |
65 | 69 | 73 | 83 | 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | export default FormView; 91 | -------------------------------------------------------------------------------- /3_Testing_Review/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/3_Testing_Review/frontend/src/cover.jpg -------------------------------------------------------------------------------- /3_Testing_Review/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /3_Testing_Review/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /3_Testing_Review/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/3_Testing_Review/frontend/src/star-black.png -------------------------------------------------------------------------------- /3_Testing_Review/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/3_Testing_Review/frontend/src/star.png -------------------------------------------------------------------------------- /3_Testing_Review/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /3_Testing_Review/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /3_Testing_Review/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /3_Testing_Review/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /3_Testing_Review/frontend/src/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/3_Testing_Review/frontend/src/trash.png -------------------------------------------------------------------------------- /3_Testing_Starter/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 = "bookshelf" 7 | database_path = "postgresql://{}:{}@{}/{}".format( 8 | "student", "student", "localhost:5432", database_name 9 | ) 10 | 11 | db = SQLAlchemy() 12 | 13 | """ 14 | setup_db(app) 15 | binds a flask application and a SQLAlchemy service 16 | """ 17 | 18 | 19 | def setup_db(app, database_path=database_path): 20 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 21 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 22 | db.app = app 23 | db.init_app(app) 24 | db.create_all() 25 | 26 | 27 | """ 28 | Book 29 | 30 | """ 31 | 32 | 33 | class Book(db.Model): 34 | __tablename__ = "books" 35 | 36 | id = Column(Integer, primary_key=True) 37 | title = Column(String) 38 | author = Column(String) 39 | rating = Column(Integer) 40 | 41 | def __init__(self, title, author, rating): 42 | self.title = title 43 | self.author = author 44 | self.rating = rating 45 | 46 | def insert(self): 47 | db.session.add(self) 48 | db.session.commit() 49 | 50 | def update(self): 51 | db.session.commit() 52 | 53 | def delete(self): 54 | db.session.delete(self) 55 | db.session.commit() 56 | 57 | def format(self): 58 | return { 59 | "id": self.id, 60 | "title": self.title, 61 | "author": self.author, 62 | "rating": self.rating, 63 | } 64 | -------------------------------------------------------------------------------- /3_Testing_Starter/backend/setup.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS bookshelf; 2 | DROP DATABASE IF EXISTS bookshelf_test; 3 | DROP USER IF EXISTS student; 4 | CREATE DATABASE bookshelf; 5 | CREATE DATABASE bookshelf_test; 6 | CREATE USER student WITH ENCRYPTED PASSWORD 'student'; 7 | GRANT ALL PRIVILEGES ON DATABASE bookshelf TO student; 8 | GRANT ALL PRIVILEGES ON DATABASE bookshelf_test TO student; 9 | ALTER USER student CREATEDB; 10 | ALTER USER student WITH SUPERUSER; -------------------------------------------------------------------------------- /3_Testing_Starter/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, Book 8 | 9 | 10 | class BookTestCase(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 = "bookshelf_test" 18 | self.database_path = "postgresql://{}:{}@{}/{}".format( 19 | "student", "student", "localhost:5432", self.database_name 20 | ) 21 | setup_db(self.app, self.database_path) 22 | 23 | self.new_book = {"title": "Anansi Boys", "author": "Neil Gaiman", "rating": 5} 24 | 25 | # binds the app to the current context 26 | with self.app.app_context(): 27 | self.db = SQLAlchemy() 28 | self.db.init_app(self.app) 29 | # create all tables 30 | self.db.create_all() 31 | 32 | def tearDown(self): 33 | """Executed after reach test""" 34 | pass 35 | 36 | 37 | # @TODO: Write at least two tests for each endpoint - one each for success and error behavior. 38 | # You can feel free to write additional tests for nuanced functionality, 39 | # Such as adding a book without a rating, etc. 40 | # Since there are four routes currently, you should have at least eight tests. 41 | # Optional: Update the book information in setUp to make the test database your own! 42 | 43 | 44 | # Make the tests conveniently executable 45 | if __name__ == "__main__": 46 | unittest.main() 47 | -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookshelf-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 | -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /3_Testing_Starter/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 | -------------------------------------------------------------------------------- /3_Testing_Starter/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 | -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | 4 | import '../stylesheets/FormView.css'; 5 | 6 | class FormView extends Component { 7 | constructor(props){ 8 | super(); 9 | this.state = { 10 | title: "", 11 | author: "", 12 | rating: 1, 13 | search: '', 14 | } 15 | } 16 | 17 | submitBook = (event) => { 18 | event.preventDefault(); 19 | $.ajax({ 20 | url: '/books', //TODO: update request URL 21 | type: "POST", 22 | dataType: 'json', 23 | contentType: 'application/json', 24 | data: JSON.stringify({ 25 | title: this.state.title, 26 | author: this.state.author, 27 | rating: this.state.rating, 28 | }), 29 | xhrFields: { 30 | withCredentials: true 31 | }, 32 | crossDomain: true, 33 | success: (result) => { 34 | document.getElementById("add-book-form").reset(); 35 | return; 36 | }, 37 | error: (error) => { 38 | alert('Unable to add book. Please try your request again') 39 | return; 40 | } 41 | }) 42 | } 43 | 44 | handleSearch = (event) => { 45 | event.preventDefault(); 46 | this.props.searchBooks(this.state.search); 47 | } 48 | 49 | handleChange = (event) => { 50 | this.setState({[event.target.name]: event.target.value}) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 |
57 |

Search

58 |
59 | 60 | 61 |
62 |
63 |

Add a New Book

64 |
65 | 69 | 73 | 83 | 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | export default FormView; 91 | -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/3_Testing_Starter/frontend/src/cover.jpg -------------------------------------------------------------------------------- /3_Testing_Starter/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/3_Testing_Starter/frontend/src/star-black.png -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/3_Testing_Starter/frontend/src/star.png -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /3_Testing_Starter/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /4_TDD_Review/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 = "bookshelf" 7 | database_path = "postgresql://{}:{}@{}/{}".format( 8 | "student", "student", "localhost:5432", database_name 9 | ) 10 | 11 | db = SQLAlchemy() 12 | 13 | """ 14 | setup_db(app) 15 | binds a flask application and a SQLAlchemy service 16 | """ 17 | 18 | 19 | def setup_db(app, database_path=database_path): 20 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 21 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 22 | db.app = app 23 | db.init_app(app) 24 | db.create_all() 25 | 26 | 27 | """ 28 | Book 29 | 30 | """ 31 | 32 | 33 | class Book(db.Model): 34 | __tablename__ = "books" 35 | 36 | id = Column(Integer, primary_key=True) 37 | title = Column(String) 38 | author = Column(String) 39 | rating = Column(Integer) 40 | 41 | def __init__(self, title, author, rating): 42 | self.title = title 43 | self.author = author 44 | self.rating = rating 45 | 46 | def insert(self): 47 | db.session.add(self) 48 | db.session.commit() 49 | 50 | def update(self): 51 | db.session.commit() 52 | 53 | def delete(self): 54 | db.session.delete(self) 55 | db.session.commit() 56 | 57 | def format(self): 58 | return { 59 | "id": self.id, 60 | "title": self.title, 61 | "author": self.author, 62 | "rating": self.rating, 63 | } 64 | -------------------------------------------------------------------------------- /4_TDD_Review/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.4 15 | -------------------------------------------------------------------------------- /4_TDD_Review/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookshelf-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 | -------------------------------------------------------------------------------- /4_TDD_Review/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /4_TDD_Review/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 | -------------------------------------------------------------------------------- /4_TDD_Review/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 | -------------------------------------------------------------------------------- /4_TDD_Review/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /4_TDD_Review/frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | 4 | import '../stylesheets/FormView.css'; 5 | 6 | class FormView extends Component { 7 | constructor(props){ 8 | super(); 9 | this.state = { 10 | title: "", 11 | author: "", 12 | rating: 1, 13 | search: '', 14 | } 15 | } 16 | 17 | submitBook = (event) => { 18 | event.preventDefault(); 19 | $.ajax({ 20 | url: '/books', //TODO: update request URL 21 | type: "POST", 22 | dataType: 'json', 23 | contentType: 'application/json', 24 | data: JSON.stringify({ 25 | title: this.state.title, 26 | author: this.state.author, 27 | rating: this.state.rating, 28 | }), 29 | xhrFields: { 30 | withCredentials: true 31 | }, 32 | crossDomain: true, 33 | success: (result) => { 34 | document.getElementById("add-book-form").reset(); 35 | return; 36 | }, 37 | error: (error) => { 38 | alert('Unable to add book. Please try your request again') 39 | return; 40 | } 41 | }) 42 | } 43 | 44 | handleSearch = (event) => { 45 | event.preventDefault(); 46 | this.props.searchBooks(this.state.search); 47 | } 48 | 49 | handleChange = (event) => { 50 | this.setState({[event.target.name]: event.target.value}) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 |
57 |

Search

58 |
59 | 60 | 61 |
62 |
63 |

Add a New Book

64 |
65 | 69 | 73 | 83 | 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | export default FormView; 91 | -------------------------------------------------------------------------------- /4_TDD_Review/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/4_TDD_Review/frontend/src/cover.jpg -------------------------------------------------------------------------------- /4_TDD_Review/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /4_TDD_Review/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /4_TDD_Review/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/4_TDD_Review/frontend/src/star-black.png -------------------------------------------------------------------------------- /4_TDD_Review/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/4_TDD_Review/frontend/src/star.png -------------------------------------------------------------------------------- /4_TDD_Review/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /4_TDD_Review/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /4_TDD_Review/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /4_TDD_Review/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /4_TDD_Review/frontend/src/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/4_TDD_Review/frontend/src/trash.png -------------------------------------------------------------------------------- /4_TDD_Starter/README.md: -------------------------------------------------------------------------------- 1 | # TDD Exercise 2 | In this exercise, you'll be practicing your Test Driven Development skills. Before you get started, let's start the services. 3 | 4 | * Start/Stop the PostgreSQL server 5 | * Create and Populate the database, if not already 6 | * Start the frontend server 7 | * Start the backend server 8 | * Complete the ToDos, and test out the behavior in the web app interface again. 9 | 10 | 11 | ## ToDo 12 | First you'll write 2 or more tests to address expected behavior to search for a book by title in `test_flask.py`. Write those tests first and ensure that they fail by running `python test_flaskr.py` from the backend folder. 13 | 14 | 15 | ## TDD Practice (Continued) 16 | 17 | After you see your tests fail, write the solution code in `__init__.py` by writing a new endpoint or updating a previous endpoint. At last, **test out the behavior in the web app interface again**. 18 | 19 | -------------------------------------------------------------------------------- /4_TDD_Starter/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 = "bookshelf" 7 | database_path = "postgresql://{}:{}@{}/{}".format( 8 | "student", "student", "localhost:5432", database_name 9 | ) 10 | 11 | db = SQLAlchemy() 12 | 13 | """ 14 | setup_db(app) 15 | binds a flask application and a SQLAlchemy service 16 | """ 17 | 18 | 19 | def setup_db(app, database_path=database_path): 20 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 21 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 22 | db.app = app 23 | db.init_app(app) 24 | db.create_all() 25 | 26 | 27 | """ 28 | Book 29 | 30 | """ 31 | 32 | 33 | class Book(db.Model): 34 | __tablename__ = "books" 35 | 36 | id = Column(Integer, primary_key=True) 37 | title = Column(String) 38 | author = Column(String) 39 | rating = Column(Integer) 40 | 41 | def __init__(self, title, author, rating): 42 | self.title = title 43 | self.author = author 44 | self.rating = rating 45 | 46 | def insert(self): 47 | db.session.add(self) 48 | db.session.commit() 49 | 50 | def update(self): 51 | db.session.commit() 52 | 53 | def delete(self): 54 | db.session.delete(self) 55 | db.session.commit() 56 | 57 | def format(self): 58 | return { 59 | "id": self.id, 60 | "title": self.title, 61 | "author": self.author, 62 | "rating": self.rating, 63 | } 64 | -------------------------------------------------------------------------------- /4_TDD_Starter/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.4 15 | -------------------------------------------------------------------------------- /4_TDD_Starter/backend/setup.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS bookshelf; 2 | DROP DATABASE IF EXISTS bookshelf_test; 3 | DROP USER IF EXISTS student; 4 | CREATE DATABASE bookshelf; 5 | CREATE DATABASE bookshelf_test; 6 | CREATE USER student WITH ENCRYPTED PASSWORD 'student'; 7 | GRANT ALL PRIVILEGES ON DATABASE bookshelf TO student; 8 | GRANT ALL PRIVILEGES ON DATABASE bookshelf_test TO student; 9 | ALTER USER student CREATEDB; 10 | ALTER USER student WITH SUPERUSER; -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bookshelf-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 | -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /4_TDD_Starter/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 | -------------------------------------------------------------------------------- /4_TDD_Starter/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 | -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | 4 | import '../stylesheets/FormView.css'; 5 | 6 | class FormView extends Component { 7 | constructor(props){ 8 | super(); 9 | this.state = { 10 | title: "", 11 | author: "", 12 | rating: 1, 13 | search: '', 14 | } 15 | } 16 | 17 | submitBook = (event) => { 18 | event.preventDefault(); 19 | $.ajax({ 20 | url: '/books', //TODO: update request URL 21 | type: "POST", 22 | dataType: 'json', 23 | contentType: 'application/json', 24 | data: JSON.stringify({ 25 | title: this.state.title, 26 | author: this.state.author, 27 | rating: this.state.rating, 28 | }), 29 | xhrFields: { 30 | withCredentials: true 31 | }, 32 | crossDomain: true, 33 | success: (result) => { 34 | document.getElementById("add-book-form").reset(); 35 | return; 36 | }, 37 | error: (error) => { 38 | alert('Unable to add book. Please try your request again') 39 | return; 40 | } 41 | }) 42 | } 43 | 44 | handleSearch = (event) => { 45 | event.preventDefault(); 46 | this.props.searchBooks(this.state.search); 47 | } 48 | 49 | handleChange = (event) => { 50 | this.setState({[event.target.name]: event.target.value}) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 |
57 |

Search

58 |
59 | 60 | 61 |
62 |
63 |

Add a New Book

64 |
65 | 69 | 73 | 83 | 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | export default FormView; 91 | -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/4_TDD_Starter/frontend/src/cover.jpg -------------------------------------------------------------------------------- /4_TDD_Starter/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/4_TDD_Starter/frontend/src/star-black.png -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/4_TDD_Starter/frontend/src/star.png -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /4_TDD_Starter/frontend/src/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/4_TDD_Starter/frontend/src/trash.png -------------------------------------------------------------------------------- /5_API_Doc_Review/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 = "bookshelf" 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 | 16 | 17 | def setup_db(app, database_path=database_path): 18 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 19 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 20 | db.app = app 21 | db.init_app(app) 22 | db.create_all() 23 | 24 | 25 | """ 26 | Book 27 | 28 | """ 29 | 30 | 31 | class Book(db.Model): 32 | __tablename__ = "books" 33 | 34 | id = Column(Integer, primary_key=True) 35 | title = Column(String) 36 | author = Column(String) 37 | rating = Column(Integer) 38 | 39 | def __init__(self, title, author, rating): 40 | self.title = title 41 | self.author = author 42 | self.rating = rating 43 | 44 | def insert(self): 45 | db.session.add(self) 46 | db.session.commit() 47 | 48 | def update(self): 49 | db.session.commit() 50 | 51 | def delete(self): 52 | db.session.delete(self) 53 | db.session.commit() 54 | 55 | def format(self): 56 | return { 57 | "id": self.id, 58 | "title": self.title, 59 | "author": self.author, 60 | "rating": self.rating, 61 | } 62 | -------------------------------------------------------------------------------- /5_API_Doc_Review/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.4 15 | -------------------------------------------------------------------------------- /5_API_Doc_Review/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 | -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /5_API_Doc_Review/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 | -------------------------------------------------------------------------------- /5_API_Doc_Review/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 | -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | 4 | import '../stylesheets/FormView.css'; 5 | 6 | class FormView extends Component { 7 | constructor(props){ 8 | super(); 9 | this.state = { 10 | title: "", 11 | author: "", 12 | rating: 1, 13 | search: '', 14 | } 15 | } 16 | 17 | submitBook = (event) => { 18 | event.preventDefault(); 19 | $.ajax({ 20 | url: '/books', //TODO: update request URL 21 | type: "POST", 22 | dataType: 'json', 23 | contentType: 'application/json', 24 | data: JSON.stringify({ 25 | title: this.state.title, 26 | author: this.state.author, 27 | rating: this.state.rating, 28 | }), 29 | xhrFields: { 30 | withCredentials: true 31 | }, 32 | crossDomain: true, 33 | success: (result) => { 34 | document.getElementById("add-book-form").reset(); 35 | return; 36 | }, 37 | error: (error) => { 38 | alert('Unable to add book. Please try your request again') 39 | return; 40 | } 41 | }) 42 | } 43 | 44 | handleSearch = (event) => { 45 | event.preventDefault(); 46 | this.props.searchBooks(this.state.search); 47 | } 48 | 49 | handleChange = (event) => { 50 | this.setState({[event.target.name]: event.target.value}) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 |
57 |

Search

58 |
59 | 60 | 61 |
62 |
63 |

Add a New Book

64 |
65 | 69 | 73 | 83 | 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | export default FormView; 91 | -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/5_API_Doc_Review/frontend/src/cover.jpg -------------------------------------------------------------------------------- /5_API_Doc_Review/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/5_API_Doc_Review/frontend/src/star-black.png -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/5_API_Doc_Review/frontend/src/star.png -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /5_API_Doc_Review/frontend/src/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/5_API_Doc_Review/frontend/src/trash.png -------------------------------------------------------------------------------- /5_API_Doc_Starter/README.md: -------------------------------------------------------------------------------- 1 | # API Documentation Practice 2 | In this exercise, your task is to practice writing documentation for the bookstore app we created earlier. 3 | 4 | You'll soon be writing documentation for your final project (the Trivia API), after which you'll get feedback from a reviewer. You can think of this as some rudimentary practice to prepare for that. 5 | 6 | At each step, you can compare what you've written with our own version. Of course, **there isn't a single correct way to write a piece of documentation**, so your version may look quite different. However, there are principles and practices you should follow in order to produce quality documentation, and we'll point this out so you can check whether you've incorporated them in what you wrote. 7 | 8 | ## Getting started 9 | Now, add a Getting Started section to your documentation. Remember, this should include at least your base URL and an explanation of authentication. Feel free to provide other information that is relevant for your API 10 | 11 | 12 | ## Error Handling 13 | Now, add an Error Handling section to your documentation. It should include the format of the error responses the client can expect as well as which status codes you use. 14 | - Response codes 15 | - Messages 16 | - Error types 17 | 18 | ## Endpoint Library 19 | Now, add an Endpoint Library section to your documentation. Make sure that endpoints, methods and returned data are all clear. Consider including sample requests for clarity 20 | 21 | - Organized by resource 22 | - Include each endpoint 23 | - Sample request 24 | - Arguments including data types 25 | - Response object including status codes and data types -------------------------------------------------------------------------------- /5_API_Doc_Starter/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 = "bookshelf" 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 | 16 | 17 | def setup_db(app, database_path=database_path): 18 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 19 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 20 | db.app = app 21 | db.init_app(app) 22 | db.create_all() 23 | 24 | 25 | """ 26 | Book 27 | 28 | """ 29 | 30 | 31 | class Book(db.Model): 32 | __tablename__ = "books" 33 | 34 | id = Column(Integer, primary_key=True) 35 | title = Column(String) 36 | author = Column(String) 37 | rating = Column(Integer) 38 | 39 | def __init__(self, title, author, rating): 40 | self.title = title 41 | self.author = author 42 | self.rating = rating 43 | 44 | def insert(self): 45 | db.session.add(self) 46 | db.session.commit() 47 | 48 | def update(self): 49 | db.session.commit() 50 | 51 | def delete(self): 52 | db.session.delete(self) 53 | db.session.commit() 54 | 55 | def format(self): 56 | return { 57 | "id": self.id, 58 | "title": self.title, 59 | "author": self.author, 60 | "rating": self.rating, 61 | } 62 | -------------------------------------------------------------------------------- /5_API_Doc_Starter/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.4 15 | -------------------------------------------------------------------------------- /5_API_Doc_Starter/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 | -------------------------------------------------------------------------------- /5_API_Doc_Starter/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /5_API_Doc_Starter/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 | -------------------------------------------------------------------------------- /5_API_Doc_Starter/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 | -------------------------------------------------------------------------------- /5_API_Doc_Starter/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /5_API_Doc_Starter/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/5_API_Doc_Starter/frontend/src/cover.jpg -------------------------------------------------------------------------------- /5_API_Doc_Starter/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /5_API_Doc_Starter/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /5_API_Doc_Starter/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/5_API_Doc_Starter/frontend/src/star-black.png -------------------------------------------------------------------------------- /5_API_Doc_Starter/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/5_API_Doc_Starter/frontend/src/star.png -------------------------------------------------------------------------------- /5_API_Doc_Starter/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /5_API_Doc_Starter/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /5_API_Doc_Starter/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /5_API_Doc_Starter/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /5_API_Doc_Starter/frontend/src/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/5_API_Doc_Starter/frontend/src/trash.png -------------------------------------------------------------------------------- /6_Final_Review/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 = "bookshelf" 7 | database_path = "postgresql://{}:{}@{}/{}".format( 8 | "student", "student", "localhost:5432", database_name 9 | ) 10 | 11 | db = SQLAlchemy() 12 | 13 | """ 14 | setup_db(app) 15 | binds a flask application and a SQLAlchemy service 16 | """ 17 | 18 | 19 | def setup_db(app, database_path=database_path): 20 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 21 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 22 | db.app = app 23 | db.init_app(app) 24 | db.create_all() 25 | 26 | 27 | """ 28 | Book 29 | 30 | """ 31 | 32 | 33 | class Book(db.Model): 34 | __tablename__ = "books" 35 | 36 | id = Column(Integer, primary_key=True) 37 | title = Column(String) 38 | author = Column(String) 39 | rating = Column(Integer) 40 | 41 | def __init__(self, title, author, rating): 42 | self.title = title 43 | self.author = author 44 | self.rating = rating 45 | 46 | def insert(self): 47 | db.session.add(self) 48 | db.session.commit() 49 | 50 | def update(self): 51 | db.session.commit() 52 | 53 | def delete(self): 54 | db.session.delete(self) 55 | db.session.commit() 56 | 57 | def format(self): 58 | return { 59 | "id": self.id, 60 | "title": self.title, 61 | "author": self.author, 62 | "rating": self.rating, 63 | } 64 | -------------------------------------------------------------------------------- /6_Final_Review/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.4 15 | -------------------------------------------------------------------------------- /6_Final_Review/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 | -------------------------------------------------------------------------------- /6_Final_Review/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /6_Final_Review/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 | -------------------------------------------------------------------------------- /6_Final_Review/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 | -------------------------------------------------------------------------------- /6_Final_Review/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /6_Final_Review/frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | 4 | import '../stylesheets/FormView.css'; 5 | 6 | class FormView extends Component { 7 | constructor(props){ 8 | super(); 9 | this.state = { 10 | title: "", 11 | author: "", 12 | rating: 1, 13 | search: '', 14 | } 15 | } 16 | 17 | submitBook = (event) => { 18 | event.preventDefault(); 19 | $.ajax({ 20 | url: '/books', //TODO: update request URL 21 | type: "POST", 22 | dataType: 'json', 23 | contentType: 'application/json', 24 | data: JSON.stringify({ 25 | title: this.state.title, 26 | author: this.state.author, 27 | rating: this.state.rating, 28 | }), 29 | xhrFields: { 30 | withCredentials: true 31 | }, 32 | crossDomain: true, 33 | success: (result) => { 34 | document.getElementById("add-book-form").reset(); 35 | return; 36 | }, 37 | error: (error) => { 38 | alert('Unable to add book. Please try your request again') 39 | return; 40 | } 41 | }) 42 | } 43 | 44 | handleSearch = (event) => { 45 | event.preventDefault(); 46 | this.props.searchBooks(this.state.search); 47 | } 48 | 49 | handleChange = (event) => { 50 | this.setState({[event.target.name]: event.target.value}) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 |
57 |

Search

58 |
59 | 60 | 61 |
62 |
63 |

Add a New Book

64 |
65 | 69 | 73 | 83 | 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | export default FormView; 91 | -------------------------------------------------------------------------------- /6_Final_Review/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/6_Final_Review/frontend/src/cover.jpg -------------------------------------------------------------------------------- /6_Final_Review/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /6_Final_Review/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /6_Final_Review/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/6_Final_Review/frontend/src/star-black.png -------------------------------------------------------------------------------- /6_Final_Review/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/6_Final_Review/frontend/src/star.png -------------------------------------------------------------------------------- /6_Final_Review/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /6_Final_Review/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /6_Final_Review/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /6_Final_Review/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /6_Final_Review/frontend/src/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/6_Final_Review/frontend/src/trash.png -------------------------------------------------------------------------------- /6_Final_Starter/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 = "bookshelf" 7 | database_path = "postgresql://{}:{}@{}/{}".format( 8 | "student", "student", "localhost:5432", database_name 9 | ) 10 | 11 | db = SQLAlchemy() 12 | 13 | """ 14 | setup_db(app) 15 | binds a flask application and a SQLAlchemy service 16 | """ 17 | 18 | 19 | def setup_db(app, database_path=database_path): 20 | app.config["SQLALCHEMY_DATABASE_URI"] = database_path 21 | app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False 22 | db.app = app 23 | db.init_app(app) 24 | db.create_all() 25 | 26 | 27 | """ 28 | Book 29 | 30 | """ 31 | 32 | 33 | class Book(db.Model): 34 | __tablename__ = "books" 35 | 36 | id = Column(Integer, primary_key=True) 37 | title = Column(String) 38 | author = Column(String) 39 | rating = Column(Integer) 40 | 41 | def __init__(self, title, author, rating): 42 | self.title = title 43 | self.author = author 44 | self.rating = rating 45 | 46 | def insert(self): 47 | db.session.add(self) 48 | db.session.commit() 49 | 50 | def update(self): 51 | db.session.commit() 52 | 53 | def delete(self): 54 | db.session.delete(self) 55 | db.session.commit() 56 | 57 | def format(self): 58 | return { 59 | "id": self.id, 60 | "title": self.title, 61 | "author": self.author, 62 | "rating": self.rating, 63 | } 64 | -------------------------------------------------------------------------------- /6_Final_Starter/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.4 15 | -------------------------------------------------------------------------------- /6_Final_Starter/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 | -------------------------------------------------------------------------------- /6_Final_Starter/frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | React App 23 | 24 | 25 | 26 |
27 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /6_Final_Starter/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 | -------------------------------------------------------------------------------- /6_Final_Starter/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 | -------------------------------------------------------------------------------- /6_Final_Starter/frontend/src/components/Book.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import '../stylesheets/Book.css'; 4 | 5 | const starArray = [5,4,3,2,1] 6 | 7 | class Book extends Component { 8 | createStars(){ 9 | let {id, rating, deleteBook} = this.props; 10 | 11 | return ( 12 |
13 | {starArray.map(num => ( 14 |
{this.props.changeRating(this.props.id, num)}} 17 | className={`star ${rating >= num ? 'active':''}`} 18 | /> 19 | ))} 20 |
{deleteBook(id)}} /> 21 |
22 | ) 23 | } 24 | 25 | render() { 26 | let {title, author} = this.props; 27 | 28 | return ( 29 |
30 |
31 |
{title}
32 |
33 |
34 | {author} 35 |
36 | {this.createStars()} 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default Book; 43 | -------------------------------------------------------------------------------- /6_Final_Starter/frontend/src/components/FormView.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import $ from 'jquery'; 3 | 4 | import '../stylesheets/FormView.css'; 5 | 6 | class FormView extends Component { 7 | constructor(props){ 8 | super(); 9 | this.state = { 10 | title: "", 11 | author: "", 12 | rating: 1, 13 | search: '', 14 | } 15 | } 16 | 17 | submitBook = (event) => { 18 | event.preventDefault(); 19 | $.ajax({ 20 | url: '/books', //TODO: update request URL 21 | type: "POST", 22 | dataType: 'json', 23 | contentType: 'application/json', 24 | data: JSON.stringify({ 25 | title: this.state.title, 26 | author: this.state.author, 27 | rating: this.state.rating, 28 | }), 29 | xhrFields: { 30 | withCredentials: true 31 | }, 32 | crossDomain: true, 33 | success: (result) => { 34 | document.getElementById("add-book-form").reset(); 35 | return; 36 | }, 37 | error: (error) => { 38 | alert('Unable to add book. Please try your request again') 39 | return; 40 | } 41 | }) 42 | } 43 | 44 | handleSearch = (event) => { 45 | event.preventDefault(); 46 | this.props.searchBooks(this.state.search); 47 | } 48 | 49 | handleChange = (event) => { 50 | this.setState({[event.target.name]: event.target.value}) 51 | } 52 | 53 | render() { 54 | return ( 55 |
56 |
57 |

Search

58 |
59 | 60 | 61 |
62 |
63 |

Add a New Book

64 |
65 | 69 | 73 | 83 | 84 |
85 |
86 | ); 87 | } 88 | } 89 | 90 | export default FormView; 91 | -------------------------------------------------------------------------------- /6_Final_Starter/frontend/src/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/6_Final_Starter/frontend/src/cover.jpg -------------------------------------------------------------------------------- /6_Final_Starter/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 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: https://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | 14 | 15 | //"start": "HOST='127.0.0.1' PORT='5000' react-scripts start", 16 | -------------------------------------------------------------------------------- /6_Final_Starter/frontend/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /6_Final_Starter/frontend/src/star-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/6_Final_Starter/frontend/src/star-black.png -------------------------------------------------------------------------------- /6_Final_Starter/frontend/src/star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/6_Final_Starter/frontend/src/star.png -------------------------------------------------------------------------------- /6_Final_Starter/frontend/src/stylesheets/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | display: flex; 3 | flex-direction: row; 4 | margin: 35px 10px 0px; 5 | } 6 | 7 | div#main-view { 8 | flex-basis: 70%; 9 | } 10 | 11 | div#form-view { 12 | flex-basis: 30%; 13 | } 14 | 15 | .bookshelf-container { 16 | display: flex; 17 | flex-wrap: wrap; 18 | } 19 | 20 | .pagination-menu { 21 | display: flex; 22 | width: fit-content; 23 | margin: 0px auto; 24 | text-align: center; 25 | } 26 | 27 | 28 | .page-num { 29 | display: inline-block; 30 | margin: 0px 3px; 31 | } 32 | 33 | .page-num.active { 34 | color: blue; 35 | font-weight: bolder; 36 | } -------------------------------------------------------------------------------- /6_Final_Starter/frontend/src/stylesheets/Book.css: -------------------------------------------------------------------------------- 1 | div.star { 2 | width: 20px; 3 | height: 20px; 4 | margin: 2px; 5 | background-size: contain; 6 | background-repeat: no-repeat; 7 | background-image: url('../star-black.png'); 8 | } 9 | 10 | div.star.active { 11 | background-image: url('../star.png') 12 | } 13 | 14 | .delete { 15 | width: 20px; 16 | height: 20px; 17 | margin: 2px; 18 | background-size: contain; 19 | background-repeat: no-repeat; 20 | background-image: url('../trash.png') 21 | } 22 | 23 | .rating { 24 | display: flex; 25 | } 26 | 27 | .title { 28 | padding: 50% 5px; 29 | width: 65%; 30 | color: azure; 31 | } 32 | 33 | .book { 34 | flex-basis: 25%; 35 | height: 350px 36 | } 37 | 38 | .author { 39 | margin: 5px 0px; 40 | } 41 | 42 | .cover { 43 | width: 80%; 44 | left: 10%; 45 | height: 225px; 46 | padding: 5px 15px; 47 | text-align: center; 48 | background-size: contain; 49 | background-repeat: no-repeat; 50 | background-image: url('../cover.jpg') 51 | } -------------------------------------------------------------------------------- /6_Final_Starter/frontend/src/stylesheets/FormView.css: -------------------------------------------------------------------------------- 1 | #form-view { 2 | text-align: center; 3 | } 4 | 5 | label { 6 | display: block; 7 | margin: 10px 0px; 8 | } 9 | 10 | input { 11 | display: block; 12 | margin-left: auto; 13 | margin-right: auto; 14 | margin-bottom: 10px; 15 | } 16 | -------------------------------------------------------------------------------- /6_Final_Starter/frontend/src/stylesheets/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /6_Final_Starter/frontend/src/trash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/udacity/cd0037-API-Development-and-Documentation-exercises/ec53547028737a45c40c5c5d8bf113610df9f866/6_Final_Starter/frontend/src/trash.png -------------------------------------------------------------------------------- /Bookshelf_database_files/setup.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS bookshelf; 2 | DROP DATABASE IF EXISTS bookshelf_test; 3 | CREATE DATABASE bookshelf; 4 | CREATE DATABASE bookshelf_test; 5 | CREATE USER student WITH ENCRYPTED PASSWORD 'student'; 6 | GRANT ALL PRIVILEGES ON DATABASE bookshelf TO student; 7 | GRANT ALL PRIVILEGES ON DATABASE bookshelf_test TO student; 8 | ALTER USER student CREATEDB; 9 | ALTER USER student WITH SUPERUSER; -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @udacity/active-public-content -------------------------------------------------------------------------------- /Examples_from_plants_database/FirstFlaskApp/flaskr/__init__.py: -------------------------------------------------------------------------------- 1 | # Import your dependencies 2 | from flask import Flask, jsonify 3 | 4 | # Define the create_app function 5 | def create_app(test_config=None): 6 | # Create and configure the app 7 | # Include the first parameter: Here, __name__is the name of the current Python module. 8 | app = Flask(__name__) 9 | 10 | @app.route("/") 11 | def hello_world(): 12 | return jsonify({"message": "HELLO, WORLD!"}) 13 | 14 | @app.route("/smiley") 15 | def smiley(): 16 | return ":)" 17 | 18 | return app 19 | -------------------------------------------------------------------------------- /Examples_from_plants_database/Flask-CORS-Example-0/flask-cors.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "graffitiCellId": "id_b2slrdu" 7 | }, 8 | "source": [ 9 | "# Flask-CORS\n", 10 | "In your Flask applications, you'll use the Flask-CORS library to enable CORS. \n", 11 | "Below you'll see samples of how to use Flask-CORS to enable different configurations of CORS. Try reading and analyzing the code before using the tooltips to hear the explanations. " 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": null, 17 | "metadata": { 18 | "graffitiCellId": "id_f5o09dc" 19 | }, 20 | "outputs": [], 21 | "source": [ 22 | "# Import Dependencies\n", 23 | "from flask import Flask\n", 24 | "from flask_cors import CORS" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": null, 30 | "metadata": { 31 | "graffitiCellId": "id_smo0qpl" 32 | }, 33 | "outputs": [], 34 | "source": [ 35 | "def create_app(test_config=None):\n", 36 | " app = Flask(__name__)\n", 37 | "# CORS(app)\n", 38 | " cors = CORS(app, resources={r\"/api/*\": {\"origins\": \"*\"}})\n", 39 | "\n", 40 | " # CORS Headers \n", 41 | " @app.after_request\n", 42 | " def after_request(response):\n", 43 | " response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization,true')\n", 44 | " response.headers.add('Access-Control-Allow-Methods', 'GET,PATCH,POST,DELETE,OPTIONS')\n", 45 | " return response\n", 46 | " \n", 47 | " @app.route('/messages')\n", 48 | " @cross_origin()\n", 49 | " def get_messages():\n", 50 | " return 'GETTING MESSAGES'" 51 | ] 52 | } 53 | ], 54 | "metadata": { 55 | "graffiti": { 56 | "firstAuthorId": "5362449611", 57 | "id": "id_j0as61w", 58 | "language": "EN" 59 | }, 60 | "kernelspec": { 61 | "display_name": "Python 3", 62 | "language": "python", 63 | "name": "python3" 64 | }, 65 | "language_info": { 66 | "codemirror_mode": { 67 | "name": "ipython", 68 | "version": 3 69 | }, 70 | "file_extension": ".py", 71 | "mimetype": "text/x-python", 72 | "name": "python", 73 | "nbconvert_exporter": "python", 74 | "pygments_lexer": "ipython3", 75 | "version": "3.6.3" 76 | } 77 | }, 78 | "nbformat": 4, 79 | "nbformat_minor": 2 80 | } 81 | -------------------------------------------------------------------------------- /Examples_from_plants_database/Flask-CORS-Example-1/flaskr/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, jsonify, request, abort 2 | from models import setup_db, Plant 3 | from flask_cors import CORS, cross_origin 4 | 5 | 6 | def create_app(test_config=None): 7 | app = Flask(__name__, instance_relative_config=True) 8 | setup_db(app) 9 | # CORS(app, resources={r"*/api/*" : {origins: '*'}}) 10 | CORS(app) 11 | 12 | @app.after_request 13 | def after_request(response): 14 | response.headers.add( 15 | "Access-Control-Allow-Headers", "Content-Type, Authorization" 16 | ) 17 | response.headers.add( 18 | "Access-Control-Allow-Headers", "GET, POST, PATCH, DELETE, OPTION" 19 | ) 20 | return response 21 | 22 | @app.route("/plants", methods=["GET", "POST"]) 23 | # @cross_origin 24 | def get_plants(): 25 | # Implement pagniation 26 | page = request.args.get("page", 1, type=int) 27 | start = (page - 1) * 10 28 | end = start + 10 29 | 30 | plants = Plant.query.all() 31 | formatted_plants = [plant.format() for plant in plants] 32 | return jsonify( 33 | { 34 | "success": True, 35 | "plants": formatted_plants[start:end], 36 | "total_plants": len(formatted_plants), 37 | } 38 | ) 39 | 40 | @app.route("/plants/") 41 | def get_specific_plant(plant_id): 42 | plant = Plant.query.filter(Plant.id == plant_id).one_or_none() 43 | if plant is None: 44 | abort(404) 45 | else: 46 | return jsonify({"success": True, "plant": plant.format()}) 47 | 48 | return app 49 | -------------------------------------------------------------------------------- /Examples_from_plants_database/Flask-CORS-Example-1/models.py: -------------------------------------------------------------------------------- 1 | import os 2 | from sqlalchemy import Column, String, Integer, Boolean, create_engine 3 | from flask_sqlalchemy import SQLAlchemy 4 | import json 5 | from sqlalchemy.sql.schema import PrimaryKeyConstraint 6 | 7 | database_name = "plantsdb" 8 | # Feel free to remove the password argument from the below format() method 9 | database_path = "postgresql://{}:{}@{}/{}".format( 10 | "postgres", "", "localhost:5432", database_name 11 | ) 12 | db = SQLAlchemy() 13 | 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 | """Plant class""" 24 | 25 | 26 | class Plant(db.Model): 27 | __tablename__ = "plants" 28 | 29 | id = Column(Integer, primary_key=True) 30 | name = Column(String) 31 | scientific_name = Column(String) 32 | is_poisonous = Column(Boolean) 33 | primary_color = Column(String) 34 | 35 | 36 | def __init__(self, name, scientific_name, is_poisonous, primary_color): 37 | self.name = name 38 | self.scientific_name = scientific_name 39 | self.is_poisonous = is_poisonous 40 | self.primary_color = primary_color 41 | 42 | 43 | def insert(self): 44 | db.session.add(self) 45 | db.session.commit() 46 | 47 | 48 | def update(self): 49 | db.session.commit() 50 | 51 | 52 | def delete(self): 53 | db.session.delete(self) 54 | db.session.commit() 55 | 56 | 57 | def format(self): 58 | return { 59 | "id": self.id, 60 | "name": self.name, 61 | "scientific_name": self.scientific_name, 62 | "is_poisonous": self.is_poisonous, 63 | "primary_color": self.primary_color, 64 | } 65 | -------------------------------------------------------------------------------- /Examples_from_plants_database/plantsdb_setup.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE IF EXISTS plantsdb; 2 | DROP USER IF EXISTS student; 3 | CREATE DATABASE plantsdb; 4 | CREATE USER student WITH ENCRYPTED PASSWORD 'student'; 5 | GRANT ALL PRIVILEGES ON DATABASE plantsdb TO student; 6 | ALTER USER student CREATEDB; 7 | ALTER USER student WITH SUPERUSER; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About the Great Bookshelf of Udacity 2 | 3 | This project is a virtual bookshelf for Udacity students. Students are able to add their books to the bookshelf, give them a rating, update the rating and search through their book lists. This project will serve as a practice module for the current course. By completing this project, students learn and apply their skills structuring and implementing well-formatted API endpoints that leverage the knowledge of HTTP and API development best practices. 4 | 5 | All backend code follows [PEP8 style guidelines](https://www.python.org/dev/peps/pep-0008/). 6 | 7 | * **What we expect from you?**
8 | You'll use this base in various workspaces throughout the course to build the project incrementally as you expand your skills. At each stage, there will be **various 'TODO's marked for you to complete.** You'll also notice some TODOs in the frontend section in the upcoming workspaces. 9 | 10 | You should feel free to expand on the project in any way you can dream up to extend your skills. For instance, you could add additional book information to each entry or create individual book views including more information about the book, your thoughts, or when you completed it. 11 | 12 | ### Exercise starter code and solution 13 | The current repository contains the starter code and solution for six exercises based on the the *bookshelf* database. These exercises are namely: 14 | 1. Requests 15 | 2. Errors 16 | 3. Testing 17 | 4. TDD 18 | 5. API Doc 19 | 6. Final 20 | The solution for each exercise is present in the corresponding *Review* folder. 21 | 22 | ### Local setup 23 | The instructions for setting up your local environment is available in the first exercise's [README](https://github.com/udacity/nd0044-c2-API-Development-and-Documentation-exercises/blob/master/1_Requests_Starter/README.md). 24 | 25 | 26 | >Feel free to suggest updates and bugs/fixes in the Pull Requests. 27 | --- 28 | --------------------------------------------------------------------------------