Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ea6da11
[starts #161737782] build model and view for parcels POST api
ipaullly Nov 5, 2018
26d0031
[starts #161748253] Create GET delivery order list
ipaullly Nov 6, 2018
67006a9
[starts #161766269]Build API to retrieve single order
ipaullly Nov 6, 2018
37f0d14
[finishes #161766269] Add class and methods docstrings
ipaullly Nov 6, 2018
534a2b9
[starts #161773293] write passing test for POST new parcel order
ipaullly Nov 7, 2018
66c2c04
[starts #161773596] write test for GET list of delivery orders
ipaullly Nov 7, 2018
101cd2c
[finishes #161773596] add doctsrtings to test class
ipaullly Nov 7, 2018
7f1fd34
[starts #161774149] restructure v1 test folder and write test for sin…
ipaullly Nov 7, 2018
49471e2
[starts #161777597] Add status attribute and update status method
ipaullly Nov 7, 2018
7c66fd9
Create PUT method to cancel order by id
ipaullly Nov 7, 2018
d011417
[finishes #161777597] write test for PUT cancel order endpoint
ipaullly Nov 7, 2018
e22c523
[starts #161796710] Create authentication blueprint in project directory
ipaullly Nov 7, 2018
1abc184
create user registration model and registration api
ipaullly Nov 7, 2018
5520d68
[finishes #161796710] add a test for registration endpoint
ipaullly Nov 8, 2018
0d6055e
write test for registration endpoint and additional logic in its view
ipaullly Nov 8, 2018
0a4cd75
[starts #161820120] Add methods to decode/encode tokens and instance …
ipaullly Nov 8, 2018
dcf6e3e
Add enpoint to login and generate token
ipaullly Nov 8, 2018
9e33084
[finishes #161820120] write minimal tests for sign in endpoint
ipaullly Nov 8, 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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
*__pycache__
venv
.vscode
*.pytest_cache
instance

4 changes: 4 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
from flask import Flask, Blueprint
from instance.config import DevConfig
from .api.v1 import version1
from .auth.v1 import auth

def create_app():
app = Flask(__name__)
app.config.from_object(DevConfig)
app.register_blueprint(version1)
app.register_blueprint(auth)

return app
6 changes: 5 additions & 1 deletion app/api/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from flask import Blueprint
from flask_restful import Api

from .views import ParcelList, IndividualParcel, CancelParcel

version1 = Blueprint('v1', __name__, url_prefix="/api/v1")

api = Api(version1)

api.add_resource(ParcelList, '/parcels')
api.add_resource(IndividualParcel, '/parcels/<int:id>')
api.add_resource(CancelParcel, '/parcels/<int:id>/cancel')
46 changes: 46 additions & 0 deletions app/api/v1/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

parcels = []

class Parcels:
"""
Class with CRUD functionalities on the Parcels resource
"""
def __init__(self):
self.db = parcels
self.parcel_status = 'pending'

def create_order(self, item, pickup, dest, pricing):
"""
instance method to generate new entry into delivery orders list
"""
payload = {
"id" : len(self.db) + 1,
"itemName" : item,
"pickupLocation" : pickup,
"destination" : dest,
"pricing" : pricing,
"status" : self.parcel_status
}
self.db.append(payload)

def order_list(self):
"""
retrieves entire list of delivery orders
"""
return self.db

def retrieve_single_order(self, parcelID):
"""
retrieve a single order by id
"""
order_by_id = [parc for parc in self.db if parc['id'] == parcelID][0]
return order_by_id

def cancel_order(self, ParcelID):
"""
update parcel status to cancel
"""
parcel_to_cancel = [parc for parc in self.db if parc['id'] == ParcelID]
parcel_to_cancel[0]['status'] = 'cancelled'
return parcel_to_cancel

56 changes: 50 additions & 6 deletions app/api/v1/views.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,63 @@
from flask_restful import Resource
from flask import make_response, jsonify, request
from .models import Parcels

order = Parcels()

class ParcelList(Resource):

"""
class for Create order and retrieve list of orders API endpoints
"""
def post(self):
pass
"""
post method to add new order to list of orders
"""
data = request.get_json()
item = data['item']
pickup = data['pickup']
dest = data['dest']
pricing = data['pricing']

order.create_order(item, pickup, dest, pricing)
return make_response(jsonify({
"message" : "delivery order created successfully"
}), 201)

def get(self):
pass
"""
get method to retrieve list of all orders
"""
resp = order.order_list()
return make_response(jsonify({
"message" : "ok",
"Delivery Orders" : resp
}), 200)

class IndividualParcel(Resource):

"""
class for API endpoints for retrieving single order and cancelling particular order
"""
def get(self, id):
pass
"""
get method to retrieve order by id
"""
single = order.retrieve_single_order(id)
return make_response(jsonify({
"message" : "Ok",
"order" : single
}), 200)

class CancelParcel(Resource):
"""
class for endpoint to cancel parcel order
"""
def put(self, id):
pass
"""
PUT request to update parcel status to 'cancelled'
"""
cancel_parcel = order.cancel_order(id)
return make_response(jsonify({
"message" : "order is cancelled",
"cancelled order" : cancel_parcel
}), 201)

Empty file added app/auth/__init__.py
Empty file.
10 changes: 10 additions & 0 deletions app/auth/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from flask import Blueprint
from flask_restful import Api
from .views import Registration, SignIn

auth = Blueprint('auth', __name__, url_prefix="/auth/v1")

api = Api(auth)

api.add_resource(Registration, '/register')
api.add_resource(SignIn, '/login')
111 changes: 111 additions & 0 deletions app/auth/v1/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
from datetime import datetime, timedelta
from flask import current_app
import jwt
from werkzeug.security import generate_password_hash, check_password_hash

class MockDb():
"""
class for a data structure database
"""
def __init__(self):
self.users = {}
self.orders = {}
self.user_no = 0
self.entry_no = 0
def drop(self):
self.__init__()

db = MockDb()

class Parent():
"""
user class will inherit this class
"""
def update(self, data):
# Validate the contents before passing to mock database
for key in data:
setattr(self, key, data[key])
setattr(self, 'last_updated', datetime.utcnow().isoformat())
return self.lookup()

class User(Parent):
"""
class to register user and generate tokens
"""
def __init__(self, email, password):
self.email = email
self.password = generate_password_hash(password)
self.id = None
self.created_at = datetime.utcnow().isoformat()
self.last_updated = datetime.utcnow().isoformat()

def add_user(self):
"""
method to save a user's registration details
"""
setattr(self, 'id', db.user_no + 1)
db.users.update({self.id: self})
db.user_no += 1
db.orders.update({self.id: {}})
return self.lookup()

def validate_password(self, password):
"""
method to validate user password
"""
if check_password_hash(self.password, password):
return True
return False

def lookup(self):
"""
method to jsonify object that represents user
"""
keys = ['email', 'id']
return {key: getattr(self, key) for key in keys}

def generate_token(self, userID):
"""
method that generates token during each login
"""
try:
payload = {
'exp' : datetime.utcnow()+timedelta(minutes=5),
'iat' : datetime.utcnow(),
'id' : userID
}
token = jwt.encode(
payload,
current_app.config.get('SECRET_KEY'),
algorithm='HS256'
)
return token
except Exception as err:
return str(err)

@staticmethod
def decode_token(token):
"""
method to decode the token generated during login
"""
try:
#attempt to decode token using SECRET_KEY variable
payload = jwt.decode(token, current_app.config.get('SECRET_KEY'))
return payload['id']
except jwt.ExpiredSignatureError:
# expired token returns an error string
return "Token expired. please login again to generate fresh token"
except jwt.InvalidTokenError:
#the token is not valid, throw error
return "Unworthy token. Please login to get fresh authorization"

@classmethod
def get_user_by_email(cls, email):
"""
method for getting a user by email
"""
for user_id in db.users:
user = db.users.get(user_id)
if user.email == email:
return user
return None
56 changes: 56 additions & 0 deletions app/auth/v1/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from flask_restful import Resource
from flask import make_response,jsonify, request
from .models import User

class Registration(Resource):
"""
class that handles registration of new user
"""
def post(self):
data = request.get_json()
email = data['email']
password = data['password']

if not User.get_user_by_email(email):
new_user = User(email=email, password=password)
new_user.add_user()

return make_response(jsonify({
'message' : 'you have successfully registered an account'
}), 201)
else:
response = {
'message': 'Account with provided email exists. please login'
}

return make_response((jsonify(response)), 202)

class SignIn(Resource):
"""
class that handles logging into user accounts and token generation
"""
def post(self):
data = request.get_json()
email = data['email']
password = data['password']
try:
user = User.get_user_by_email(email)
user_id = user.id
if user and user.validate_password(password):
auth_token = user.generate_token(user_id)
if auth_token:
response = {
'message' : 'Successfully logged in',
'authentication token' : auth_token.decode()
}
return make_response(jsonify(response), 200)
else:
response = {
'message' : 'User with email already exists, please login'
}
return make_response(jsonify(response), 401)
except Exception as err:
response = {
'message' : str(err)
}
return make_response(jsonify(response), 500)
Empty file added app/tests/__init__.py
Empty file.
Empty file added app/tests/v1/__init__.py
Empty file.
40 changes: 40 additions & 0 deletions app/tests/v1/test_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from ... import create_app
import unittest
import json

class LoginTestCase(unittest.TestCase):
"""
test class for the registration endpoint
"""
def setUp(self):
create_app().testing = True
self.app = create_app().test_client()
self.mock_data = {
'email' : 'test@chocoly.com',
'password' : 'balerion'
}
self.not_user_data = {
'email' : 'not_user@chocoly.com',
'password' : 'silmarillion'
}

def test_user_signin(self):
#test if a registered user can log in
res = self.app.post('/auth/v1/register', data=json.dumps(self.mock_data), content_type='application/json')
self.assertEqual(res.status_code, 201)
signin_res = self.app.post('/auth/v1/login', data=json.dumps(self.mock_data), content_type='application/json')
result = json.loads(signin_res.data)
self.assertEqual(result['message'], "Successfully logged in")
self.assertEqual(signin_res.status_code, 200)
self.assertTrue(result['authentication token'])

def test_non_registered_user(self):
#test that unregistered user cannot log in
res = self.app.post('/auth/v1/login', data=json.dumps(self.mock_data), content_type='application/json')
result = json.loads(res.data)
self.assertEqual(res.status_code, 500)


if __name__ == "__main__":
unittest.main()

Loading