Skip to content
Merged
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
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

1 change: 1 addition & 0 deletions app/auth/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ def post(self):
if access_token:
response = {
'message': 'Successful Login',
'status': 'success',
'access_token': access_token.decode()
}
return make_response(jsonify(response)), 200
Expand Down
31 changes: 24 additions & 7 deletions app/classes/categories.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ def post(self, current_user):
required: true
type: string
schema:
id: categories
id: categories create
properties:
category_name:
type: string
default: Breakfast


responses:
200:
schema:
Expand All @@ -43,6 +44,18 @@ def post(self, current_user):
category_name:
type: string
default: Breakfast
created_by:
type: integer
default: 2
date_created:
type: string
default: Wed 20 Dec
date_modified:
type: string
default: Wed 20 Dec
id:
type: integer
default: 1
400:
description: category name not valid
400:
Expand Down Expand Up @@ -287,7 +300,8 @@ def put(self, current_user, id):
category_name=category_name, created_by=current_user.id).first()

if category_details:
response = {'message': 'Category name exists'}
response = {'message': 'Category name exists',
'status': 'fail'}
return make_response(jsonify(response)), 400

category.category_name = category_name
Expand Down Expand Up @@ -337,13 +351,15 @@ def delete(self, current_user, id):
category = Categories.query.filter_by(
id=id, created_by=current_user.id).first()
if not category:
response = {'message': 'Category does not exist'}
response = {'message': 'Category does not exist',
'status': 'error'}
return make_response(jsonify(response)), 404
else:
category.delete_categories()
return {
"message": "successfully deleted category" .format(category.id)
}, 200
response = {'message': 'successfully deleted category',
'status': 'success',
'id': '{}'.format(category.id)}
return make_response(jsonify(response)), 200


class SearchCategory(MethodView):
Expand Down Expand Up @@ -433,7 +449,8 @@ def get(self, current_user):
results.append(category_object)
return make_response(jsonify(results)), 200
else:
response = {'message': 'No search item provided'}
response = {'message': 'No search item provided',
'status': 'error'}
return make_response(jsonify(response)), 200


Expand Down
31 changes: 20 additions & 11 deletions app/classes/recipes.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,14 @@ def post(self, current_user, id):
return make_response(jsonify(response)), 400

recipe = Recipes(recipe_name=recipe_name, recipe_ingredients=recipe_ingredients,
recipe_methods=recipe_methods, category_id=category_id)
recipe_methods=recipe_methods, category_id=category_id, created_by=current_user.id)

recipe_details = Recipes.query.filter_by(
category_id=category_id, recipe_name=recipe_name).first()
category_id=category_id, recipe_name=recipe_name, created_by=current_user.id).first()

if recipe_details:
response = {'message': 'Recipe name exists'}
response = {'message': 'Recipe name exists',
'status': 'fail'}
return make_response(jsonify(response)), 400
recipe.save()
response = {'id': recipe.id,
Expand All @@ -92,7 +93,8 @@ def post(self, current_user, id):
return response

except Exception:
response = {'message': 'Category does not exist'}
response = {'message': 'Category does not exist',
'status': 'error'}
return make_response(jsonify(response)), 404

def get(self, current_user, id):
Expand Down Expand Up @@ -151,7 +153,8 @@ def get(self, current_user, id):

results.append(recipe_obj)
if len(results) <= 0:
response = {'message': 'No recipe found '}
response = {'message': 'No recipe found ',
'status': 'error'}
response = make_response(jsonify(response)), 404
return response
response = jsonify(results)
Expand Down Expand Up @@ -198,7 +201,8 @@ def get(self, current_user, id, recipe_id):
"""
recipe = Recipes.query.filter_by(category_id=id, id=recipe_id).first()
if not recipe:
response = {'message': 'No recipe found'}
response = {'message': 'No recipe found',
'status': 'error'}
response = make_response(jsonify(response)), 404
return response
else:
Expand Down Expand Up @@ -255,14 +259,15 @@ def put(self, current_user, id, recipe_id):
try:
category_id = id
recipe = Recipes.query.filter_by(
category_id=category_id, id=recipe_id).first()
category_id=category_id, id=recipe_id, created_by=current_user.id).first()
recipe_name = str(request.data.get('recipe_name', ''))
recipe_ingredients = str(request.data.get('recipe_ingredients', ''))
recipe_methods = str(request.data.get('recipe_methods', ''))
recipe_validation(recipe_name,recipe_methods, recipe_ingredients)

if not recipe:
response = {'message': 'No recipe found'}
response = {'message': 'No recipe found',
'status': 'error'}
response = make_response(jsonify(response)), 404
return response
else:
Expand Down Expand Up @@ -314,13 +319,16 @@ def delete(self, current_user, id, recipe_id):
"""
recipe = Recipes.query.filter_by(category_id=id, id=recipe_id).first()
if not recipe:
response = {'message': 'No recipe found'}
response = {'message': 'No recipe found',
'status': 'error'}
response = make_response(jsonify(response)), 404
return response
else:
recipe.delete_recipes()
response = {
"message": "successfully deleted category".format(recipe.id)}
"message": "successfully deleted recipe",
'status': 'success',
"id": '{}' .format(recipe.id)}
response = make_response(jsonify(response)), 200
return response

