Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f9c0981
[fx]separate logic from validations in auth
mwaz Jan 5, 2018
0c62f62
[fx] fix the auth tests
mwaz Jan 5, 2018
2d8be82
[fx] add category validatios to helper module
mwaz Jan 5, 2018
20c68a3
[fx] test code refactor and remove unused imports
mwaz Jan 6, 2018
86d118f
[fx] remove method repetiition
mwaz Jan 6, 2018
266936f
[fx] add login method check as a static method
mwaz Jan 6, 2018
db22f3e
[fx] separate validations and programming language
mwaz Jan 6, 2018
50d724d
[fx] fix failling recipe tests
mwaz Jan 6, 2018
bcd785b
[ft] add marshmallow package for validation
mwaz Jan 6, 2018
800aa5e
[fx] change validator names on import
mwaz Jan 6, 2018
4b4538f
[fx] changed fetch of username on login
mwaz Jan 6, 2018
284024b
[fx] changed location of decorators
mwaz Jan 6, 2018
a1f847f
[fx] replaced filter with filter_by on user fetch on login
mwaz Jan 6, 2018
abfc298
[fx] remodel API diaplay tables on README.md
mwaz Jan 6, 2018
8c66ec0
[fx] fix the response status
mwaz Jan 6, 2018
d685b83
[fx] add test methods to handle errors on bad request
mwaz Jan 7, 2018
1ed6a04
[fx] add method to capture errors on bad requests or invalid routes
mwaz Jan 7, 2018
3a063cf
[fx] add test methods to handle errors on bad requests and invalid pages
mwaz Jan 7, 2018
4a8787b
[fx] response delete message of recipes and categories
mwaz Jan 7, 2018
96c32b0
[fx] users should not be able to edit recipes of other users
mwaz Jan 8, 2018
866e8a8
[fx] add body in schema for category creation
mwaz Jan 8, 2018
25c35f5
[fx] add body in schema for category creation
mwaz Jan 8, 2018
859b713
[fx] validation errors on recipes and auth
mwaz Jan 8, 2018
6c1c6a9
[fx] fix failing asserts on recipe tests
mwaz Jan 8, 2018
17b0bb5
[fx] formating of recipe and category id after deletion
mwaz Jan 8, 2018
d206537
Merge pull request #12 from mwaz/fx-bug-fix-154108647
mwaz Jan 9, 2018
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ instance/__pycache__
tests/__pycache__
.vscode/settings.json
migrations/

.DS_Store
.cache/
.coverage
cover/
39 changes: 20 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,30 +76,31 @@ Interact with the API: -), send http requests using Postman

## User API Endpoints

URL Endpoint | HTTP requests | access | status |
---------------- | ----------------- | ------------- | ------------------
/ flask_api / v1 / auth / register | POST | Register a new user | publc
/ flask_api / v1 / auth / login | POST | Login and retrieve token | public
|URL Endpoint |HTTP requests |access |status|
---------------- | ----------------- | ------------- | ------------------
|/flask_api/v1/auth/register | POST | Register a new user | public|
|/flask_api/v1/auth/login | POST | Login and retrieve token | public|


## Categories API Endpoints

URL Endpoint | HTTP requests | access | status |
---------------- | ----------------- | ------------- | ------------------
/ flask_api / v1 / categories / | POST | Create a new recipe category | private
/ flask_api / v1 / categories / | GET | Retrieve all categories for user | private
/ flask_api / v1 / categories / search /?q= & limit & page | GET | Retrieve all categories for a given search | private
/ flask_api / v1 / categories / <category_id > / | GET | Retrieve a category by ID | private
/ flask_api / v1 / categories / <category_id > / | PUT | Update a category | private
/ flask_api / v1 / categories / <category_id > / | DELETE | Delete a category | private
URL Endpoint | HTTP requests | access | status |
---------------- | ----------------- | ------------- | ------------------
/flask_api/v1/categories/ | POST | Create a new recipe category | private
/flask_api/v1/categories/ | GET | Retrieve all categories for user | private
/flask_api/v1/categories/search/?q= & limit & page | GET | Retrieve all categories for a given search | private
/flask_api/v1/categories/<category_id>/ | GET | Retrieve a category by ID | private
/flask_api/v1/categories/<category_id>/ | PUT | Update a category | private
/flask_api/v1/categories/<category_id>/ | DELETE | Delete a category | private

## Recipes API Endpoints

URL Endpoint | HTTP requests | access | status |
---------------- | ----------------- | ------------- | ------------------
/ flask_api / v1 / categories / <category_id > /recipes / | GET | Retrive recipes in a given category | private
/ flask_api / v1 / categories / <category_id > /recipes / | POST | Create recipes in a category | private
/ flask_api / v1 / categories / <category_id > /recipes / search /?q= & limit & page | GET | Retrieve all recipes for a given search | private
/ flask_api / v1 / categories / <category_id > /recipes / <recipe_id > / | DELETE | Delete a recipe in a category | private
/ flask_api / v1 / categories / <category_id > /recipes / <recipe_id > / | PUT | update recipe details | private
URL Endpoint | HTTP requests | access | status |
---------------- | ----------------- | ------------- | ------------------
/flask_api/v1/categories/<category_id>/recipes/ | GET | Retrive recipes in a given category | private
/flask_api/v1/categories/<category_id>/recipes/ | POST | Create recipes in a category | private
/flask_api/v1/categories/<category_id>/recipes/search/?q=&limit&page | GET | Retrieve all recipes for a given search | private
/flask_api/v1/categories/<category_id>/recipes/<recipe_id>/| DELETE | Delete a recipe in a category | private
/flask_api/v1/categories/<category_id>/recipes/<recipe_id>/ | PUT | update recipe details | private

