diff --git a/.coverage b/.coverage index 31b9d6a..641e004 100644 --- a/.coverage +++ b/.coverage @@ -1 +1 @@ -!coverage.py: This is a private format, don't read it directly!{"lines":{"/home/kasule/Desktop/fast-food-fast-db/run.py":[],"/home/kasule/Desktop/fast-food-fast-db/app/models/users.py":[],"/home/kasule/Desktop/fast-food-fast-db/app/views/__init__.py":[1,2,4,5],"/home/kasule/Desktop/fast-food-fast-db/tests/test_user.py":[1],"/home/kasule/Desktop/fast-food-fast-db/tests/test_base.py":[1],"/home/kasule/Desktop/fast-food-fast-db/app/database/connect.py":[1],"/home/kasule/Desktop/fast-food-fast-db/app/database/__init__.py":[1],"/home/kasule/Desktop/fast-food-fast-db/app/models/model.py":[],"/home/kasule/Desktop/fast-food-fast-db/app/models/__init__.py":[],"/home/kasule/Desktop/fast-food-fast-db/app/views/routes.py":[1,2,3],"/home/kasule/Desktop/fast-food-fast-db/app/views/menu.py":[],"/home/kasule/Desktop/fast-food-fast-db/tests/__init__.py":[1],"/home/kasule/Desktop/fast-food-fast-db/app/__init__.py":[1,2]}} \ No newline at end of file +!coverage.py: This is a private format, don't read it directly!{"lines":{"/home/kasule/Desktop/fast-food-fast-db/app/views/__init__.py":[1,2,4,5],"/home/kasule/Desktop/fast-food-fast-db/run.py":[],"/home/kasule/Desktop/fast-food-fast-db/app/database/__init__.py":[1],"/home/kasule/Desktop/fast-food-fast-db/tests/test_user.py":[1],"/home/kasule/Desktop/fast-food-fast-db/app/models/model.py":[],"/home/kasule/Desktop/fast-food-fast-db/app/__init__.py":[1,2],"/home/kasule/Desktop/fast-food-fast-db/app/views/orders.py":[1,2,3],"/home/kasule/Desktop/fast-food-fast-db/app/views/menu.py":[],"/home/kasule/Desktop/fast-food-fast-db/app/database/connect.py":[1],"/home/kasule/Desktop/fast-food-fast-db/app/models/__init__.py":[],"/home/kasule/Desktop/fast-food-fast-db/tests/test_orders.py":[1],"/home/kasule/Desktop/fast-food-fast-db/tests/__init__.py":[1],"/home/kasule/Desktop/fast-food-fast-db/tests/test_base.py":[1]}} \ No newline at end of file diff --git a/README.md b/README.md index b1e99e0..55197db 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,7 @@ To run tests run this command below in your terminal coverage run --source=. -m unittest discover ``` ## API End Points + | End Point | Resource Accessed | Access | Requirements| | -------------------------------------- |-----------------------|------------|-------------| | api/v1/auth/signup POST | Register a user | PUBLIC | username, email, password @@ -58,3 +59,4 @@ coverage run --source=. -m unittest discover | api/v1/orders/order_id PUT | Update the status of an order | ADMIN | order_id | api/v1/menu GET | Get available menu | PUBLIC | menu_id | api/v1/menu POST | Add a meal option to the menu. | ADMIN | menu_id + diff --git a/app/__init__.py b/app/__init__.py index ccbcb0e..354923f 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,11 +1,10 @@ -from flask import Flask +from flask import Flask from app.views.orders import main from app.auth.views import auth +from app.auth import views from instance.config import app_config app = Flask(__name__, instance_relative_config=True) app.config.from_object(app_config["development"]) app.register_blueprint(main) app.register_blueprint(auth) - -from app.auth import views \ No newline at end of file diff --git a/app/auth/decorator.py b/app/auth/decorator.py index 3281f42..515089a 100644 --- a/app/auth/decorator.py +++ b/app/auth/decorator.py @@ -1,24 +1,28 @@ from functools import wraps -from flask import request, jsonify, current_app,make_response +from flask import request, jsonify, current_app, make_response import jwt from app.models.model import User -from app.database.connect import Database +from app.database.connect import Database + + def get_token(): token = None if 'Authorization' in request.headers: token = request.headers['Authorization'] - - token = token.split(" ")[1] + token = token.split(" ")[1] if not token: return make_response(jsonify({ - 'status':'failed', - 'message':'Token is missing!' - }),401) + 'status': 'failed', + 'message': 'Token is missing!' + }), 401) return token -def role_required(user): - return user + +# def role_required(user): +# return user + + def token_required(f): """ Decotator function to ensure that end points are provided by @@ -28,51 +32,54 @@ def token_required(f): def decorated(*args, **kwargs): token = get_token() try: - data = jwt.decode(token,'mysecret') + data = jwt.decode(token, 'mysecret') user_role = data['role'] - # role_required(user = user_role) database = Database() query = database.get_order_by_value( - 'users','email', data['email'] + 'users', 'email', data['email'] ) - current_user = User(query[0], query[1], query[2], query[3],query[4]) + current_user = User( + query[0], query[1], query[2], query[3], query[4], query[5]) except jwt.ExpiredSignatureError: - return 'Signature expired. Please log in again.' + return 'Signature expired. Please log in again.', 401 except jwt.InvalidTokenError: - return 'Invalid token. Please log in again.' - except: - return make_response(jsonify({ - "status": "failed", - "mesage": "Invalid token" - }), 401) - + return 'Invalid token. Please log in again.', 401 return f(current_user, *args, **kwargs) return decorated + def role_required(): token = get_token() - data = jwt.decode(token,'mysecret') + data = jwt.decode(token, 'mysecret') user_role = data['role'] return user_role + +def user_id(): + token = get_token() + data = jwt.decode(token, 'mysecret') + id = data['sub'] + return id + + def response(id, username, message, token, status_code): """ method to make http response for authorization token """ - return make_response(jsonify({ + return { "id": id, "username": username, - "message": message, - "auth_token": token + "message": message, + "auth_token": token + + }, status_code - }), status_code) -def response_message(status,message,status_code): +def response_message(status, message, status_code): """ method to handle response messages """ - return make_response(jsonify({ + return { "status": status, "message": message - }), status_code) - + }, status_code \ No newline at end of file diff --git a/app/auth/views.py b/app/auth/views.py index 7e1383f..72efe77 100644 --- a/app/auth/views.py +++ b/app/auth/views.py @@ -6,102 +6,105 @@ from app.models.model import User from app.auth.decorator import response, response_message from werkzeug.security import generate_password_hash, check_password_hash -from flask_restful import Api,Resource -from flask import make_response,Blueprint,request,jsonify +from flask_restful import Api, Resource +from flask import make_response, Blueprint, request, jsonify auth = Blueprint('auth', __name__) db = Database() api = Api(auth) + class RegisterUser(Resource): """ Class to register a user via api """ - def post(self): """ - User creates an account + User creates an account User sign up details are added to the data base """ if request.content_type != 'application/json': - return response_message('Bad request','Content-type must be in json', 202) - + return response_message( + 'Bad request', 'Content-type must be in json', 202) detail = request.get_json() username = detail['username'] email = detail['email'] location = detail['location'] password = generate_password_hash(detail['password']) - if not username : + if not username: return response_message('Missing', 'Username required', 400) - - if not re.match(r"[^@]+@[^@]+\.[^@]+", email) : - return response_message('Error','Missing or wrong email format', 202) - + if not re.match(r"[^@]+@[^@]+\.[^@]+", email): + return response_message( + 'Error', 'Missing or wrong email format', 202) if not len(detail['password']) > 4: - return response_message('Failed','Ensure password is morethan 4 characters', 202) - - if not isinstance(username, str) : - return response_message('Type Error', 'username must all be string', 202) - + return response_message( + 'Failed', 'Ensure password is morethan 4 characters', 202) + if not isinstance(username, str): + return response_message( + 'Type Error', 'username must all be string', 202) if not re.match("^[a-zA-Z0-9_.-]+$", username): - return response_message('Space Error', 'Username should not have space, better user -', 400) - - if db.get_order_by_value('users','email',email): + return response_message( + 'Space Error', 'Username should not have space, better user -', + 400) + if db.get_order_by_value('users', 'email', email): return response_message('Failed', 'User already registered', 409) - - db.insert_into_user(username, email,location,password) - return response_message('Success', 'User account successfully created, log in', 201) + db.insert_into_user(username, email, location, password) + if detail['role']: + db.update_role(detail['role'], email) + return response_message( + 'Success', 'User account successfully created, log in', 201) + class LoginUser(Resource): """ Class to register a user via api """ - def post(self): """ User login if he supplies correct credentials - Token is generated and given to a user + token is generated and given to a user """ if request.content_type != 'application/json': - return response_message('Bad request','Content-type must be in json', 202) - + return response_message( + 'Bad request', 'Content-type must be in json', 202) detail = request.get_json() username = detail['username'] - role = detail['role'] + # role = detail['role'] password = generate_password_hash(detail['password']) - - if not username : + if not username: return response_message('Failed', 'Username required', 400) - - if not username : + if not username: return response_message('Failed', 'Passed required', 400) - - db_user = db.get_order_by_value('users','username',username) - new_user = User(db_user[0],db_user[1],db_user[2],db_user[3],db_user[4]) - - if new_user.username == detail['username'] and check_password_hash(new_user.password, detail['password']): - #generate token + db_user = db.get_order_by_value('users', 'username', username) + if not db_user: + return ({"Failed": "incorect username"}, 401) + new_user = User( + db_user[0], db_user[1], db_user[2], db_user[3], + db_user[4], db_user[5] + ) + if new_user.username == detail['username'] and check_password_hash( + new_user.password, detail['password']): payload = { 'email': new_user.email, 'exp': datetime.datetime.utcnow() + - datetime.timedelta(days=60), + datetime.timedelta(days=60), 'iat': datetime.datetime.utcnow(), 'sub': new_user.user_id, - 'role': role + 'role': new_user.role } token = jwt.encode( payload, 'mysecret', algorithm='HS256' ) - if token: return response( - new_user.user_id,new_user.username,'You have succesfully logged in.', - token.decode('UTF-8'),200) - return response_message('Failed', 'Check your username or password', 401) - - -api.add_resource(RegisterUser,'/api/v1/auth/signup') -api.add_resource(LoginUser,'/api/v1/auth/login') - + new_user.user_id, new_user.username, + 'You have succesfully logged in.', + token.decode('UTF-8'), 200) + return response_message( + 'Failed', 'incorrect password', 401 + ) +# register +api.add_resource(RegisterUser, '/api/v1/auth/signup') +api.add_resource(LoginUser, '/api/v1/auth/login') \ No newline at end of file diff --git a/app/database/connect.py b/app/database/connect.py index 20a9a35..c257b47 100644 --- a/app/database/connect.py +++ b/app/database/connect.py @@ -1,31 +1,28 @@ import psycopg2 from flask import current_app as app + class Database(object): """class for app database""" def __init__(self): """initialize db connection """ self.connection = psycopg2.connect( - """ - dbname ='food_db' user='postgres' password ='password' - host='127.0.0.1' port='5432' - """ + """ + dbname ='food_db' user='postgres' password ='password' + host='127.0.0.1' port='5432' + """ ) self.connection.autocommit = True self.cursor = self.connection.cursor() try: if app.config['TESTING']: self.connection = psycopg2.connect( - """ - dbname ='test_db' user='postgres' password ='password' - host='127.0.0.1' port='5432' - """ + """ + dbname ='test_db' user='postgres' password ='password' + host='127.0.0.1' port='5432' + """ ) - else: - DATABASE_URL = os.environ['DATABASE_URL'] - - self.connection = psycopg2.connect(DATABASE_URL, sslmode='require') except Exception as e: print(e) self.cursor = self.connection.cursor() @@ -34,50 +31,56 @@ def create_tables(self): """ create tables """ create_table = """CREATE TABLE IF NOT EXISTS users (user_id SERIAL PRIMARY KEY, username VARCHAR(30), - email VARCHAR(100), location VARCHAR(100), password VARCHAR(150))""" + email VARCHAR(100), location VARCHAR(100), password VARCHAR(150), + role VARCHAR(100) DEFAULT 'user')""" self.cursor.execute(create_table) create_table = """ CREATE TABLE IF NOT EXISTS menu( - menu_id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL, - FOREIGN KEY(user_id) REFERENCES users(user_id) ON UPDATE CASCADE ON DELETE CASCADE, + menu_id SERIAL PRIMARY KEY, meal VARCHAR(40), description VARCHAR(200), - price INT NOT NULL, status VARCHAR(30))""" + price INT NOT NULL)""" self.cursor.execute(create_table) create_table = """ CREATE TABLE IF NOT EXISTS orders( - order_id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL, menu_id INTEGER NOT NULL, - meal VARCHAR(40), description VARCHAR(200), price INT, status VARCHAR(30), - FOREIGN KEY(user_id) REFERENCES users(user_id) ON UPDATE CASCADE ON DELETE CASCADE, - FOREIGN KEY(menu_id) REFERENCES menu(menu_id) ON UPDATE CASCADE ON DELETE CASCADE)""" + order_id SERIAL PRIMARY KEY, user_id INTEGER NOT NULL, + menu_id INTEGER NOT NULL, meal VARCHAR(40), description + VARCHAR(200), price INT, status VARCHAR(30), FOREIGN KEY(user_id) + REFERENCES users(user_id) ON UPDATE CASCADE ON DELETE CASCADE, + FOREIGN KEY(menu_id) REFERENCES menu(menu_id) ON UPDATE CASCADE ON + DELETE CASCADE)""" self.cursor.execute(create_table) - def insert_into_user(self,username,email,location,password): + def insert_into_user(self, username, email, location, password): """ - Query to add a new user + Query to add a new user :admin,user """ - user = """INSERT INTO users (username, email, location,password) VALUES - ('{}','{}','{}','{}'); """.format(username,email,location,password) + user = """INSERT INTO users + (username, email, location, password) + VALUES ('{}','{}','{}','{}'); + """.format(username, email, location, password) self.cursor.execute(user) self.connection.commit() - def add_to_menu(self,user_id, meal,description, price): + def add_to_menu(self, meal, description, price): """ Query to add food item to menu table in database - :admin """ meal_query = """INSERT INTO menu(meal,description,price) - VALUES('{}','{}','{}','{}'); """.format(user_id,meal,description,price) + VALUES('{}','{}','{}'); """.format(meal, description, price) self.cursor.execute(meal_query) self.connection.commit() - def insert_into_orders(self,user_id, menu_id, meal,description,price,status): + def insert_into_orders( + self, user_id, menu_id, meal, description, price, status + ): """ - Query to add order to the database - :user + Query to add order to the database : user """ - order_query = """INSERT INTO orders(user_id, menu_id, meal, description, price, status) - VALUES('{}','{}','{}','{}','{}','{}'); """.format(user_id, menu_id, meal, description, price, status) + order_query = """INSERT INTO orders( + user_id, menu_id, meal, description, price, status) + VALUES('{}','{}','{}','{}','{}','{}'); """.format( + user_id, menu_id, meal, description, price, status) self.cursor.execute(order_query) self.connection.commit() @@ -93,14 +96,13 @@ def get_all_orders(self): order_list.append(order) return order_list - def get_order_by_value(self,table_name,table_colum,value): + def get_order_by_value(self, table_name, table_colum, value): """ Function gets items from the - same table with similar ids - :admin + same table with similar ids :admin """ query = "SELECT * FROM {} WHERE {} = '{}';".format( - table_name,table_colum,value) + table_name, table_colum, value) self.cursor.execute(query) results = self.cursor.fetchone() return results @@ -114,31 +116,43 @@ def fetch_menu(self): menu = self.cursor.fetchall() menu_list = [] for item in menu: - menu_list.append(item) - return menu_list + menu_list.append(item) + return menu_list - def get_order_history_for_a_user(self): + def get_order_history_for_a_user(self, user_id): """ Select from orders where order.user_id = user.user_id :Admin """ - pass - - def update_order_status(self,stat, id): + query = "SELECT * FROM orders WHERE user_id = '{}';".format(user_id) + self.cursor.execute(query) + results = self.cursor.fetchall() + if results: + return results + return "you haven't ordered yet" + + def update_order_status(self, stat, id): """ update table orders set status ='' where order_id = id :Admin """ - status = ['New','processing','complete','cancelled'] + status = ['New', 'processing', 'complete', 'cancelled'] if stat in status: - query = "UPDATE orders SET status = '{}' WHERE order_id ='{}' ".format(stat,id) + query = """UPDATE orders SET status = '{}' + WHERE order_id ='{}' """.format(stat, id) self.cursor.execute(query) self.connection.commit() return "Order succcessfully Updated" return "Invalid Update status name" + def update_role(self, role, email): + query = """UPDATE users SET role = '{}' + WHERE email ='{}' """.format(role, email) + self.cursor.execute(query) + self.connection.commit() + def drop_tables(self): drop_query = "DROP TABLE IF EXISTS {0} CASCADE" - tables = ["users","menu","orders"] + tables = ["users", "menu", "orders"] for table in tables: self.cursor.execute(drop_query.format(table)) diff --git a/app/models/model.py b/app/models/model.py index 431defa..280eb86 100644 --- a/app/models/model.py +++ b/app/models/model.py @@ -1,18 +1,21 @@ from flask import json + class User: """ user class """ - def __init__(self,user_id,username,email,location,password): + def __init__(self, user_id, username, email, location, password, role): self.user_id = user_id self.username = username self.email = email self.password = password + self.role = role + class Order: """ class define order entities and return order dictionary """ - def __init__(self, dish_name,description,price,status): + def __init__(self, dish_name, description, price, status): self.dish = dish_name self.description = description self.price = price @@ -22,7 +25,7 @@ def order_json(self): """ return order in dictionary format """ - return { + return { 'dish': self.dish, 'description': self.description, 'price': self.price, @@ -34,5 +37,5 @@ def status(): """ Generate status list for orders """ - status = ['New','processing','cancelled','complete'] + status = ['New', 'processing', 'cancelled', 'complete'] return status diff --git a/app/views/menu.py b/app/views/menu.py index 17ff6f1..46d454d 100644 --- a/app/views/menu.py +++ b/app/views/menu.py @@ -1,11 +1,12 @@ -from flask import Flask, request,jsonify,Blueprint,make_response +from flask import Flask, request, jsonify, Blueprint, make_response from flask_restful import Api, Resource, abort from app.database.connect import Database +from app import main -main = Blueprint('main', __name__) Database = Database() food_api = Api(main) + class MenuAll(Resource): """ User or Admin get all food items on menu @@ -24,7 +25,13 @@ class MenuPost(Resource): :User """ def post(self): - pass + data = request.get_json() + meal = data['meal'] + desc = data['description'] + price = data['price'] + if Database.add_to_menu(meal,desc,price): + return make_response(jsonify({'message':'successfully added to menu'}), 201) + return make_response(jsonify({'Failed': 'Error adding a menu'})) food_api.add_resource(MenuAll, '/api/v1/menu') food_api.add_resource(MenuPost, '/api/v1/menu') \ No newline at end of file diff --git a/app/views/orders.py b/app/views/orders.py index 5d86b9c..5e65467 100644 --- a/app/views/orders.py +++ b/app/views/orders.py @@ -1,4 +1,4 @@ -from flask import Flask, request,jsonify,Blueprint,make_response,json +from flask import Flask, request, jsonify, Blueprint, make_response, json from flask_restful import Api, Resource, abort from app.database.connect import Database from app.auth.decorator import token_required, role_required @@ -9,24 +9,36 @@ Database = Database() food_api = Api(main) + class OrderAll(Resource): """ - Class has all request methods that - uses the end point /api/v1/orders/ - :Admin + Class has all request methods that + uses the end point /api/v1/orders/ :Admin """ @token_required - def get(self,current_user): + def get(self, current_user): if role_required() == 'admin': all = Database.get_all_orders() - if all: - return make_response(jsonify({ - 'order_id':all[0][0], 'menu_id':all[0][1], 'user_id':all[0][2], 'meal':all[0][3], - 'desc':all[0][4], 'price':all[0][5], 'status':all[0][6]}),200 - ) - return make_response(jsonify({'error':'no orders posted yet'}),404) - return jsonify({'Failed':'You dont have permission to access this route'}),409 + all_order_list = [] + if not all: + return {'error': 'no orders posted yet'}, 404 + for row in all: + order_dict = { + "order_id": row[0], + "menu_id": row[1], + 'user_id': row[2], + "meal": row[3], + "desc": row[4], + "price": row[5], + "status": row[6] + } + all_order_list.append(order_dict) + return {'Orders': all_order_list}, 200 + return { + 'Failed': 'You dont have permission to access this route' + }, 409 + class OrderPost(Resource): """ @@ -34,37 +46,23 @@ class OrderPost(Resource): :User """ @token_required - def post(current_user,menu): + def post(current_user, menu_id): data = request.get_json() if request.content_type != 'application/json': - return response_message('Failed','Content type must be application/json', 401) - - if not isinstance(data['description'], str) or not isinstance(data['meal'], str): - return response_message('Failed','Description and Dish must be string format', 401) - - if data['meal'].isspace() or data['description'].isspace(): - return response_message('Failed','order request contains spaces only', 401) - - if not isinstance(data['price'], int): - return response_message('Failed','price must be integer', 401) - - if len(data['meal'])==0 or len(data['description']) == 0 or data['price'] == 0: - return response_message('Failed','No field should be left empty', 401) - - order = Order(data['meal'],data['description'],data['price'],status='new') - - meal = order.dish - description = order.description - price = order.price - status = order.status[0] - menu_id = 2 + return response_message( + 'Failed', 'Content type must be application/json', 401) + id_menu = data['meal_id'] current_user = current_user.user_id + order_row = Database.get_order_by_value('menu', 'menu_id', id_menu) + if not order_row: + return ({"Message": "No item for that id"}, 404) + dish = order_row[1] + desc = order_row[2] + price = order_row[3] + Database.insert_into_orders( + current_user, id_menu, dish, desc, price, status='new') + return response_message('Success', 'Order successfully submited', 200) - if Database.get_order_by_value('orders','meal',meal): - return response_message('Failed','Order Already Exists', 409) - Database.insert_into_orders(current_user,menu_id,meal,description,price,status) - return response_message('Success','Order successfully submited', 201) - class OrderById(Resource): """ @@ -72,18 +70,22 @@ class OrderById(Resource): :Admin """ @token_required - def get(self,current_user,order_id): - if role_required != 'admin': - return make_response(jsonify({'Failed':'You dont have permission to access this route'}),409) + def get(self, current_user, order_id): + if role_required() != 'admin': + return { + 'Failed': 'You dont have permission to access this route' + }, 409 order_one = Database.get_order_by_value('orders', 'order_id', order_id) if order_one: - response = {'order_id':order_one[0],'meal':order_one[3], 'desc':order_one[4], 'price':order_one[5]} + response = { + 'order_id': order_one[0], 'meal': order_one[3], + 'desc': order_one[4], 'price': order_one[5] + } user = Database.get_order_by_value('users', 'user_id', order_id) - return make_response(jsonify({'order':response, 'Order BY':user}),200) - - return response_message('Failed','No order by that Id', 404) - + return make_response(jsonify({ + 'order': response, 'Order BY': role_required()}), 200) + return response_message('Failed', 'No order by that Id', 404) class UpdateStatus(Resource): @@ -93,25 +95,113 @@ class UpdateStatus(Resource): :Admin """ @token_required - def put(self,current_user, order_id): - if role_required != 'admin': - return make_response(jsonify({'Failed':'You dont have permission to access this route'}),409) + def put(self, current_user, order_id): + if role_required() != 'admin': + return make_response(jsonify({ + 'Failed': 'You dont have permission to access this route' + }), 409) data = request.get_json() if request.content_type != 'application/json': - return response_message('Failed','Content type must be application/json', 401) - - if not isinstance(data['status'], str) : - return response_message('Type Error', 'Status must only be string', 400) - - if data['status'].isspace() or len(data['status']) ==0: - return response_message('Failed','Status should not be empty or have only spaces', 401) - - to_update = Database.update_order_status(data['status'],order_id) + return response_message( + 'Failed', 'Content type must be application/json', + 401) + + if not isinstance(data['status'], str): + return response_message( + 'Type Error', 'Status must only be string', + 400) + if data['status'].isspace() or len(data['status']) == 0: + return response_message( + 'Failed', 'Status should not be empty or have only spaces', + 401) + to_update = Database.update_order_status(data['status'], order_id) if to_update: - return response_message('message',to_update, 200) + return response_message('message', to_update, 200) + + +class UserHistory(Resource): + """ + user can view all the order histories + """ + @token_required + def get(current_user, user): + user_id = current_user.user_id + order_all = Database.get_order_history_for_a_user(user_id) + order_list = [] + if not order_all: + return {'error': 'You have not ordered from the site yet'}, 404 + for row in order_all: + order_dict = { + "order_id": row[0], + "menu_id": row[1], + 'user_id': row[2], + "meal": row[3], + "desc": row[4], + "price": row[5], + "status": row[6] + } + order_list.append(order_dict) + return {'Requested': order_list}, 200 + + +class MenuAll(Resource): + """ + User or Admin get all food items on menu + end point /api/v1/menu/ + :User, Admin + """ + def get(self): + all = Database.fetch_menu() + menu_list = [] + if not all: + return {'error': 'nothing on menu today'}, 404 + for row in all: + menu_dict = { + "menu_id": row[0], + "meal": row[1], + "desc": row[2], + "price": row[3] + } + menu_list.append(menu_dict) + return {'Onmenu': menu_list}, 200 + + +class MenuPost(Resource): + """ + Class for posting an order request by the user + :User + """ + def post(self): + data = request.get_json() + meal = data['meal'] + desc = data['description'] + price = data['price'] + if not isinstance( + desc, str) or not isinstance(meal, str): + return response_message( + 'Failed', 'Description and Dish must be string format', 401) + if meal.isspace() or desc.isspace(): + return response_message( + 'Failed', 'order request contains spaces only', 401) + if not isinstance(price, int): + return response_message('Failed', 'price must be integer', 401) + if len(meal) == 0 or len(desc) == 0 or price == 0: + return response_message( + 'Failed', 'No field should be left empty', 401) + order = Order(meal, desc, price, status='new') + meal = order.dish + if Database.add_to_menu(meal, desc, price): + return {'message': 'successfully added to menu'}, 201 + return {'Failed': 'Error adding a menu'}, 401 + +food_api.add_resource(MenuAll, '/api/v1/menu') +food_api.add_resource(MenuPost, '/api/v1/menu') + +# register food_api.add_resource(OrderAll, '/api/v1/orders/') food_api.add_resource(OrderPost, '/api/v1/users/orders/') food_api.add_resource(OrderById, '/api/v1/orders/') -food_api.add_resource(UpdateStatus, '/api/v1/orders/') \ No newline at end of file +food_api.add_resource(UpdateStatus, '/api/v1/orders/') +food_api.add_resource(UserHistory, '/api/v1/users/orders/') \ No newline at end of file diff --git a/instance/config.py b/instance/config.py index e12279c..f881dfb 100644 --- a/instance/config.py +++ b/instance/config.py @@ -6,18 +6,21 @@ class Config(object): DEBUG = False SECRET = os.getenv('SECRET') + class DevelopmentConfig(Config): """Configurations for Development.""" DEBUG = True # DATABASE_URL1 = 'postgresql://postgres:password@localhost:5432/food_db' DATABASE_URL = 'postgres://mlmjbgdiusgkek:42f5c949ece6dd260303c77682bffb55cb2a53cd8973435a1e8bb3fd0de1abe2@ec2-23-23-80-20.compute-1.amazonaws.com:5432/dbigg45nbqe6jq' + class TestingConfig(Config): """Configurations for Testing.""" TESTING = True DEBUG = True DATABASE_URL = 'postgresql://postgres:password@localhost:5432/test_db' + class ProductionConfig(Config): """Configurations for Production.""" DEBUG = False diff --git a/tests/test_base.py b/tests/test_base.py index c767431..472cbc9 100644 --- a/tests/test_base.py +++ b/tests/test_base.py @@ -1,5 +1,6 @@ from app import app, app_config from flask import current_app +from instance import config from app.database.connect import Database as db import unittest import json @@ -21,7 +22,13 @@ def setUp(self): connect.drop_tables() connect.create_tables() - def signup_user(self,username,email,location,password): + def tearDown(self): + with app.app_context(): + connect = db() + connect.drop_tables() + connect.create_tables() + + def signup_user(self, username, email, location, password, role): """ Method to define user registration details """ @@ -29,14 +36,16 @@ def signup_user(self,username,email,location,password): "username": username, "email": email, "location": location, - "password": password + "password": password, + "role": role } return self.client.post( '/api/v1/auth/signup', - content_type= "application/json", - data = json.dumps(register) + content_type="application/json", + data=json.dumps(register) ) - def login_user(self,username,password): + + def login_user(self, username, password): """ Method to define user login details """ @@ -46,28 +55,13 @@ def login_user(self,username,password): } return self.client.post( '/api/v1/auth/login', - content_type = "application/json", - data = json.dumps(login) + content_type="application/json", + data=json.dumps(login) ) - # def order_post(self,meal,desc,price): - # """ - # Define post attributes and post route - # """ - # data = { - # "meal": meal, - # "desc": desc, - # "price": price - # } - # return self.client.post( - # '/api/v1/orders/', - # content_type= "application/json", - # data = json.dumps(data) - # ) - - def order_post(self,meal,desc,price): + def menu_post(self, meal, desc, price): """ - Define post attributes and post route + Define post attributes and route """ data = { "meal": meal, @@ -75,18 +69,16 @@ def order_post(self,meal,desc,price): "price": price } return self.client.post( - '/api/v1/users/orders/', - content_type= "application/json", - data = json.dumps(data) + '/api/v1/menu', + content_type="application/json", + data=json.dumps(data) ) - - - def tearDown(self): - with app.app_context(): - connect = db() - connect.drop_tables() - connect.create_tables() - - + def order(self, meal, desc, price, status): + return { + "meal": meal, + "desc": desc, + "price": price, + "status": status + } diff --git a/tests/test_orders.py b/tests/test_menu.py similarity index 64% rename from tests/test_orders.py rename to tests/test_menu.py index bba5527..df3dcea 100644 --- a/tests/test_orders.py +++ b/tests/test_menu.py @@ -4,55 +4,76 @@ import json db = Database() -class TestOrder(BaseTestCase): +class TestOrder(BaseTestCase): - # def test_order_in_json(self): - # with self.client: - # order = self.order_post("katogo","any kind",9000) - # self.assertTrue(order.content_type=="application/json") - # self.assertFalse(order.content_type=="text/plain") + def test_menu_in_json(self): + """ + menu item should be in json format + """ + with self.client: + order = self.menu_post("katogo", "any kind", 9000) + self.assertTrue(order.content_type == "application/json") + self.assertFalse(order.content_type == "text/plain") def test_desc_and_meal_not_string(self): - ord = { + """ + description and meal should be of string data type + """ + ord = { "meal": 2334, "description": True, "price": 7900 } rs = self.client.post( - '/api/v1/users/orders/', - content_type= "application/json", - data = json.dumps(ord) + '/api/v1/menu', + content_type="application/json", + data=json.dumps(ord) ) self.assertEqual(rs.status_code, 401) data = json.loads(rs.data.decode()) - self.assertTrue(data['status']== 'Failed') - self.assertTrue(data['message']== 'Description and Dish must be string format') + self.assertTrue(data['status'] == 'Failed') + self.assertTrue(data['message'] == 'Description and Dish must be string format') def test_meal_and_desc_not_spaces(self): + """ + checks whether meal and desc have only spaces + """ with self.client: - order = self.order_post(" "," ",9000) + order = self.menu_post(" ", " ", 9000) self.assertEqual(order.status_code, 401) data = json.loads(order.data.decode()) self.assertTrue(data['status'] == 'Failed') self.assertTrue(data['message'] == 'order request contains spaces only') def test_price_not_integer(self): + """ tests price is integer data type + """ with self.client: - order = self.order_post("kati","any","") + order = self.menu_post("kati", "any", "") self.assertEqual(order.status_code, 401) data = json.loads(order.data.decode()) self.assertTrue(data['status'] == 'Failed') self.assertTrue(data['message'] == 'price must be integer') def test_fields_not_filled(self): + """ + fields should not be left un filled + """ with self.client: - order = self.order_post("kati","any",0) + order = self.menu_post("kati", "any", 0) self.assertEqual(order.status_code, 401) data = json.loads(order.data.decode()) self.assertTrue(data['status'] == 'Failed') self.assertTrue(data['message'] == 'No field should be left empty') + def test_menu_submited(self): + with self.client: + order = self.order("chicken", "roasted", 8990, 'new') + self.assertTrue(order) + # aa = Database().add_to_menu( + # order['meal'], order['desc'], order['price']) + # self.assertTrue(aa) # def test_succesful_order_creation(self): # with self.client: # order = { diff --git a/tests/test_user.py b/tests/test_user.py index b708fa3..2d9840a 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -3,35 +3,38 @@ from app.database.connect import Database import json db = Database() -class TestAuth(BaseTestCase): +class TestAuth(BaseTestCase): + def test_user_class(self): - user = User(1,"KASULE","kasule@gmail.com","kansanga",1234) + user = User(1, "KASULE", "kasule@gmail.com", "kansanga", 1234, 'user') self.assertTrue(user) def test_details_json_format(self): with self.client: - result = self.signup_user("kasule","kasule@gmail.com","kansanga","1234") - self.assertTrue(result.content_type=="application/json") + result = self.signup_user( + "kasule", "kasule@gmail.com", "kansanga", "1234", "user") + self.assertTrue(result.content_type == "application/json") def test_email_not_valid(self): """ - Test for invalid email address + Test for invalid email address """ register = { "username": "kasule", "email": "email", "location": "location", - "password": "password" + "password": "password", + "role": "user" } rs = self.client.post( '/api/v1/auth/signup', - content_type= "application/json", - data = json.dumps(register) + content_type="application/json", + data=json.dumps(register) ) data = json.loads(rs.data.decode()) - self.assertEqual(rs.status_code,202) + self.assertEqual(rs.status_code, 202) self.assertTrue(data['status'] == 'Error') self.assertTrue(data['message'] == 'Missing or wrong email format') @@ -40,7 +43,8 @@ def test_short_password(self): Test for short password """ with self.client: - result = self.signup_user("kasule","kasule@gmail.com","kansanga","124") + result = self.signup_user( + "kasule", "kasule@gmail.com", "kansanga", "124", "user") self.assertEqual(result.status_code, 202) data = json.loads(result.data.decode()) self.assertTrue(data['status'] == 'Failed') @@ -51,7 +55,8 @@ def test_username_is_string(self): Test username isstring """ with self.client: - result = self.signup_user(1234,"kasule@gmail.com","kansanga","1246575") + result = self.signup_user( + 1234, "kasule@gmail.com", "kansanga", "1246575", "user") self.assertEqual(result.status_code, 202) data = json.loads(result.data.decode()) self.assertTrue(data['status'] == 'Type Error') @@ -62,7 +67,8 @@ def test_spaces_in_username(self): Test username has no spaces between characters """ with self.client: - result = self.signup_user(" ","kasule@gmail.com","kansanga","1246575") + result = self.signup_user( + " ", "kasule@gmail.com", "kansanga", "1246575", "user") self.assertEqual(result.status_code, 400) data = json.loads(result.data.decode()) self.assertTrue(data['status'] == 'Space Error') @@ -73,70 +79,79 @@ def test_username_not_provided(self): Test username field left empty """ with self.client: - result = self.signup_user("","kasule@gmail.com","kansanga","1246575") + result = self.signup_user( + "", "kasule@gmail.com", "kansanga", "1246575", "user") self.assertEqual(result.status_code, 400) data = json.loads(result.data.decode()) self.assertTrue(data['status'] == 'Missing') self.assertTrue(data['message'] == 'Username required') - def test_user_dat_not_json(self): - """ + """ Test Content_type not application/json for sign up request """ rv = self.client.post( '/api/v1/auth/signup', - content_type= "text", - data = json.dumps(dict({'status':'register'})) + content_type="text", + data=json.dumps(dict({'status': 'register'})) ) self.assertEqual(rv.status_code, 202) - self.assertIn('"message": "Content-type must be in json"',str(rv.data)) - + self.assertIn('"message": "Content-type must be in json"', str(rv.data)) + def test_content_type_4_login_not_json(self): """ Test Content_type not application/json for login request """ rv = self.client.post( '/api/v1/auth/login', - content_type= "text", - data = json.dumps(dict({'status':'register'})) + content_type="text", + data=json.dumps(dict({'status': 'register'})) ) self.assertEqual(rv.status_code, 202) - self.assertIn('"message": "Content-type must be in json"',str(rv.data)) - + self.assertIn('"message": "Content-type must be in json"', str(rv.data)) + def test_user_already_exist(self): with self.client: - self.signup_user("kasule","kasule@gmail.com","kansanga","123464") - result = self.signup_user("kasule","kasule@gmail.com","kansanga","12ht34") + self.signup_user( + "kasule", "kasule@gmail.com", "kansanga", "123464", "user") + result = self.signup_user( + "kasule", "kasule@gmail.com", "kansanga", "123464", "user") self.assertEqual(result.status_code, 409) data = json.loads(result.data.decode()) self.assertTrue(data['status'] == 'Failed') self.assertTrue(data['message'] == 'User already registered') - def test_successful_login(self): with self.client: - self.signup_user("kasule","kasule@gmail.com","kansanga","12389894") - response = self.login_user("kasule","12389894") + self.signup_user( + "kasule", "kasule@gmail.com", "kansanga", "12389894", "user") + response = self.login_user("kasule", "12389894") res = json.loads(response.data.decode()) - self.assertEqual(response.status_code,200) + self.assertEqual(response.status_code, 200) self.assertTrue(res['username'] == 'kasule') - self.assertEqual("You have succesfully logged in.",str(res['message'])) + self.assertEqual( + "You have succesfully logged in.", str(res['message'])) self.assertTrue(res['auth_token']) def test_login_credentials(self): with self.client: - self.signup_user("kasule","kasule@gmail.com","kansanga","12389894") - resp = self.login_user("kasule","boooooooo") + self.signup_user( + "kasule", "kasule@gmail.com", "kansanga", "12389894", "user") + resp = self.login_user("kasule", "boooooooo") res = json.loads(resp.data.decode()) - self.assertEqual(resp.status_code,401) + self.assertEqual(resp.status_code, 401) self.assertTrue(res['status'] == 'Failed') - self.assertEqual('Check your username or password',str(res['message'])) + self.assertEqual( + 'incorrect password', str(res['message']) + ) def test_successful_signup(self): with self.client: - result = self.signup_user("kasule","kasule@gmail.com","kansanga","12347809") - self.assertEqual(result.status_code,201) + result = self.signup_user( + "kasule", "kasule@gmail.com", "kansanga", "12347809", "admin") + self.assertEqual(result.status_code, 201) res = json.loads(result.data.decode()) self.assertTrue(res['status'] == 'Success') - self.assertEqual('User account successfully created, log in',str(res['message'])) \ No newline at end of file + self.assertEqual( + 'User account successfully created, log in', str(res['message']) + ) \ No newline at end of file