Expand Down Expand Up @@ -396,7 +404,8 @@ def get(self, current_user, id):
response.status_code = 200
return response
else:
response = {'message': 'No search item provided'}
response = {'message': 'No search item provided',
'status': 'error'}
return make_response(jsonify(response)), 200


Expand Down
10 changes: 5 additions & 5 deletions app/helpers/auth_validators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Class to validate user input
"""
from marshmallow import Schema, fields, ValidationError
from marshmallow import ValidationError
from validate_email import validate_email
import re

Expand All @@ -14,17 +14,17 @@ def user_registration_validation(email, username, password, secret):
valid_email = validate_email(email)

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

if not email or not password or not username:
error = ValidationError('Kindly provide all details')
error = ValidationError('Email, password, username missing')
if not len(password) >= 6:
error = ValidationError('Password should be more than six characters')
if not secret:
error = ValidationError('Kindly provide a SECRET word')
if not username:
error = ValidationError('{} is not a valid username'.format(username))
error = ValidationError('{} Kindly provide a username ')
if not valid_email:
error = ValidationError('{} is not a valid email'.format(email))

Expand All @@ -49,7 +49,7 @@ def password_reset_validation(email, reset_password, secret):
"""
error = None
if reset_password:
reset_password = re.sub(r'\s+', ' ', reset_password).strip()
reset_password = re.sub(r'\s+', '', reset_password).strip()
reset_password = None if reset_password == " " else reset_password

if not reset_password:
Expand Down
1 change: 0 additions & 1 deletion app/helpers/category_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ def category_validation(category_name):

if error:
raise error

38 changes: 15 additions & 23 deletions app/helpers/recipe_validators.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,26 @@
from marshmallow import ValidationError
from marshmallow import ValidationError
import re


def recipe_validation(recipe_name, recipe_methods, recipe_ingredients):
def recipe_validation(recipe, *argv):
error = None
regex_pattern = "[a-zA-Z0-9-]+$"

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

if recipe_ingredients:
recipe_ingredients = re.sub(r'\s+', ' ', recipe_ingredients).strip()
recipe_ingredients = None if recipe_ingredients == " " else recipe_ingredients
if not re.search(regex_pattern, recipe):
error = ValidationError('recipe name cannot be empty or with invalid characters')

if recipe_methods:
recipe_methods = re.sub(r'\s+', ' ', recipe_methods).strip()
recipe_methods = None if recipe_methods == " " else recipe_methods
for arg in argv:
if arg:
arg = re.sub(r'\s+', '', arg).strip()
arg = None if arg == " " else arg

if not recipe_name:
error = ValidationError('Recipe name not provided')
if not arg:
error = ValidationError('Kindly provide ingredients and methods')

if not recipe_ingredients:
error = ValidationError('Recipe ingredients not provided')
if error:
raise error

if not recipe_methods:
error = ValidationError('Recipe preparation methods not provided')

if not re.search(regex_pattern, recipe_name):
error = ValidationError('Recipe name is not valid')

if error:
raise error
4 changes: 3 additions & 1 deletion app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,10 @@ class Recipes(db.Model):
date_modified = db.Column(
db.DateTime, default=db.func.current_timestamp(),
onupdate=db.func.current_timestamp())
created_by = db.Column(db.Integer, db.ForeignKey(User.id))
category_id = db.Column(db.Integer, db.ForeignKey(Categories.id))

def __init__(self, recipe_name, recipe_ingredients, recipe_methods, category_id):
def __init__(self, recipe_name, recipe_ingredients, recipe_methods, category_id, created_by):
"""
Constructor to initialize the class variables, category
name and the owner
Expand All @@ -165,6 +166,7 @@ def __init__(self, recipe_name, recipe_ingredients, recipe_methods, category_id)
self.recipe_ingredients = recipe_ingredients
self.recipe_methods = recipe_methods
self.category_id = category_id
self.created_by = created_by

def save(self):
"""
Expand Down
Loading