diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml new file mode 100644 index 00000000000..13fa7b16504 --- /dev/null +++ b/.github/workflows/manual.yml @@ -0,0 +1,46 @@ +# Workflow to ensure whenever a Github PR is submitted, +# a JIRA ticket gets created automatically. +name: Manual Workflow + +# Controls when the action will run. +on: + # Triggers the workflow on pull request events but only for the master branch + pull_request_target: + types: [assigned, opened, reopened] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + test-transition-issue: + name: Convert Github Issue to Jira Issue + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@master + + - name: Login + uses: atlassian/gajira-login@master + env: + JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} + JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} + JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} + + - name: Create NEW JIRA ticket + id: create + uses: atlassian/gajira-create@master + with: + project: CONUPDATE + issuetype: Task + summary: | + Github PR - nd0044 v2 Full Stack Web Developer (refresh) | Repo: FSND | PR# ${{github.event.number}} + description: | + Repo link: https://github.com/${{ github.repository }} + PR no. ${{ github.event.pull_request.number }} + PR title: ${{ github.event.pull_request.title }} + PR description: ${{ github.event.pull_request.description }} + In addition, please resolve other issues, if any. + fields: '{"components": [{"name":"nd0044 - Full Stack Nanodegree"}], "customfield_16449":"https://classroom.udacity.com/nanodegrees/nd0044/dashboard/overview", "customfield_16450":"Resolve the PR", "labels": ["github"], "priority":{"id": "4"}}' + + - name: Log created issue + run: echo "Issue ${{ steps.create.outputs.issue }} was created" diff --git a/.gitignore b/.gitignore index 24d321fdca7..04c5acf8d86 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ venv .Spotlight-V100 .Trashes ehthumbs.db -Thumbs.db \ No newline at end of file +Thumbs.db + +.github/** diff --git a/BasicFlaskAuth/requirements.txt b/BasicFlaskAuth/requirements.txt index ac1ad599809..b966a7772d1 100644 --- a/BasicFlaskAuth/requirements.txt +++ b/BasicFlaskAuth/requirements.txt @@ -9,11 +9,11 @@ Jinja2==2.10.1 lazy-object-proxy==1.4.0 MarkupSafe==1.1.1 mccabe==0.6.1 -pycryptodome==3.6.6 +pycryptodome==3.3.1 pylint==2.3.1 python-jose-cryptodome==1.3.2 six==1.12.0 -typed-ast==1.3.5 -Werkzeug==0.15.2 +typed-ast==1.4.2 +Werkzeug==0.15.6 wrapt==1.11.1 Flask-Cors==3.0.8 \ No newline at end of file diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000000..2a6bcb2832f --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @udacity/active-public-content \ No newline at end of file diff --git a/FlaskRecap/README.md b/FlaskRecap/README.md index 581cc97f815..fb2e1aa008b 100644 --- a/FlaskRecap/README.md +++ b/FlaskRecap/README.md @@ -4,9 +4,9 @@ A simple flask server to demonstrate basic flask. ## Getting Started -### Create a Virutal Enviornment +### Create a Virtual Environment -Follow instructions [here](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/) to create and activate virtual enviornment for this project. +Follow instructions [here](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/) to create and activate virtual environment for this project. ### Install Dependencies diff --git a/FlaskRecap/requirements.txt b/FlaskRecap/requirements.txt index 30cb0334607..d0a3120050c 100644 --- a/FlaskRecap/requirements.txt +++ b/FlaskRecap/requirements.txt @@ -3,4 +3,4 @@ Flask==1.0.3 itsdangerous==1.1.0 Jinja2==2.10.1 MarkupSafe==1.1.1 -Werkzeug==0.15.4 +Werkzeug==0.15.5 diff --git a/FlaskRecap/udacity-fsnd-flaskrecap.postman_collection.json b/FlaskRecap/udacity-fsnd-flaskrecap.postman_collection.json index b4531ce7e09..ba432e320ea 100644 --- a/FlaskRecap/udacity-fsnd-flaskrecap.postman_collection.json +++ b/FlaskRecap/udacity-fsnd-flaskrecap.postman_collection.json @@ -33,7 +33,7 @@ "method": "GET", "header": [], "url": { - "raw": "http://127.0.0.1:5000/greeting", + "raw": "http://127.0.0.1:5000/greeting/es", "protocol": "http", "host": [ "127", @@ -83,4 +83,4 @@ "response": [] } ] -} \ No newline at end of file +} diff --git a/README.md b/README.md index 72a66310e0d..2fe08e7f5af 100644 --- a/README.md +++ b/README.md @@ -1 +1,11 @@ -This is the public repository for Udacity's Full-Stack Nanodegree program. +## Full Stack Web Developer Nanodegree (nd0044 v2) +This is the public repository for Udacity's Full-Stack Nanodegree program. Here, you can find starter-code the following projects: + +* *01_fyyur/starter_code* - This is the project from C1. SQL and Data Modeling for the Web +* *02_trivia_api/starter* - This is the project from C2. API Development and Documentation +* *03_coffee_shop_full_stack/starter_code* - This is the project from C3. Identity and Access Management +* *capstone* - This is the final project of this Nanodegree. + +Feel free to suggest edits in the current repo by raising a PR. + + diff --git a/environment.yml b/environment.yml new file mode 100644 index 00000000000..89998afd838 --- /dev/null +++ b/environment.yml @@ -0,0 +1,6 @@ +name: FSND +channels: + - conda-forge + - defaults +dependencies: + - python=3.9 \ No newline at end of file diff --git a/projects/01_fyyur/starter_code/README.md b/projects/01_fyyur/starter_code/README.md index 30364eef74d..79a9a26e66e 100644 --- a/projects/01_fyyur/starter_code/README.md +++ b/projects/01_fyyur/starter_code/README.md @@ -1,13 +1,13 @@ Fyyur ----- -### Introduction +## Introduction Fyyur is a musical venue and artist booking site that facilitates the discovery and bookings of shows between local performing artists and venues. This site lets you list new artists and venues, discover them, and list shows with artists as a venue owner. Your job is to build out the data models to power the API endpoints for the Fyyur site by connecting to a PostgreSQL database for storing, querying, and creating information about artists and venues on Fyyur. -### Overview +## Overview This app is nearly complete. It is only missing one thing… real data! While the views and controllers are defined in this application, it is missing models and model interactions to be able to store retrieve, and update data from a database. By the end of this project, you should have a fully functioning site that is at least capable of doing the following, if not more, using a PostgreSQL database: @@ -17,22 +17,44 @@ This app is nearly complete. It is only missing one thing… real data! While th We want Fyyur to be the next new platform that artists and musical venues can use to find each other, and discover new music shows. Let's make that happen! -### Tech Stack - -Our tech stack will include: - -* **SQLAlchemy ORM** to be our ORM library of choice -* **PostgreSQL** as our database of choice -* **Python3** and **Flask** as our server language and server framework -* **Flask-Migrate** for creating and running schema migrations -* **HTML**, **CSS**, and **Javascript** with [Bootstrap 3](https://getbootstrap.com/docs/3.4/customize/) for our website's frontend - -### Main Files: Project Structure +## Tech Stack (Dependencies) + +### 1. Backend Dependencies +Our tech stack will include the following: + * **virtualenv** as a tool to create isolated Python environments + * **SQLAlchemy ORM** to be our ORM library of choice + * **PostgreSQL** as our database of choice + * **Python3** and **Flask** as our server language and server framework + * **Flask-Migrate** for creating and running schema migrations +You can download and install the dependencies mentioned above using `pip` as: +``` +pip install virtualenv +pip install SQLAlchemy +pip install postgres +pip install Flask +pip install Flask-Migrate +``` +> **Note** - If we do not mention the specific version of a package, then the default latest stable package will be installed. + +### 2. Frontend Dependencies +You must have the **HTML**, **CSS**, and **Javascript** with [Bootstrap 3](https://getbootstrap.com/docs/3.4/customize/) for our website's frontend. Bootstrap can only be installed by Node Package Manager (NPM). Therefore, if not already, download and install the [Node.js](https://nodejs.org/en/download/). Windows users must run the executable as an Administrator, and restart the computer after installation. After successfully installing the Node, verify the installation as shown below. +``` +node -v +npm -v +``` +Install [Bootstrap 3](https://getbootstrap.com/docs/3.3/getting-started/) for the website's frontend: +``` +npm init -y +npm install bootstrap@3 +``` + + +## Main Files: Project Structure ```sh ├── README.md ├── app.py *** the main driver of the app. Includes your SQLAlchemy models. - "python app.py" to run after installing dependences + "python app.py" to run after installing dependencies ├── config.py *** Database URLs, CSRF generation, etc ├── error.log ├── forms.py *** Your forms @@ -72,15 +94,16 @@ Instructions 1. Understand the Project Structure (explained above) and where important files are located. 2. Build and run local development following the Development Setup steps below. 3. Fill in the missing functionality in this application: this application currently pulls in fake data, and needs to now connect to a real database and talk to a real backend. -3. Fill out every `TODO` section throughout the codebase. We suggest going in order of the following: - - 1. Connect to a database in `config.py`. A project submission that uses a local database connection is fine. - 2. Using SQLAlchemy, set up normalized models for the objects we support in our web app in the Models section of `app.py`. Check out the sample pages provided at /artists/1, /venues/1, and /shows/1 for examples of the data we want to model, using all of the learned best practices in database schema design. Implement missing model properties and relationships using database migrations via Flask-Migrate. - 3. Implement form submissions for creating new Venues, Artists, and Shows. There should be proper constraints, powering the `/create` endpoints that serve the create form templates, to avoid duplicate or nonsensical form submissions. Submitting a form should create proper new records in the database. - 4. Implement the controllers for listing venues, artists, and shows. Note the structure of the mock data used. We want to keep the structure of the mock data. - 5. Implement search, powering the `/search` endpoints that serve the application's search functionalities. - 6. Serve venue and artist detail pages, powering the `/` endpoints that power the detail pages. +4. Fill out every `TODO` section throughout the codebase. We suggest going in order of the following: + * Connect to a database in `config.py`. A project submission that uses a local database connection is fine. + * Using SQLAlchemy, set up normalized models for the objects we support in our web app in the Models section of `app.py`. Check out the sample pages provided at /artists/1, /venues/1, and /shows for examples of the data we want to model, using all of the learned best practices in database schema design. Implement missing model properties and relationships using database migrations via Flask-Migrate. + * Implement form submissions for creating new Venues, Artists, and Shows. There should be proper constraints, powering the `/create` endpoints that serve the create form templates, to avoid duplicate or nonsensical form submissions. Submitting a form should create proper new records in the database. + * Implement the controllers for listing venues, artists, and shows. Note the structure of the mock data used. We want to keep the structure of the mock data. + * Implement search, powering the `/search` endpoints that serve the application's search functionalities. + * Serve venue and artist detail pages, powering the `/` endpoints that power the detail pages. +#### Data Handling with `Flask-WTF` Forms +The starter codes use an interactive form builder library called [Flask-WTF](https://flask-wtf.readthedocs.io/). This library provides useful functionality, such as form validation and error handling. You can peruse the Show, Venue, and Artist form builders in `forms.py` file. The WTForms are instantiated in the `app.py` file. For example, in the `create_shows()` function, the Show form is instantiated from the command: `form = ShowForm()`. To manage the request from Flask-WTF form, each field from the form has a `data` attribute containing the value from user input. For example, to handle the `venue_id` data from the Venue form, you can use: `show = Show(venue_id=form.venue_id.data)`, instead of using `request.form['venue_id']`. Acceptance Criteria ----- @@ -96,6 +119,7 @@ Acceptance Criteria * A user should be able to click on the venue for an upcoming show in the Artist's page, and on that Venue's page, see the same show in the Venue Page's upcoming shows section. 4. As a fellow developer on this application, I should be able to run `flask db migrate`, and have my local database (once set up and created) be populated with the right tables to run this application and have it interact with my local postgres server, serving the application's needs completely with real data I can seed my local database with. * The models should be completed (see TODOs in the `Models` section of `app.py`) and model the objects used throughout Fyyur. + * Define the models in a different file to follow [Separation of Concerns](https://en.wikipedia.org/wiki/Separation_of_concerns) design principles. You can refactor the models to a new file, such as `models.py`. * The right _type_ of relationship and parent-child dynamics between models should be accurately identified and fit the needs of this particular application. * The relationship between the models should be accurately configured, and referential integrity amongst the models should be preserved. * `flask db migrate` should work, and populate my local postgres database with properly configured tables for this application's objects, including proper columns, column data types, constraints, defaults, and relationships that completely satisfy the needs of this application. The proper type of relationship between venues, artists, and shows should be configured. @@ -110,34 +134,50 @@ Looking to go above and beyond? This is the right section for you! Here are some Best of luck in your final project! Fyyur depends on you! -### Development Setup - -First, [install Flask](http://flask.pocoo.org/docs/1.0/installation/#install-flask) if you haven't already. - - ``` - $ cd ~ - $ sudo pip3 install Flask - ``` - -To start and run the local development server, -1. Initialize and activate a virtualenv: - ``` - $ cd YOUR_PROJECT_DIRECTORY_PATH/ - $ virtualenv --no-site-packages env - $ source env/bin/activate - ``` - -2. Install the dependencies: - ``` - $ pip install -r requirements.txt - ``` - -3. Run the development server: - ``` - $ export FLASK_APP=myapp - $ export FLASK_ENV=development # enables debug mode - $ python3 app.py - ``` +## Development Setup +1. **Download the project starter code locally** +``` +git clone https://github.com/udacity/FSND.git +cd FSND/projects/01_fyyur/starter_code +``` + +2. **Create an empty repository in your Github account online. To change the remote repository path in your local repository, use the commands below:** +``` +git remote -v +git remote remove origin +git remote add origin /.git> +git branch -M master +``` +Once you have finished editing your code, you can push the local repository to your Github account using the following commands. +``` +git add . --all +git commit -m "your comment" +git push -u origin master +``` + +3. **Initialize and activate a virtualenv using:** +``` +python -m virtualenv env +source env/bin/activate +``` +>**Note** - In Windows, the `env` does not have a `bin` directory. Therefore, you'd use the analogous command shown below: +``` +source env/Scripts/activate +``` + +4. **Install the dependencies:** +``` +pip install -r requirements.txt +``` + +5. **Run the development server:** +``` +export FLASK_APP=myapp +export FLASK_ENV=development # enables debug mode +python3 app.py +``` + +6. **Verify on the Browser**
+Navigate to project homepage [http://127.0.0.1:5000/](http://127.0.0.1:5000/) or [http://localhost:5000](http://localhost:5000) -4. Navigate to Home page [http://localhost:5000](http://localhost:5000) diff --git a/projects/01_fyyur/starter_code/app.py b/projects/01_fyyur/starter_code/app.py index b30c04a42a2..299c5db6f89 100644 --- a/projects/01_fyyur/starter_code/app.py +++ b/projects/01_fyyur/starter_code/app.py @@ -67,7 +67,7 @@ def format_datetime(value, format='medium'): format="EEEE MMMM, d, y 'at' h:mma" elif format == 'medium': format="EE MM, dd, y h:mma" - return babel.dates.format_datetime(date, format) + return babel.dates.format_datetime(date, format, locale='en') app.jinja_env.filters['datetime'] = format_datetime @@ -86,7 +86,7 @@ def index(): @app.route('/venues') def venues(): # TODO: replace with real venues data. - # num_shows should be aggregated based on number of upcoming shows per venue. + # num_upcoming_shows should be aggregated based on number of upcoming shows per venue. data=[{ "city": "San Francisco", "state": "CA", @@ -112,7 +112,7 @@ def venues(): @app.route('/venues/search', methods=['POST']) def search_venues(): - # TODO: implement search on artists with partial string search. Ensure it is case-insensitive. + # TODO: implement search on venues with partial string search. Ensure it is case-insensitive. # seach for Hop should return "The Musical Hop". # search for "Music" should return "The Musical Hop" and "Park Square Live Music & Coffee" response={ @@ -272,8 +272,8 @@ def search_artists(): @app.route('/artists/') def show_artist(artist_id): - # shows the venue page with the given venue_id - # TODO: replace with real venue data from the venues table, using venue_id + # shows the artist page with the given artist_id + # TODO: replace with real artist data from the artist table, using artist_id data1={ "id": 4, "name": "Guns N Petals", @@ -430,7 +430,6 @@ def create_artist_submission(): def shows(): # displays list of shows at /shows # TODO: replace with real venues data. - # num_shows should be aggregated based on number of upcoming shows per venue. data=[{ "venue_id": 1, "venue_name": "The Musical Hop", diff --git a/projects/01_fyyur/starter_code/forms.py b/projects/01_fyyur/starter_code/forms.py index 42771beb5dd..fbaa95d8949 100644 --- a/projects/01_fyyur/starter_code/forms.py +++ b/projects/01_fyyur/starter_code/forms.py @@ -1,6 +1,6 @@ from datetime import datetime from flask_wtf import Form -from wtforms import StringField, SelectField, SelectMultipleField, DateTimeField +from wtforms import StringField, SelectField, SelectMultipleField, DateTimeField, BooleanField from wtforms.validators import DataRequired, AnyOf, URL class ShowForm(Form): @@ -116,6 +116,17 @@ class VenueForm(Form): facebook_link = StringField( 'facebook_link', validators=[URL()] ) + website_link = StringField( + 'website_link' + ) + + seeking_talent = BooleanField( 'seeking_talent' ) + + seeking_description = StringField( + 'seeking_description' + ) + + class ArtistForm(Form): name = StringField( @@ -181,14 +192,13 @@ class ArtistForm(Form): ] ) phone = StringField( - # TODO implement validation logic for state + # TODO implement validation logic for phone 'phone' ) image_link = StringField( 'image_link' ) genres = SelectMultipleField( - # TODO implement enum restriction 'genres', validators=[DataRequired()], choices=[ ('Alternative', 'Alternative'), @@ -211,10 +221,19 @@ class ArtistForm(Form): ('Soul', 'Soul'), ('Other', 'Other'), ] - ) + ) facebook_link = StringField( # TODO implement enum restriction 'facebook_link', validators=[URL()] - ) + ) + + website_link = StringField( + 'website_link' + ) + + seeking_venue = BooleanField( 'seeking_venue' ) + + seeking_description = StringField( + 'seeking_description' + ) -# TODO IMPLEMENT NEW ARTIST FORM AND NEW SHOW FORM diff --git a/projects/01_fyyur/starter_code/requirements.txt b/projects/01_fyyur/starter_code/requirements.txt index 31f089f5ba4..16d8ace896c 100755 --- a/projects/01_fyyur/starter_code/requirements.txt +++ b/projects/01_fyyur/starter_code/requirements.txt @@ -1,4 +1,5 @@ -babel +babel==2.9.0 python-dateutil==2.6.0 -flask-moment -flask-wtf \ No newline at end of file +flask-moment==0.11.0 +flask-wtf==0.14.3 +flask_sqlalchemy==2.4.4 diff --git a/projects/01_fyyur/starter_code/templates/forms/edit_artist.html b/projects/01_fyyur/starter_code/templates/forms/edit_artist.html index 87036546900..02472fe5d63 100644 --- a/projects/01_fyyur/starter_code/templates/forms/edit_artist.html +++ b/projects/01_fyyur/starter_code/templates/forms/edit_artist.html @@ -26,13 +26,34 @@

Edit artist {{ artist.name }}

Ctrl+Click to select multiple - {{ form.genres(class_ = 'form-control', placeholder='Genres, separated by commas', id=form.state, autofocus = true) }} + {{ form.genres(class_ = 'form-control', placeholder='Genres, separated by commas', autofocus = true) }}
- - {{ form.facebook_link(class_ = 'form-control', placeholder='http://', id=form.state, autofocus = true) }} + + {{ form.facebook_link(class_ = 'form-control', placeholder='http://', autofocus = true) }}
+ +
+ + {{ form.image_link(class_ = 'form-control', placeholder='http://', autofocus = true) }} +
+ +
+ + {{ form.website_link(class_ = 'form-control', placeholder='http://', autofocus = true) }} +
+ +
+ + {{ form.seeking_venue(placeholder='Venue', autofocus = true) }} +
+ +
+ + {{ form.seeking_description(class_ = 'form-control', autofocus = true) }} +
+ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/projects/01_fyyur/starter_code/templates/forms/edit_venue.html b/projects/01_fyyur/starter_code/templates/forms/edit_venue.html index 1c3469f1bb9..caeb78d39a8 100644 --- a/projects/01_fyyur/starter_code/templates/forms/edit_venue.html +++ b/projects/01_fyyur/starter_code/templates/forms/edit_venue.html @@ -30,13 +30,34 @@

Edit venue {{ venue.name }} Ctrl+Click to select multiple - {{ form.genres(class_ = 'form-control', placeholder='Genres, separated by commas', id=form.state, autofocus = true) }} + {{ form.genres(class_ = 'form-control', placeholder='Genres, separated by commas', autofocus = true) }}
- - {{ form.facebook_link(class_ = 'form-control', placeholder='http://', id=form.state, autofocus = true) }} + + {{ form.facebook_link(class_ = 'form-control', placeholder='http://', autofocus = true) }} +
+ +
+ + {{ form.image_link(class_ = 'form-control', placeholder='http://', autofocus = true) }} +
+ +
+ + {{ form.website_link(class_ = 'form-control', placeholder='http://', autofocus = true) }}
+ +
+ + {{ form.seeking_talent(placeholder='Venue', autofocus = true) }} +
+ +
+ + {{ form.seeking_description(class_ = 'form-control', autofocus = true) }} +
+ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/projects/01_fyyur/starter_code/templates/forms/new_artist.html b/projects/01_fyyur/starter_code/templates/forms/new_artist.html index 33062fa2777..067391bd826 100644 --- a/projects/01_fyyur/starter_code/templates/forms/new_artist.html +++ b/projects/01_fyyur/starter_code/templates/forms/new_artist.html @@ -26,13 +26,33 @@

List a new artist

Ctrl+Click to select multiple - {{ form.genres(class_ = 'form-control', placeholder='Genres, separated by commas', id=form.state, autofocus = true) }} + {{ form.genres(class_ = 'form-control', placeholder='Genres, separated by commas', autofocus = true) }}
- - {{ form.facebook_link(class_ = 'form-control', placeholder='http://', id=form.state, autofocus = true) }} + + {{ form.facebook_link(class_ = 'form-control', placeholder='http://', autofocus = true) }}
- + +
+ + {{ form.image_link(class_ = 'form-control', placeholder='http://', autofocus = true) }} +
+ +
+ + {{ form.website_link(class_ = 'form-control', placeholder='http://', autofocus = true) }} +
+ +
+ + {{ form.seeking_venue(placeholder='Venue', autofocus = true) }} +
+ +
+ + {{ form.seeking_description(class_ = 'form-control', autofocus = true) }} +
+ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/projects/01_fyyur/starter_code/templates/forms/new_venue.html b/projects/01_fyyur/starter_code/templates/forms/new_venue.html index a0bc91ae27d..d21977cc055 100644 --- a/projects/01_fyyur/starter_code/templates/forms/new_venue.html +++ b/projects/01_fyyur/starter_code/templates/forms/new_venue.html @@ -2,7 +2,7 @@ {% block title %}New Venue{% endblock %} {% block content %}
+ +
+ + {{ form.image_link(class_ = 'form-control', placeholder='http://', autofocus = true) }} +
+ +
+ + {{ form.website_link(class_ = 'form-control', placeholder='http://', autofocus = true) }} +
+ +
+ + {{ form.seeking_talent(placeholder='Venue', autofocus = true) }} +
+ +
+ + {{ form.seeking_description(class_ = 'form-control', placeholder='Description', autofocus = true) }} +
-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/projects/01_fyyur/starter_code/templates/pages/show_artist.html b/projects/01_fyyur/starter_code/templates/pages/show_artist.html index 1068ad926ce..2c33e52df44 100644 --- a/projects/01_fyyur/starter_code/templates/pages/show_artist.html +++ b/projects/01_fyyur/starter_code/templates/pages/show_artist.html @@ -72,5 +72,7 @@
{{ show.start_time|datetime('full') }}
+
+ {% endblock %} diff --git a/projects/01_fyyur/starter_code/templates/pages/show_venue.html b/projects/01_fyyur/starter_code/templates/pages/show_venue.html index c2c0ac856dc..39d562b0689 100644 --- a/projects/01_fyyur/starter_code/templates/pages/show_venue.html +++ b/projects/01_fyyur/starter_code/templates/pages/show_venue.html @@ -75,5 +75,7 @@

{{ show.start_time|datetime('full') }}
+ + {% endblock %} diff --git a/projects/02_trivia_api/starter/.gitignore b/projects/02_trivia_api/starter/.gitignore index 24d321fdca7..412283d0045 100644 --- a/projects/02_trivia_api/starter/.gitignore +++ b/projects/02_trivia_api/starter/.gitignore @@ -10,4 +10,6 @@ venv .Spotlight-V100 .Trashes ehthumbs.db -Thumbs.db \ No newline at end of file +Thumbs.db + +**/node_modules diff --git a/projects/02_trivia_api/starter/README.md b/projects/02_trivia_api/starter/README.md index e9ae1c6bd82..7addaab2194 100644 --- a/projects/02_trivia_api/starter/README.md +++ b/projects/02_trivia_api/starter/README.md @@ -1,44 +1,52 @@ # Full Stack API Final Project + ## Full Stack Trivia -Udacity is invested in creating bonding experiences for its employees and students. A bunch of team members got the idea to hold trivia on a regular basis and created a webpage to manage the trivia app and play the game, but their API experience is limited and still needs to be built out. +Udacity is invested in creating bonding experiences for its employees and students. A bunch of team members got the idea to hold trivia on a regular basis and created a webpage to manage the trivia app and play the game, but their API experience is limited and still needs to be built out. That's where you come in! Help them finish the trivia app so they can start holding trivia and seeing who's the most knowledgeable of the bunch. The application must: -1) Display questions - both all questions and by category. Questions should show the question, category and difficulty rating by default and can show/hide the answer. -2) Delete questions. -3) Add questions and require that they include question and answer text. -4) Search for questions based on a text query string. -5) Play the quiz game, randomizing either all questions or within a specific category. - -Completing this trivia app will give you the ability to structure plan, implement, and test an API - skills essential for enabling your future applications to communicate with others. - -## Tasks +1. Display questions - both all questions and by category. Questions should show the question, category and difficulty rating by default and can show/hide the answer. +2. Delete questions. +3. Add questions and require that they include question and answer text. +4. Search for questions based on a text query string. +5. Play the quiz game, randomizing either all questions or within a specific category. -There are `TODO` comments throughout project. Start by reading the READMEs in: - -1. [`./frontend/`](./frontend/README.md) -2. [`./backend/`](./backend/README.md) - -We recommend following the instructions in those files in order. This order will look familiar from our prior work in the course. +Completing this trivia app will give you the ability to structure plan, implement, and test an API - skills essential for enabling your future applications to communicate with others. ## Starting and Submitting the Project -[Fork](https://help.github.com/en/articles/fork-a-repo) the [project repository]() and [Clone](https://help.github.com/en/articles/cloning-a-repository) your forked repository to your machine. Work on the project locally and make sure to push all your changes to the remote repository before submitting the link to your repository in the Classroom. +[Fork](https://help.github.com/en/articles/fork-a-repo) the [project repository](https://github.com/udacity/FSND/blob/master/projects/02_trivia_api/starter) and [Clone](https://help.github.com/en/articles/cloning-a-repository) your forked repository to your machine. Work on the project locally and make sure to push all your changes to the remote repository before submitting the link to your repository in the Classroom. +>Once you're ready, you can submit your project on the last page. ## About the Stack -We started the full stack application for you. It is desiged with some key functional areas: +We started the full stack application for you. It is designed with some key functional areas: ### Backend +The [./backend](https://github.com/udacity/FSND/blob/master/projects/02_trivia_api/starter/backend/README.md) directory contains a partially completed Flask and SQLAlchemy server. You will work primarily in `__init__.py` to define your endpoints and can reference models.py for DB and SQLAlchemy setup. These are the files you'd want to edit in the backend: + +1. *./backend/flaskr/`__init__.py`* +2. *./backend/test_flaskr.py* -The `./backend` directory contains a partially completed Flask and SQLAlchemy server. You will work primarily in app.py to define your endpoints and can reference models.py for DB and SQLAlchemy setup. ### Frontend -The `./frontend` directory contains a complete React frontend to consume the data from the Flask server. You will need to update the endpoints after you define them in the backend. Those areas are marked with TODO and can be searched for expediency. +The [./frontend](https://github.com/udacity/FSND/blob/master/projects/02_trivia_api/starter/frontend/README.md) directory contains a complete React frontend to consume the data from the Flask server. If you have prior experience building a frontend application, you should feel free to edit the endpoints as you see fit for the backend you design. If you do not have prior experience building a frontend application, you should read through the frontend code before starting and make notes regarding: + +1. What are the end points and HTTP methods the frontend is expecting to consume? +2. How are the requests from the frontend formatted? Are they expecting certain parameters or payloads? + +Pay special attention to what data the frontend is expecting from each API response to help guide how you format your API. The places where you may change the frontend behavior, and where you should be looking for the above information, are marked with `TODO`. These are the files you'd want to edit in the frontend: + +1. *./frontend/src/components/QuestionView.js* +2. *./frontend/src/components/FormView.js* +3. *./frontend/src/components/QuizView.js* + + +By making notes ahead of time, you will practice the core skill of being able to read and understand code and will have a simple plan to follow to build out the endpoints of your backend API. + -Pay special attention to what data the frontend is expecting from each API response to help guide how you format your API. -[View the README.md within ./frontend for more details.](./frontend/README.md) +>View the [README within ./frontend for more details.](./frontend/README.md) diff --git a/projects/02_trivia_api/starter/backend/README.md b/projects/02_trivia_api/starter/backend/README.md index 6692aed3e18..a377c59b350 100644 --- a/projects/02_trivia_api/starter/backend/README.md +++ b/projects/02_trivia_api/starter/backend/README.md @@ -1,82 +1,93 @@ -# Full Stack Trivia API Backend +# Backend - Full Stack Trivia API -## Getting Started +### Installing Dependencies for the Backend -### Installing Dependencies +1. **Python 3.7** - Follow instructions to install the latest version of python for your platform in the [python docs](https://docs.python.org/3/using/unix.html#getting-and-installing-the-latest-version-of-python) -#### Python 3.7 -Follow instructions to install the latest version of python for your platform in the [python docs](https://docs.python.org/3/using/unix.html#getting-and-installing-the-latest-version-of-python) +2. **Virtual Enviornment** - We recommend working within a virtual environment whenever using Python for projects. This keeps your dependencies for each project separate and organaized. Instructions for setting up a virual enviornment for your platform can be found in the [python docs](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/) -#### Virtual Enviornment - -We recommend working within a virtual environment whenever using Python for projects. This keeps your dependencies for each project separate and organaized. Instructions for setting up a virual enviornment for your platform can be found in the [python docs](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/) - -#### PIP Dependencies - -Once you have your virtual environment setup and running, install dependencies by naviging to the `/backend` directory and running: +3. **PIP Dependencies** - Once you have your virtual environment setup and running, install dependencies by naviging to the `/backend` directory and running: ```bash pip install -r requirements.txt ``` - This will install all of the required packages we selected within the `requirements.txt` file. -##### Key Dependencies -- [Flask](http://flask.pocoo.org/) is a lightweight backend microservices framework. Flask is required to handle requests and responses. +4. **Key Dependencies** + - [Flask](http://flask.pocoo.org/) is a lightweight backend microservices framework. Flask is required to handle requests and responses. -- [SQLAlchemy](https://www.sqlalchemy.org/) is the Python SQL toolkit and ORM we'll use handle the lightweight sqlite database. You'll primarily work in app.py and can reference models.py. + - [SQLAlchemy](https://www.sqlalchemy.org/) is the Python SQL toolkit and ORM we'll use handle the lightweight sqlite database. You'll primarily work in app.py and can reference models.py. -- [Flask-CORS](https://flask-cors.readthedocs.io/en/latest/#) is the extension we'll use to handle cross origin requests from our frontend server. + - [Flask-CORS](https://flask-cors.readthedocs.io/en/latest/#) is the extension we'll use to handle cross origin requests from our frontend server. -## Database Setup +### Database Setup With Postgres running, restore a database using the trivia.psql file provided. From the backend folder in terminal run: ```bash psql trivia < trivia.psql ``` -## Running the server +### Running the server -From within the `backend` directory first ensure you are working using your created virtual environment. +From within the `./src` directory first ensure you are working using your created virtual environment. To run the server, execute: ```bash -export FLASK_APP=flaskr -export FLASK_ENV=development -flask run +flask run --reload ``` -Setting the `FLASK_ENV` variable to `development` will detect file changes and restart the server automatically. +The `--reload` flag will detect file changes and restart the server automatically. + +## ToDo Tasks +These are the files you'd want to edit in the backend: -Setting the `FLASK_APP` variable to `flaskr` directs flask to use the `flaskr` directory and the `__init__.py` file to find the application. +1. *./backend/flaskr/`__init__.py`* +2. *./backend/test_flaskr.py* -## Tasks -One note before you delve into your tasks: for each endpoint you are expected to define the endpoint and response data. The frontend will be a plentiful resource because it is set up to expect certain endpoints and response data formats already. You should feel free to specify endpoints in your own way; if you do so, make sure to update the frontend or you will get some unexpected behavior. +One note before you delve into your tasks: for each endpoint, you are expected to define the endpoint and response data. The frontend will be a plentiful resource because it is set up to expect certain endpoints and response data formats already. You should feel free to specify endpoints in your own way; if you do so, make sure to update the frontend or you will get some unexpected behavior. 1. Use Flask-CORS to enable cross-domain requests and set response headers. + + 2. Create an endpoint to handle GET requests for questions, including pagination (every 10 questions). This endpoint should return a list of questions, number of total questions, current category, categories. + + 3. Create an endpoint to handle GET requests for all available categories. + + 4. Create an endpoint to DELETE question using a question ID. + + 5. Create an endpoint to POST a new question, which will require the question and answer text, category, and difficulty score. + + 6. Create a POST endpoint to get questions based on category. + + 7. Create a POST endpoint to get questions based on a search term. It should return any questions for whom the search term is a substring of the question. + + 8. Create a POST endpoint to get questions to play the quiz. This endpoint should take category and previous question parameters and return a random questions within the given category, if provided, and that is not one of the previous questions. + + 9. Create error handlers for all expected errors including 400, 404, 422 and 500. -REVIEW_COMMENT + + +## Review Comment to the Students ``` This README is missing documentation of your endpoints. Below is an example for your endpoint to get all categories. Please use it as a reference for creating your documentation and resubmit your code. Endpoints -GET '/categories' +GET '/api/v1.0/categories' GET ... POST ... DELETE ... -GET '/categories' +GET '/api/v1.0/categories' - Fetches a dictionary of categories in which the keys are the ids and the value is the corresponding string of the category - Request Arguments: None - Returns: An object with a single key, categories, that contains a object of id: category_string key:value pairs. @@ -97,4 +108,4 @@ dropdb trivia_test createdb trivia_test psql trivia_test < trivia.psql python test_flaskr.py -``` \ No newline at end of file +``` diff --git a/projects/02_trivia_api/starter/backend/requirements.txt b/projects/02_trivia_api/starter/backend/requirements.txt index 7c73e0d30db..fdf8b85aaba 100644 --- a/projects/02_trivia_api/starter/backend/requirements.txt +++ b/projects/02_trivia_api/starter/backend/requirements.txt @@ -11,4 +11,4 @@ psycopg2-binary==2.8.2 pytz==2019.1 six==1.12.0 SQLAlchemy==1.3.4 -Werkzeug==0.15.4 +Werkzeug==0.15.5 diff --git a/projects/02_trivia_api/starter/frontend/README.md b/projects/02_trivia_api/starter/frontend/README.md index 019ce3e039e..4d22cbfd47e 100644 --- a/projects/02_trivia_api/starter/frontend/README.md +++ b/projects/02_trivia_api/starter/frontend/README.md @@ -1,28 +1,24 @@ -# Full Stack Trivia API Frontend +# Frontend - Full Stack Trivia API -## Getting Setup +### Getting Setup > _tip_: this frontend is designed to work with [Flask-based Backend](../backend). It is recommended you stand up the backend first, test using Postman or curl, update the endpoints in the frontend, and then the frontend should integrate smoothly. ### Installing Dependencies -#### Installing Node and NPM - +1. **Installing Node and NPM**
This project depends on Nodejs and Node Package Manager (NPM). Before continuing, you must download and install Node (the download includes NPM) from [https://nodejs.com/en/download](https://nodejs.org/en/download/). -#### Installing project dependencies - +2. **Installing project dependencies**
This project uses NPM to manage software dependencies. NPM Relies on the package.json file located in the `frontend` directory of this repository. After cloning, open your terminal and run: - ```bash npm install ``` - >_tip_: **npm i** is shorthand for **npm install** -## Required Tasks +# Required Tasks -## Running Your Frontend in Dev Mode +### Running Your Frontend in Dev Mode The frontend app was built using create-react-app. In order to run the app in development mode use ```npm start```. You can change the script in the ```package.json``` file. @@ -32,20 +28,159 @@ Open [http://localhost:3000](http://localhost:3000) to view it in the browser. T npm start ``` -## Request Formatting +### Request Formatting -The frontend should be fairly straightforward and disgestible. You'll primarily work within the ```components``` folder in order to edit the endpoints utilized by the components. While working on your backend request handling and response formatting, you can reference the frontend to view how it parses the responses. +The frontend should be fairly straightforward and disgestible. You'll primarily work within the ```components``` folder in order to understand, and if you so choose edit, the endpoints utilized by the components. While working on your backend request handling and response formatting, you can reference the frontend to view how it parses the responses. -After you complete your endpoints, ensure you return to and update the frontend to make request and handle responses appropriately: -- Correct endpoints -- Update response body handling +After you complete your endpoints, ensure you return to the frontend to confirm your API handles requests and responses appropriately: +- Endpoints defined as expected by the frontend +- Response body provided as expected by the frontend -## Optional: Styling +### Optional: Updating Endpoints and API behavior + +Would you rather the API had different behavior - different endpoints, return the response body in a different format? Go for it! Make the updates to your API and the corresponding updates to the frontend so it works with your API seamlessly. + + +### Optional: Styling In addition, you may want to customize and style the frontend by editing the CSS in the ```stylesheets``` folder. -## Optional: Game Play Mechanics +### Optional: Game Play Mechanics Currently, when a user plays the game they play up to five questions of the chosen category. If there are fewer than five questions in a category, the game will end when there are no more questions in that category. -You can optionally update this game play to increase the number of questions or whatever other game mechanics you decide. Make sure to specify the new mechanics of the game in the README of the repo you submit so the reviewers are aware that the behavior is correct. \ No newline at end of file +You can optionally update this game play to increase the number of questions or whatever other game mechanics you decide. Make sure to specify the new mechanics of the game in the README of the repo you submit so the reviewers are aware that the behavior is correct. + + + +>**Spoiler Alert:** If needed, there are details below regarding the expected endpoints and behavior. But, ONLY consult there if necessary, so you give yourself the opportunity to practice understanding code! + +# DO NOT PROCEED: ENDPOINT SPOILERS +>Only read the below to confirm your notes regarding the expected API endpoint behavior based on reading the frontend codebase. + + +**Here are the expected endpoints and behavior**: + + +```js +GET '/categories' +- Fetches a dictionary of categories in which the keys are the ids and the value is the corresponding string of the category +- Request Arguments: None +- Returns: An object with a single key, categories, that contains an object of id: category_string key:value pairs. +{ + 'categories': { '1' : "Science", + '2' : "Art", + '3' : "Geography", + '4' : "History", + '5' : "Entertainment", + '6' : "Sports" } +} +``` + + +```js +GET '/questions?page=${integer}' +- Fetches a paginated set of questions, a total number of questions, all categories and current category string. +- Request Arguments: page - integer +- Returns: An object with 10 paginated questions, total questions, object including all categories, and current category string +{ + 'questions': [ + { + 'id': 1, + 'question': 'This is a question', + 'answer': 'This is an answer', + 'difficulty': 5, + 'category': 2 + }, + ], + 'totalQuestions': 100, + 'categories': { '1' : "Science", + '2' : "Art", + '3' : "Geography", + '4' : "History", + '5' : "Entertainment", + '6' : "Sports" }, + 'currentCategory': 'History' +} +``` + +```js +GET '/categories/${id}/questions' +- Fetches questions for a cateogry specified by id request argument +- Request Arguments: id - integer +- Returns: An object with questions for the specified category, total questions, and current category string +{ + 'questions': [ + { + 'id': 1, + 'question': 'This is a question', + 'answer': 'This is an answer', + 'difficulty': 5, + 'category': 4 + }, + ], + 'totalQuestions': 100, + 'currentCategory': 'History' +} +``` + +```js +DELETE '/questions/${id}' +- Deletes a specified question using the id of the question +- Request Arguments: id - integer +- Returns: Does not need to return anything besides the appropriate HTTP status code. Optionally can return the id of the question. If you are able to modify the frontend, you can have it remove the question using the id instead of refetching the questions. +``` + +```js +POST '/quizzes' +- Sends a post request in order to get the next question +- Request Body: +{'previous_questions': an array of question id's such as [1, 4, 20, 15] +'quiz_category': a string of the current category } +- Returns: a single new question object +{ + 'question': { + 'id': 1, + 'question': 'This is a question', + 'answer': 'This is an answer', + 'difficulty': 5, + 'category': 4 + } +} +``` + +```js +POST '/questions' +- Sends a post request in order to add a new question +- Request Body: +{ + 'question': 'Heres a new question string', + 'answer': 'Heres a new answer string', + 'difficulty': 1, + 'category': 3, +} +- Returns: Does not return any new data +``` + +```js +POST '/questions' +- Sends a post request in order to search for a specific question by search term +- Request Body: +{ + 'searchTerm': 'this is the term the user is looking for' +} +- Returns: any array of questions, a number of totalQuestions that met the search term and the current category string +{ + 'questions': [ + { + 'id': 1, + 'question': 'This is a question', + 'answer': 'This is an answer', + 'difficulty': 5, + 'category': 5 + }, + ], + 'totalQuestions': 100, + 'currentCategory': 'Entertainment' +} +``` diff --git a/projects/02_trivia_api/starter/frontend/src/components/FormView.js b/projects/02_trivia_api/starter/frontend/src/components/FormView.js index 93ceffb566e..2731dbfa978 100755 --- a/projects/02_trivia_api/starter/frontend/src/components/FormView.js +++ b/projects/02_trivia_api/starter/frontend/src/components/FormView.js @@ -5,7 +5,7 @@ import '../stylesheets/FormView.css'; class FormView extends Component { constructor(props){ - super(); + super(props); this.state = { question: "", answer: "", @@ -21,11 +21,9 @@ class FormView extends Component { type: "GET", success: (result) => { this.setState({ categories: result.categories }) - return; }, error: (error) => { alert('Unable to load categories. Please try your request again') - return; } }) } @@ -50,11 +48,9 @@ class FormView extends Component { crossDomain: true, success: (result) => { document.getElementById("add-question-form").reset(); - return; }, error: (error) => { alert('Unable to add question. Please try your request again') - return; } }) } diff --git a/projects/02_trivia_api/starter/frontend/src/components/Question.js b/projects/02_trivia_api/starter/frontend/src/components/Question.js index 7e0ee8af32e..04ea31d4805 100755 --- a/projects/02_trivia_api/starter/frontend/src/components/Question.js +++ b/projects/02_trivia_api/starter/frontend/src/components/Question.js @@ -2,8 +2,8 @@ import React, { Component } from 'react'; import '../stylesheets/Question.css'; class Question extends Component { - constructor(){ - super(); + constructor(props){ + super(props); this.state = { visibleAnswer: false } @@ -19,7 +19,7 @@ class Question extends Component {
{question}
- +
Difficulty: {difficulty}
this.props.questionAction('DELETE')}/> diff --git a/projects/02_trivia_api/starter/frontend/src/components/QuestionView.js b/projects/02_trivia_api/starter/frontend/src/components/QuestionView.js index d246a692929..1999a3ffabe 100755 --- a/projects/02_trivia_api/starter/frontend/src/components/QuestionView.js +++ b/projects/02_trivia_api/starter/frontend/src/components/QuestionView.js @@ -6,8 +6,8 @@ import Search from './Search'; import $ from 'jquery'; class QuestionView extends Component { - constructor(){ - super(); + constructor(props){ + super(props); this.state = { questions: [], page: 1, @@ -30,12 +30,11 @@ class QuestionView extends Component { questions: result.questions, totalQuestions: result.total_questions, categories: result.categories, - currentCategory: result.current_category }) - return; + currentCategory: result.current_category + }) }, error: (error) => { alert('Unable to load questions. Please try your request again') - return; } }) } @@ -66,12 +65,11 @@ class QuestionView extends Component { this.setState({ questions: result.questions, totalQuestions: result.total_questions, - currentCategory: result.current_category }) - return; + currentCategory: result.current_category + }) }, error: (error) => { alert('Unable to load questions. Please try your request again') - return; } }) } @@ -91,12 +89,11 @@ class QuestionView extends Component { this.setState({ questions: result.questions, totalQuestions: result.total_questions, - currentCategory: result.current_category }) - return; + currentCategory: result.current_category + }) }, error: (error) => { alert('Unable to load questions. Please try your request again') - return; } }) } @@ -112,7 +109,6 @@ class QuestionView extends Component { }, error: (error) => { alert('Unable to load questions. Please try your request again') - return; } }) } @@ -128,7 +124,7 @@ class QuestionView extends Component { {Object.keys(this.state.categories).map((id, ) => (
  • {this.getByCategory(id)}}> {this.state.categories[id]} - +
  • ))} @@ -141,7 +137,7 @@ class QuestionView extends Component { key={q.id} question={q.question} answer={q.answer} - category={this.state.categories[q.category]} + category={this.state.categories[q.category]} difficulty={q.difficulty} questionAction={this.questionAction(q.id)} /> diff --git a/projects/02_trivia_api/starter/frontend/src/components/QuizView.js b/projects/02_trivia_api/starter/frontend/src/components/QuizView.js index a65789ae375..4ef764b097e 100644 --- a/projects/02_trivia_api/starter/frontend/src/components/QuizView.js +++ b/projects/02_trivia_api/starter/frontend/src/components/QuizView.js @@ -3,14 +3,14 @@ import $ from 'jquery'; import '../stylesheets/QuizView.css'; -const questionsPerPlay = 5; +const questionsPerPlay = 5; class QuizView extends Component { constructor(props){ - super(); + super(props); this.state = { quizCategory: null, - previousQuestions: [], + previousQuestions: [], showAnswer: false, categories: {}, numCorrect: 0, @@ -26,11 +26,9 @@ class QuizView extends Component { type: "GET", success: (result) => { this.setState({ categories: result.categories }) - return; }, error: (error) => { alert('Unable to load categories. Please try your request again') - return; } }) } @@ -68,11 +66,9 @@ class QuizView extends Component { guess: '', forceEnd: result.question ? false : true }) - return; }, error: (error) => { alert('Unable to load question. Please try your request again') - return; } }) } @@ -80,7 +76,7 @@ class QuizView extends Component { submitGuess = (event) => { event.preventDefault(); const formatGuess = this.state.guess.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").toLowerCase() - let evaluate = this.evaluateAnswer() + const evaluate = this.evaluateAnswer() this.setState({ numCorrect: !evaluate ? this.state.numCorrect : this.state.numCorrect + 1, showAnswer: true, @@ -90,7 +86,7 @@ class QuizView extends Component { restartGame = () => { this.setState({ quizCategory: null, - previousQuestions: [], + previousQuestions: [], showAnswer: false, numCorrect: 0, currentQuestion: {}, @@ -133,12 +129,12 @@ class QuizView extends Component { evaluateAnswer = () => { const formatGuess = this.state.guess.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").toLowerCase() const answerArray = this.state.currentQuestion.answer.toLowerCase().split(' '); - return answerArray.includes(formatGuess) + return answerArray.every(el => formatGuess.includes(el)); } renderCorrectAnswer(){ const formatGuess = this.state.guess.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").toLowerCase() - let evaluate = this.evaluateAnswer() + const evaluate = this.evaluateAnswer() return(
    {this.state.currentQuestion.question}
    @@ -152,7 +148,7 @@ class QuizView extends Component { renderPlay(){ return this.state.previousQuestions.length === questionsPerPlay || this.state.forceEnd ? this.renderFinalScore() - : this.state.showAnswer + : this.state.showAnswer ? this.renderCorrectAnswer() : (
    diff --git a/projects/03_coffee_shop_full_stack/starter_code/README.md b/projects/03_coffee_shop_full_stack/starter_code/README.md index 291bb2c7ec9..ee6bd66fd17 100755 --- a/projects/03_coffee_shop_full_stack/starter_code/README.md +++ b/projects/03_coffee_shop_full_stack/starter_code/README.md @@ -6,10 +6,10 @@ Udacity has decided to open a new digitally enabled cafe for students to order d You have been called on to demonstrate your newly learned skills to create a full stack drink menu application. The application must: -1) Display graphics representing the ratios of ingredients in each drink. -2) Allow public users to view drink names and graphics. -3) Allow the shop baristas to see the recipe information. -4) Allow the shop managers to create new drinks and edit existing drinks. +1. Display graphics representing the ratios of ingredients in each drink. +2. Allow public users to view drink names and graphics. +3. Allow the shop baristas to see the recipe information. +4. Allow the shop managers to create new drinks and edit existing drinks. ## Tasks @@ -20,7 +20,7 @@ There are `@TODO` comments throughout the project. We recommend tackling the sec ## About the Stack -We started the full stack application for you. It is desiged with some key functional areas: +We started the full stack application for you. It is designed with some key functional areas: ### Backend @@ -30,6 +30,6 @@ The `./backend` directory contains a partially completed Flask server with a pre ### Frontend -The `./frontend` directory contains a complete Ionic frontend to consume the data from the Flask server. You will only need to update the environment variables found within (./frontend/src/environment/environment.ts) to reflect the Auth0 configuration details set up for the backend app. +The `./frontend` directory contains a complete Ionic frontend to consume the data from the Flask server. You will only need to update the environment variables found within (./frontend/src/environment/environment.ts) to reflect the Auth0 configuration details set up for the backend app. [View the README.md within ./frontend for more details.](./frontend/README.md) diff --git a/projects/03_coffee_shop_full_stack/starter_code/backend/README.md b/projects/03_coffee_shop_full_stack/starter_code/backend/README.md index 400ba241d8a..f702063eb9a 100755 --- a/projects/03_coffee_shop_full_stack/starter_code/backend/README.md +++ b/projects/03_coffee_shop_full_stack/starter_code/backend/README.md @@ -8,9 +8,9 @@ Follow instructions to install the latest version of python for your platform in the [python docs](https://docs.python.org/3/using/unix.html#getting-and-installing-the-latest-version-of-python) -#### Virtual Enviornment +#### Virtual Environment -We recommend working within a virtual environment whenever using Python for projects. This keeps your dependencies for each project separate and organaized. Instructions for setting up a virual enviornment for your platform can be found in the [python docs](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/) +We recommend working within a virtual environment whenever using Python for projects. This keeps your dependencies for each project separate and organized. Instructions for setting up a virtual environment for your platform can be found in the [python docs](https://packaging.python.org/guides/installing-using-pip-and-virtual-environments/) #### PIP Dependencies @@ -24,7 +24,7 @@ This will install all of the required packages we selected within the `requireme ##### Key Dependencies -- [Flask](http://flask.pocoo.org/) is a lightweight backend microservices framework. Flask is required to handle requests and responses. +- [Flask](http://flask.pocoo.org/) is a lightweight backend microservices framework. Flask is required to handle requests and responses. - [SQLAlchemy](https://www.sqlalchemy.org/) and [Flask-SQLAlchemy](https://flask-sqlalchemy.palletsprojects.com/en/2.x/) are libraries to handle the lightweight sqlite database. Since we want you to focus on auth, we handle the heavy lift for you in `./src/database/models.py`. We recommend skimming this code first so you know how to interface with the Drink model. @@ -56,26 +56,27 @@ The `--reload` flag will detect file changes and restart the server automaticall 2. Select a unique tenant domain 3. Create a new, single page web application 4. Create a new API - - in API Settings: - - Enable RBAC - - Enable Add Permissions in the Access Token + - in API Settings: + - Enable RBAC + - Enable Add Permissions in the Access Token 5. Create new API permissions: - - `get:drinks-detail` - - `post:drinks` - - `patch:drinks` - - `delete:drinks` + - `get:drinks` + - `get:drinks-detail` + - `post:drinks` + - `patch:drinks` + - `delete:drinks` 6. Create new roles for: - - Barista - - can `get:drinks-detail` - - Manager - - can perform all actions -7. Test your endpoints with [Postman](https://getpostman.com). - - Register 2 users - assign the Barista role to one and Manager role to the other. - - Sign into each account and make note of the JWT. - - Import the postman collection `./starter_code/backend/udacity-fsnd-udaspicelatte.postman_collection.json` - - Right-clicking the collection folder for barista and manager, navigate to the authorization tab, and including the JWT in the token field (you should have noted these JWTs). - - Run the collection and correct any errors. - - Export the collection overwriting the one we've included so that we have your proper JWTs during review! + - Barista + - can `get:drinks-detail` + - Manager + - can perform all actions +7. Test your endpoints with [Postman](https://getpostman.com). + - Register 2 users - assign the Barista role to one and Manager role to the other. + - Sign into each account and make note of the JWT. + - Import the postman collection `./starter_code/backend/udacity-fsnd-udaspicelatte.postman_collection.json` + - Right-clicking the collection folder for barista and manager, navigate to the authorization tab, and including the JWT in the token field (you should have noted these JWTs). + - Run the collection and correct any errors. + - Export the collection overwriting the one we've included so that we have your proper JWTs during review! ### Implement The Server diff --git a/projects/03_coffee_shop_full_stack/starter_code/backend/requirements.txt b/projects/03_coffee_shop_full_stack/starter_code/backend/requirements.txt index ea86d1996b1..590fb1ec6d3 100755 --- a/projects/03_coffee_shop_full_stack/starter_code/backend/requirements.txt +++ b/projects/03_coffee_shop_full_stack/starter_code/backend/requirements.txt @@ -2,7 +2,7 @@ astroid==2.2.5 Click==7.0 ecdsa==0.13.2 Flask==1.0.2 -Flask-SQLAlchemy==2.4.0 +Flask-SQLAlchemy==2.5.0 future==0.17.1 isort==4.3.18 itsdangerous==1.1.0 @@ -14,8 +14,7 @@ pycryptodome==3.3.1 pylint==2.3.1 python-jose-cryptodome==1.3.2 six==1.12.0 -SQLAlchemy==1.3.3 -typed-ast==1.3.5 -Werkzeug==0.15.2 +typed-ast==1.4.2 +Werkzeug==0.15.6 wrapt==1.11.1 -Flask-Cors==3.0.8 \ No newline at end of file +Flask-Cors==3.0.8 diff --git a/projects/03_coffee_shop_full_stack/starter_code/backend/src/api.py b/projects/03_coffee_shop_full_stack/starter_code/backend/src/api.py index 40535c46974..83203875322 100755 --- a/projects/03_coffee_shop_full_stack/starter_code/backend/src/api.py +++ b/projects/03_coffee_shop_full_stack/starter_code/backend/src/api.py @@ -15,10 +15,11 @@ @TODO uncomment the following line to initialize the datbase !! NOTE THIS WILL DROP ALL RECORDS AND START YOUR DB FROM SCRATCH !! NOTE THIS MUST BE UNCOMMENTED ON FIRST RUN +!! Running this function will add one ''' # db_drop_and_create_all() -## ROUTES +# ROUTES ''' @TODO implement endpoint GET /drinks @@ -75,23 +76,26 @@ ''' -## Error Handling +# Error Handling ''' Example error handling for unprocessable entity ''' + + @app.errorhandler(422) def unprocessable(error): return jsonify({ - "success": False, - "error": 422, - "message": "unprocessable" - }), 422 + "success": False, + "error": 422, + "message": "unprocessable" + }), 422 + ''' @TODO implement error handlers using the @app.errorhandler(error) decorator each error handler should return (with approprate messages): jsonify({ - "success": False, + "success": False, "error": 404, "message": "resource not found" }), 404 @@ -100,11 +104,15 @@ def unprocessable(error): ''' @TODO implement error handler for 404 - error handler should conform to general task above + error handler should conform to general task above ''' ''' @TODO implement error handler for AuthError - error handler should conform to general task above + error handler should conform to general task above ''' + +if __name__ == "__main__": + app.debug = True + app.run() diff --git a/projects/03_coffee_shop_full_stack/starter_code/backend/src/database/models.py b/projects/03_coffee_shop_full_stack/starter_code/backend/src/database/models.py index 295bda96767..89bf2e3c5aa 100755 --- a/projects/03_coffee_shop_full_stack/starter_code/backend/src/database/models.py +++ b/projects/03_coffee_shop_full_stack/starter_code/backend/src/database/models.py @@ -13,26 +13,42 @@ setup_db(app) binds a flask application and a SQLAlchemy service ''' + + def setup_db(app): app.config["SQLALCHEMY_DATABASE_URI"] = database_path app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False db.app = app db.init_app(app) + ''' db_drop_and_create_all() drops the database tables and starts fresh can be used to initialize a clean database !!NOTE you can change the database_filename variable to have multiple verisons of a database ''' + + def db_drop_and_create_all(): db.drop_all() db.create_all() + # add one demo row which is helping in POSTMAN test + drink = Drink( + title='water', + recipe='[{"name": "water", "color": "blue", "parts": 1}]' + ) + + + drink.insert() +# ROUTES ''' Drink a persistent drink entity, extends the base SQLAlchemy Model ''' + + class Drink(db.Model): # Autoincrementing, unique primary key id = Column(Integer().with_variant(Integer, "sqlite"), primary_key=True) @@ -40,12 +56,13 @@ class Drink(db.Model): title = Column(String(80), unique=True) # the ingredients blob - this stores a lazy json blob # the required datatype is [{'color': string, 'name':string, 'parts':number}] - recipe = Column(String(180), nullable=False) + recipe = Column(String(180), nullable=False) ''' short() short form representation of the Drink model ''' + def short(self): print(json.loads(self.recipe)) short_recipe = [{'color': r['color'], 'parts': r['parts']} for r in json.loads(self.recipe)] @@ -59,6 +76,7 @@ def short(self): long() long form representation of the Drink model ''' + def long(self): return { 'id': self.id, @@ -75,6 +93,7 @@ def long(self): drink = Drink(title=req_title, recipe=req_recipe) drink.insert() ''' + def insert(self): db.session.add(self) db.session.commit() @@ -87,6 +106,7 @@ def insert(self): drink = Drink(title=req_title, recipe=req_recipe) drink.delete() ''' + def delete(self): db.session.delete(self) db.session.commit() @@ -100,8 +120,9 @@ def delete(self): drink.title = 'Black Coffee' drink.update() ''' + def update(self): db.session.commit() def __repr__(self): - return json.dumps(self.short()) \ No newline at end of file + return json.dumps(self.short()) diff --git a/projects/03_coffee_shop_full_stack/starter_code/backend/udacity-fsnd-udaspicelatte.postman_collection.json b/projects/03_coffee_shop_full_stack/starter_code/backend/udacity-fsnd-udaspicelatte.postman_collection.json index 7501665426c..d6093f6d998 100755 --- a/projects/03_coffee_shop_full_stack/starter_code/backend/udacity-fsnd-udaspicelatte.postman_collection.json +++ b/projects/03_coffee_shop_full_stack/starter_code/backend/udacity-fsnd-udaspicelatte.postman_collection.json @@ -52,7 +52,7 @@ "script": { "id": "65a195fa-a734-44b7-a7e0-c629b32d1fbb", "exec": [ - "pm.test(\"Status code is 401\", function () {", + "pm.test(\"Status code is 401 since no credentials are present\", function () {", " pm.response.to.have.status(401);", "});" ], @@ -83,7 +83,7 @@ "script": { "id": "5050a5b9-2e15-474e-9981-0e61ec8d2ff1", "exec": [ - "pm.test(\"Status code is 401\", function () {", + "pm.test(\"Status code is 401 since no credentials are present\", function () {", " pm.response.to.have.status(401);", "});" ], @@ -114,7 +114,7 @@ "script": { "id": "ec1488aa-b4d7-468a-89f5-03484009e69c", "exec": [ - "pm.test(\"Status code is 401\", function () {", + "pm.test(\"Status code is 401 since no credentials are present\", function () {", " pm.response.to.have.status(401);", "});" ], @@ -146,7 +146,7 @@ "script": { "id": "0aea66ad-0a6e-4533-b192-a8b0af746c78", "exec": [ - "pm.test(\"Status code is 401\", function () {", + "pm.test(\"Status code is 401 since no credentials are present\", function () {", " pm.response.to.have.status(401);", "});" ], @@ -250,8 +250,8 @@ "script": { "id": "5050a5b9-2e15-474e-9981-0e61ec8d2ff1", "exec": [ - "pm.test(\"Status code is 401\", function () {", - " pm.response.to.have.status(401);", + "pm.test(\"Status code is 403 since credentials are valid, but permission is not present\", function () {", + " pm.response.to.have.status(403);", "});" ], "type": "text/javascript" @@ -291,8 +291,8 @@ "script": { "id": "ec1488aa-b4d7-468a-89f5-03484009e69c", "exec": [ - "pm.test(\"Status code is 401\", function () {", - " pm.response.to.have.status(401);", + "pm.test(\"Status code is 403 since credentials are valid, but permission is not present\", function () {", + " pm.response.to.have.status(403);", "});" ], "type": "text/javascript" @@ -323,8 +323,8 @@ "script": { "id": "0aea66ad-0a6e-4533-b192-a8b0af746c78", "exec": [ - "pm.test(\"Status code is 401\", function () {", - " pm.response.to.have.status(401);", + "pm.test(\"Status code is 403 since credentials are valid, but permission is not present\", function () {", + " pm.response.to.have.status(403);", "});" ], "type": "text/javascript" diff --git a/projects/03_coffee_shop_full_stack/starter_code/frontend/README.md b/projects/03_coffee_shop_full_stack/starter_code/frontend/README.md index fb21f8d3441..ad20d49cf81 100755 --- a/projects/03_coffee_shop_full_stack/starter_code/frontend/README.md +++ b/projects/03_coffee_shop_full_stack/starter_code/frontend/README.md @@ -12,7 +12,7 @@ This project depends on Nodejs and Node Package Manager (NPM). Before continuing #### Installing Ionic Cli -The Ionic Command Line Interface is required to serve and build the frontend. Instructions for installing the CLI is in the [Ionic Framework Docs](https://ionicframework.com/docs/installation/cli). +The Ionic Command Line Interface is required to serve and build the frontend. Instructions for installing the CLI is in the [Ionic Framework Docs](https://ionicframework.com/docs/installation/cli). #### Installing project dependencies @@ -22,11 +22,11 @@ This project uses NPM to manage software dependencies. NPM Relies on the package npm install ``` ->_tip_: **npm i** is shorthand for **npm install** +> _tip_: **npm i** is shorthand for **npm install** ## Required Tasks -### Configure Enviornment Variables +### Configure Environment Variables Ionic uses a configuration file to manage environment variables. These variables ship with the transpiled software and should not include secrets. @@ -40,8 +40,8 @@ Ionic ships with a useful development server which detects changes and transpile ionic serve ``` ->_tip_: Do not use **ionic serve** in production. Instead, build Ionic into a build artifact for your desired platforms. -[Checkout the Ionic docs to learn more](https://ionicframework.com/docs/cli/commands/build) +> _tip_: Do not use **ionic serve** in production. Instead, build Ionic into a build artifact for your desired platforms. +> [Checkout the Ionic docs to learn more](https://ionicframework.com/docs/cli/commands/build) ## Key Software Design Relevant to Our Coursework @@ -49,8 +49,8 @@ The frontend framework is a bit beefy; here are the two areas to focus your stud ### Authentication -The authentication system used for this project is Auth0. `./src/services/auth.service.ts` contains the logic to direct a user to the Auth0 login page, managing the JWT token upon successful callback, and handle setting and retrieving the token from the local store. This token is then consumed by our DrinkService (`./src/services/auth.service.ts`) and passed as an Authorization header when making requests to our backend. +The authentication system used for this project is Auth0. `./src/app/services/auth.service.ts` contains the logic to direct a user to the Auth0 login page, managing the JWT token upon successful callback, and handle setting and retrieving the token from the local store. This token is then consumed by our DrinkService (`./src/app/services/drinks.service.ts`) and passed as an Authorization header when making requests to our backend. ### Authorization -The Auth0 JWT includes claims for permissions based on the user's role within the Auth0 system. This project makes use of these claims using the `auth.can(permission)` method which checks if particular permissions exist within the JWT permissions claim of the currently logged in user. This method is defined in `./src/services/auth.service.ts` and is then used to enable and disable buttons in `./src/pages/drink-menu/drink-form/drink-form.html`. \ No newline at end of file +The Auth0 JWT includes claims for permissions based on the user's role within the Auth0 system. This project makes use of these claims using the `auth.can(permission)` method which checks if particular permissions exist within the JWT permissions claim of the currently logged in user. This method is defined in `./src/app/services/auth.service.ts` and is then used to enable and disable buttons in `./src/app/pages/drink-menu/drink-form/drink-form.html`. diff --git a/projects/capstone/heroku_sample/starter/Procfile b/projects/capstone/heroku_sample/starter/Procfile new file mode 100644 index 00000000000..8001d1a50b9 --- /dev/null +++ b/projects/capstone/heroku_sample/starter/Procfile @@ -0,0 +1 @@ +web: gunicorn app:app \ No newline at end of file diff --git a/projects/capstone/heroku_sample/starter/app.py b/projects/capstone/heroku_sample/starter/app.py index 11961637841..82d608d7b67 100644 --- a/projects/capstone/heroku_sample/starter/app.py +++ b/projects/capstone/heroku_sample/starter/app.py @@ -1,6 +1,7 @@ import os from flask import Flask from models import setup_db +from flask_cors import CORS def create_app(test_config=None): @@ -12,7 +13,8 @@ def create_app(test_config=None): def get_greeting(): excited = os.environ['EXCITED'] greeting = "Hello" - if excited == 'true': greeting = greeting + "!!!!!" + if excited == 'true': + greeting = greeting + "!!!!! You are doing great in this Udacity project." return greeting @app.route('/coolkids') @@ -24,4 +26,4 @@ def be_cool(): app = create_app() if __name__ == '__main__': - app.run() \ No newline at end of file + app.run() diff --git a/projects/capstone/heroku_sample/starter/manage.py b/projects/capstone/heroku_sample/starter/manage.py new file mode 100644 index 00000000000..20547eb7745 --- /dev/null +++ b/projects/capstone/heroku_sample/starter/manage.py @@ -0,0 +1,14 @@ +from flask_script import Manager +from flask_migrate import Migrate, MigrateCommand + +from app import app +from models import db + +migrate = Migrate(app, db) +manager = Manager(app) + +manager.add_command('db', MigrateCommand) + + +if __name__ == '__main__': + manager.run() \ No newline at end of file diff --git a/projects/capstone/heroku_sample/starter/models.py b/projects/capstone/heroku_sample/starter/models.py index a4b853d4a11..ef55fdbf7cf 100644 --- a/projects/capstone/heroku_sample/starter/models.py +++ b/projects/capstone/heroku_sample/starter/models.py @@ -1,8 +1,11 @@ +import os from sqlalchemy import Column, String, create_engine from flask_sqlalchemy import SQLAlchemy import json database_path = os.environ['DATABASE_URL'] +if database_path.startswith("postgres://"): + database_path = database_path.replace("postgres://", "postgresql://", 1) db = SQLAlchemy() @@ -25,7 +28,7 @@ def setup_db(app, database_path=database_path): class Person(db.Model): __tablename__ = 'People' - id = Column(Integer, primary_key=True) + id = Column(db.Integer, primary_key=True) name = Column(String) catchphrase = Column(String) @@ -37,4 +40,4 @@ def format(self): return { 'id': self.id, 'name': self.name, - 'catchphrase': self.catchphrase} \ No newline at end of file + 'catchphrase': self.catchphrase} diff --git a/projects/capstone/heroku_sample/starter/requirements.txt b/projects/capstone/heroku_sample/starter/requirements.txt new file mode 100644 index 00000000000..da880dbe032 --- /dev/null +++ b/projects/capstone/heroku_sample/starter/requirements.txt @@ -0,0 +1,19 @@ +alembic==1.6.5 +click==8.0.1 +Flask==1.1.2 +Flask-Cors==3.0.10 +Flask-Migrate==2.7.0 +Flask-Script==2.0.6 +Flask-SQLAlchemy==2.5.1 +greenlet==1.1.0 +gunicorn==20.1.0 +itsdangerous==2.0.1 +Jinja2==3.0.1 +Mako==1.1.4 +MarkupSafe==2.0.1 +psycopg2-binary==2.9.1 +python-dateutil==2.8.1 +python-editor==1.0.4 +six==1.16.0 +SQLAlchemy==1.4.18 +Werkzeug==2.0.1 \ No newline at end of file diff --git a/projects/capstone/heroku_sample/starter/runtime.txt b/projects/capstone/heroku_sample/starter/runtime.txt new file mode 100644 index 00000000000..946d2ad8e2e --- /dev/null +++ b/projects/capstone/heroku_sample/starter/runtime.txt @@ -0,0 +1 @@ +python-3.8.12 \ No newline at end of file diff --git a/projects/capstone/heroku_sample/starter/setup.sh b/projects/capstone/heroku_sample/starter/setup.sh new file mode 100644 index 00000000000..05520947677 --- /dev/null +++ b/projects/capstone/heroku_sample/starter/setup.sh @@ -0,0 +1,4 @@ +#!/bin/bash +export DATABASE_URL="postgresql://postgres@localhost:5432/postgres" +export EXCITED="true" +echo "setup.sh script executed successfully!" \ No newline at end of file