Run the APIs on postman to ensure they are fully functioning.
20 changes: 20 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from flask_api import FlaskAPI
from flask_sqlalchemy import SQLAlchemy
from flasgger import Swagger
from flask import make_response, jsonify, abort


# local import
Expand Down Expand Up @@ -70,4 +71,23 @@ def make_app(config_name):
view_func=user_password_reset_view, )
app.add_url_rule(base_url + '/auth/logout', view_func=user_logout_view)

@app.errorhandler(404)
def not_found(error):
"""handles error when users enters inappropriate endpoint
"""
response = {
'message': 'Page not found'
}
return make_response(jsonify(response)), 404

@app.errorhandler(405)
def method_not_allowed(error):
""" handles errors if users uses method that is not allowed in an endpoint
"""
response = {
'message': 'Method not allowed'
}
return make_response(jsonify(response)), 405

return app

81 changes: 19 additions & 62 deletions app/auth/authentication.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
"""Class to deal with user authenticatication
"""
from app.decorators import token_required
from app.helpers.decorators import token_required
from app.models import User, Sessions
import re
from flask import request, jsonify, make_response
from flask.views import MethodView
from app import db
from app.helpers.auth_validators import user_registration_validation, \
user_login_validation, password_reset_validation


class RegisterUser(MethodView):
Expand Down Expand Up @@ -46,62 +47,25 @@ def post(self):
400:
description: Bad Requests
"""
regex_email = r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z-.]+$)"
regex_username = "[a-zA-Z0-9- .]+$"

try:
user_details = User.query.filter_by(
email=request.data['email'].lower()).first()

if not user_details:
email = str(request.data.get('email', ''))
password = str(request.data.get('password', ''))
username = str(request.data.get('username', ''))
secret = str(request.data.get('secret_word', ''))
try:
email = str(request.data.get('email', ''))
password = str(request.data.get('password', ''))
username = str(request.data.get('username', ''))
secret = str(request.data.get('secret_word', ''))

if email:
email = re.sub(r'\s+', ' ', email).strip()
email = None if email == " " else email.lower()

if username:
username = re.sub(r'\s+', ' ', username).strip()
username = None if username == " " else username.title()

if secret:
secret = re.sub(r'\s+', ' ', secret).strip()
secret = None if secret == " " else secret

if not email or not password or not username:
response = {
'message': "Kindly Provide all required details"}
return make_response(jsonify(response)), 422
if not secret:
response = {
'message': "Kindly Provide a secret word"}
return make_response(jsonify(response)), 422
if not re.search(regex_email, email):
response = {'message': "Email pattern not valid"}
return make_response(jsonify(response)), 400

if not re.search(regex_username, username):
response = {
'message': "No special characters allowed on username"}
return make_response(jsonify(response)), 400

if len(password) < 6:
response = {'message': "Password must be at least six characters"}
return make_response(jsonify(response)), 400

try:
user = User(email=email, password=password,
username=username, secret_word=secret)
user.save()
response = {'message': "Successfully registered"}
except Exception as e:
response = {'message': "User Exists, Kindly Login"}
return make_response(jsonify(response)), 409
user_registration_validation(email, username, password, secret)

user = User(email=email, password=password,
username=username, secret_word=secret)
user.save()
response = {'message': "Successfully registered"}
return make_response(jsonify(response)), 201

except Exception as e:
response = {'message': str(e)}
return make_response(jsonify(response)), 400
Expand Down Expand Up @@ -151,17 +115,15 @@ def post(self):
email = request.data['email']
password = request.data['password']
user_details = User.query.filter_by(email=email).first()

if not email or not password:
response = {'message': "Kindly Provide email and password"}
return make_response(jsonify(response)), 422
user_login_validation(email, password)

if user_details and user_details.password_check(password):
access_token = user_details.user_token_generator(user_details.id)

if access_token:
response = {
'message': 'Successful Login',
'status': 'success',
'access_token': access_token.decode()
}
return make_response(jsonify(response)), 200
Expand Down Expand Up @@ -215,18 +177,13 @@ def put(self):
"""

try:
email = str(request.data.get('email', ''))
user_details = User.query.filter_by(
email=request.data['email']).first()
email=email).first()
reset_password = str(request.data.get('reset_password', ''))
secret_word = str(request.data.get('secret_word', ''))
password_reset_validation(email, reset_password, secret_word)

if reset_password:
reset_password = re.sub(r'\s+', ' ', reset_password).strip()
reset_password = None if reset_password == " " else reset_password

if not reset_password:
response = {"message": "No password provided"}
return make_response(jsonify(response)), 400
if user_details and user_details.secret_word_check(secret_word):
res_password = User.password_hash(reset_password)
user_details.password = res_password
Expand Down
Loading