Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
541 changes: 0 additions & 541 deletions project.py

This file was deleted.

25 changes: 25 additions & 0 deletions project/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'''
Project 4 for Udacity Fullstack Nanodegree
Author: Aleksandr Zonis
'''

# Modules from Flask Library
from flask import Flask
import config

# Route modules
from categories.routes import categories
from products.routes import products
from jsonAPI.routes import jsonAPI
from auth.routes import auth

app = Flask(__name__)

# Configure the Application
app.config.from_object(config.DevelopmentConfig)

# Register Routes
app.register_blueprint(categories)
app.register_blueprint(products)
app.register_blueprint(jsonAPI)
app.register_blueprint(auth)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice and clean! 🔥

Empty file added project/auth/__init__.py
Empty file.
179 changes: 179 additions & 0 deletions project/auth/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Modules from Flask Library
from flask import Flask, render_template, request, flash, Blueprint
from flask import session as login_session
from flask import make_response, redirect, jsonify, url_for
# Access database
from project.models import Base, Category, Product, User
from project.db import session
# Services
from project.services.categories import CategoryService
from project.services.config import ConfigService
from project.services.user import UserService

import random
import string
import httplib2
import json
import requests

# Authentification modules
from oauth2client.client import flow_from_clientsecrets
from oauth2client.client import FlowExchangeError


auth = Blueprint('auth', __name__)


# Instantiate services
category_service = CategoryService()
config_service = ConfigService()
user_service = UserService()

# Get Client ID
CLIENT_ID = config_service.get_setting('client_id')

# Status Code Constansts
HTTP_STATUS_CODE_OK = 200
HTTP_STATUS_CODE_UNAUTHORIZED = 401
HTTP_STATUS_CODE_ERROR = 500

@auth.route('/login')
def showLogin():
"""App route function to display login page."""
state = ''.join(random.choice(string.ascii_uppercase + string.digits)
for x in range(32))
login_session['state'] = state
return render_template('login.html',
STATE=state,
categories=category_service.get_all_categories(),
CLIENT_ID=CLIENT_ID)


@auth.route('/googleConnect', methods=['POST'])
def googleConnect():
"""Connect via Google Account and fetch User info."""
# Validate state token
if request.args.get('state') != login_session['state']:
make_json_response('Invalid state parameter.',
HTTP_STATUS_CODE_UNAUTHORIZED)
# Obtain authorization code
code = request.data

try:
# Upgrade the authorization code into a credentials object
oauth_flow = flow_from_clientsecrets('client_secrets.json', scope='')
oauth_flow.redirect_uri = 'postmessage'
credentials = oauth_flow.step2_exchange(code)
except FlowExchangeError:
make_json_response('Invalid state parameter.',
HTTP_STATUS_CODE_UNAUTHORIZED)

# Check that the access token is valid.
access_token = credentials.access_token
url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={}'
.format(access_token))
h = requests.get(url=url)
result = json.loads(h.text)
# If there was an error in the access token info, abort.
if result.get('error') is not None:
make_json_response('error', HTTP_STATUS_CODE_ERROR)

# Verify that the access token is used for the intended user.
gplus_id = credentials.id_token['sub']
if result['user_id'] != gplus_id:
make_json_response('Invalid state parameter.',
HTTP_STATUS_CODE_UNAUTHORIZED)

# Verify that the access token is valid for this app.
if result['issued_to'] != CLIENT_ID:
make_json_response('Invalid state parameter.',
HTTP_STATUS_CODE_UNAUTHORIZED)

stored_access_token = login_session.get('access_token')
stored_gplus_id = login_session.get('gplus_id')
if stored_access_token is not None and gplus_id == stored_gplus_id:
make_json_response('Current user is already connected.',
HTTP_STATUS_CODE_OK)

# Store the access token in the session for later use.
login_session['access_token'] = credentials.access_token
login_session['gplus_id'] = gplus_id

# Get user info
userinfo_url = "https://www.googleapis.com/oauth2/v1/userinfo"
params = {'access_token': credentials.access_token, 'alt': 'json'}
answer = requests.get(userinfo_url, params=params)

data = answer.json()

login_session['username'] = data['email']
login_session['email'] = data['email']
# ADD PROVIDER TO LOGIN SESSION
login_session['provider'] = 'google'

# see if user exists, if it doesn't make a new one
user_id = getUserID(data["email"])
if not user_id:
user_id = createUser(login_session)
login_session['user_id'] = user_id
result = "User is authorized!"
flash("You are now logged in as {}".format(login_session['username']))
return result


@auth.route('/googleDisconnect')
def googleDisconnect():
"""Disconnect user"""
# Check if user is connected,
# only disconnect a connected user.
access_token = login_session.get('access_token')
if access_token is None:
response = make_response(
json.dumps('Current user not connected.'), 401)
response.headers['Content-Type'] = 'application/json'
flash("Current user not connected")
return redirect(url_for('showCategories'))
response = make_response(json.dumps('Successfully disconnected.'), 200)
response.headers['Content-Type'] = 'application/json'
# Delete all login_session info
del login_session['gplus_id']
del login_session['access_token']
del login_session['username']
del login_session['email']
del login_session['user_id']
del login_session['provider']
flash("You have successfully been logged out.")
return redirect(url_for('categories.showCategories'))


# User Helper Functions
def make_json_response(data, status):
"""Makes http response, accepts message and status code"""
response = make_response(data, status)
response.headers['Content-Type'] = 'application/json'
return response


def createUser(login_session):
"""Creates new login_session user, returns user.id."""
newUser = User(name=login_session['username'], email=login_session[
'email'])
session.add(newUser)
session.commit()
user = user_service.get_user_by_email(login_session['email'])
return user.id


def getUserInfo(user_id):
"""Returns User object"""
user = user_service.get_user_by_id(user_id)
return user


def getUserID(email):
"""Returns user.id if exists, otherwise returns none."""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love the comments!

try:
user = user_service.get_user_by_email(email)
return user.id
except:
return None
Empty file added project/categories/__init__.py
Empty file.
155 changes: 155 additions & 0 deletions project/categories/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Modules from Flask Library
from flask import Flask, render_template, request, flash, Blueprint
from flask import session as login_session
from flask import make_response, redirect, jsonify, url_for
# Access database
from project.models import Category, Product, User
from project.db import session
# Services
from project.services.categories import CategoryService
from project.services.auth import AuthService
from project.services.products import ProductService

import random
import string
import httplib2
import json
import requests


# Create Blueprint 'categories'
categories = Blueprint('categories', __name__)


# Instantiate services
category_service = CategoryService()
product_service = ProductService()
auth_service = AuthService()

DEFAULT_CATEGORY_PRODUCT_NUMBER = 8

@categories.route('/')
@categories.route('/categories/')
def showCategories():
"""App route function for main page to show all categories."""
return render_template('index.html',
categories=category_service.get_all_categories(),
isLogin=auth_service.is_user_authorized(),
latestProducts=(product_service
.get_latest_products(DEFAULT_CATEGORY_PRODUCT_NUMBER)))


@categories.route('/categories/<int:category_id>/')
def showCategoryProducts(category_id):
"""App route function to show Products of the selected Category."""
isLogin = auth_service.is_user_authorized()
# Pick category selected by user
category = category_service.get_category_by_id(category_id)
# Check if user is Creator of Category
isCreator = False
if isLogin:
isCreator = login_session['email'] == category.user.email
return render_template('category.html',
products=(product_service
.get_products_by_category_id(category_id)),
categories=category_service.get_all_categories(),
category=category,
isLogin=isLogin,
countProducts=(product_service
.products_count(category_id)),
isCreator=isCreator)


@categories.route('/addcategory/', methods=['GET', 'POST'])
def addCategory():
"""App route function to add new Category to the Category Table."""
if 'email' not in login_session:
return redirect('/login')
if request.method == 'POST':
if request.form['name'] == '':
flash("Please, enter Category Name")
return render_template('addcategory.html',
categories=(category_service
.get_all_categories()),
isLogin=auth_service.is_user_authorized())
newCategory = Category(name=request.form['name'],
user_id=login_session['user_id'])
session.add(newCategory)
session.commit()
flash('New Category {} Successfully Created'.format(newCategory.name))
return redirect(url_for('categories.showCategories'))
else:
return render_template('addcategory.html',
categories=(category_service
.get_all_categories()),
isLogin=auth_service.is_user_authorized())


@categories.route('/editcategory/<int:category_id>/',
methods=['GET', 'POST'])
def editCategory(category_id):
"""App route function to edit existing Category."""
# If user is not logged in, inform him about it and redirect
if 'email' not in login_session:
flash("You need to Log In if you want to edit")
return redirect('/categories')
# Get category that is selected to be edited
categoryToEdit = category_service.get_category_by_id(category_id)
# Check if user is Creator, if not inform that he cannot
# do changes
if not login_session['email'] == categoryToEdit.user.email:
flash("You need to be Creator of the category to be able to edit")
return redirect('/categories')
if request.method == 'POST':
if request.form['name']:
categoryToEdit.name = request.form['name']
else:
flash('Please, enter category name.')
return render_template('editcategory.html',
categories=(category_service
.get_all_categories()),
isLogin=auth_service.is_user_authorized(),
categoryToEdit=categoryToEdit)
session.add(categoryToEdit)
session.commit()
flash('You successfully \
updated category to {}'.format(categoryToEdit.name))
return redirect(url_for('categories.showCategoryProducts',
category_id=category_id))
else:
return render_template('editcategory.html',
categories=(category_service
.get_all_categories()),
isLogin=auth_service.is_user_authorized(),
categoryToEdit=categoryToEdit)


@categories.route('/deletecategory/<int:category_id>/', methods=['GET', 'POST'])
def deleteCategory(category_id):
"""App route function to delete existing Category"""
# If user is not logged in, inform him about it and redirect
if 'email' not in login_session:
flash("You need to Log In if you want to delete the category.")
return redirect('/categories')
# Get category that is selected to be edited
categoryToDelete = category_service.get_category_by_id(category_id)
# Check if user is Creator, if not inform that he cannot
# do changes
if not login_session['email'] == categoryToDelete.user.email:
flash("You need to be a Creator of the category\
to be able to delete it")
return redirect('/categories')
# Check for method and do appropriate. If 'POST' -> delete Category,
# if 'GET' -> render 'deletecategory.html'
if request.method == 'POST':
session.delete(categoryToDelete)
session.commit()
flash('You successfully deleted \
category "{}"'.format(categoryToDelete.name))
return redirect(url_for('categories.showCategories'))
else:
return render_template('deletecategory.html',
categories=(category_service
.get_all_categories()),
isLogin=auth_service.is_user_authorized(),
categoryToDelete=categoryToDelete)
10 changes: 10 additions & 0 deletions project/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class Config():
SECRET_KEY = 'super_secret_key'
DEBUG = False


class DevelopmentConfig(Config):
DEBUG = True

class ProductionConfig(Config):
DEBUG = False
20 changes: 20 additions & 0 deletions project/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from models import Base


class DBConnector():

engine = create_engine('sqlite:///furniturecatalog.db',
connect_args={'check_same_thread': False})
Base.metadata.bind = engine

def __init__(self):
self.DBSession = sessionmaker(bind=self.engine)

def get_session(self):
return self.DBSession()

# Connect to the database and create session
session = DBConnector().get_session()
Empty file added project/jsonAPI/__init__.py
Empty file.
Loading