Skip to content

Commit

Permalink
Feat: issue#63 - GET /user api endpoint and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mtreacy002 committed Jul 8, 2020
1 parent 674a287 commit b2e01a2
Show file tree
Hide file tree
Showing 7 changed files with 296 additions and 21 deletions.
3 changes: 2 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ before_script:
install:
- pip3 install -r requirements.txt
script:
- python -m unittest discover tests -v
- python -m unittest discover tests -v
- pytest --cov tests
52 changes: 52 additions & 0 deletions app/api/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ def add_models_to_namespace(api_namespace):
api_namespace.models[register_user_api_model.name] = register_user_api_model
api_namespace.models[login_request_body_model.name] = login_request_body_model
api_namespace.models[login_response_body_model.name] = login_response_body_model
api_namespace.models[full_user_api_model.name] = full_user_api_model

register_user_api_model = Model(
"User registration model",
Expand Down Expand Up @@ -43,3 +44,54 @@ def add_models_to_namespace(api_namespace):
},
)

full_user_api_model = Model(
"User Complete model used in listing",
{
"id": fields.Integer(
readOnly=True, description="The unique identifier of a user"
),
"name": fields.String(required=True, description="User name"),
"username": fields.String(required=True, description="User username"),
"email": fields.String(required=True, description="User email"),
"password_hash": fields.String(required=True, description="User password hash"),
"terms_and_conditions_checked": fields.Boolean(
required=True, description="User Terms and Conditions check state"
),
"is_admin": fields.Boolean(required=True, description="User admin status"),
"registration_date": fields.Float(
required=True, description="User registration date"
),
"is_email_verified": fields.Boolean(
required=True, description="User email verification status"
),
"email_verification_date": fields.DateTime(
required=False, description="User email verification date"
),
"bio": fields.String(required=False, description="User bio"),
"location": fields.String(required=False, description="User location"),
"occupation": fields.String(required=False, description="User occupation"),
"organization": fields.String(required=False, description="User organization"),
"slack_username": fields.String(
required=False, description="User slack username"
),
"social_media_links": fields.String(
required=False, description="User social media links"
),
"skills": fields.String(required=False, description="User skills"),
"interests": fields.String(required=False, description="User interests"),
"resume_url": fields.String(required=False, description="User resume url"),
"photo_url": fields.String(required=False, description="User photo url"),
"need_mentoring": fields.Boolean(
required=False, description="User need mentoring indication"
),
"available_to_mentor": fields.Boolean(
required=False, description="User availability to mentor indication"
),
"current_mentorship_role": fields.Integer(
required=False, description="User current role"
),
"membership_status": fields.Integer(
required=False, description="User membershipstatus"
),
},
)
62 changes: 49 additions & 13 deletions app/api/ms_api_utils.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,17 @@
import logging
from http import HTTPStatus
from http import HTTPStatus, cookies
from datetime import datetime
from flask import json
import requests
from app import messages
from app.utils.decorator_utils import http_response_namedtuple_converter


# set base url

# for ms-api local server
BASE_MS_API_URL = "http://127.0.0.1:4000"
AUTH_COOKIE = cookies.SimpleCookie()

# for MS-API on Heroku server
# WARNING!!! When you push a PR, for travis to pass test cases related to MS API
# Heroku MS API needs to be set as preference over the localhost. Otherwise, make sure
# you run the MS API local server when you push the PR.
# BASE_MS_API_URL = "https://bridge-in-tech-ms-test.herokuapp.com"

# create instance

def post_request(request_url, data):
def post_request(request_string, data):
request_url = f"{BASE_MS_API_URL}{request_string}"
try:
response = requests.post(
request_url, json=data, headers={"Accept": "application/json"}
Expand All @@ -38,6 +30,47 @@ def post_request(request_url, data):
response_message = messages.INTERNAL_SERVER_ERROR
response_code = json.dumps(HTTPStatus.INTERNAL_SERVER_ERROR)
logging.fatal(f"{e}")
finally:
if request_string == "/login" and response_code == HTTPStatus.OK:
access_token_cookie = response_message.get("access_token")
access_expiry_cookie = response_message.get("access_expiry")
AUTH_COOKIE["Authorization"] = f"Bearer {access_token_cookie}"
AUTH_COOKIE["Authorization"]["expires"] = access_expiry_cookie
response_message = {"access_token": response_message.get("access_token"), "access_expiry": response_message.get("access_expiry")}

logging.fatal(f"{response_message}")
return response_message, response_code


def get_request(request_string, token):
request_url = f"{BASE_MS_API_URL}" + request_string
if not token or not AUTH_COOKIE:
return messages.AUTHORISATION_TOKEN_IS_MISSING, HTTPStatus.UNAUTHORIZED
if AUTH_COOKIE:
if token != AUTH_COOKIE["Authorization"].value:
return messages.TOKEN_IS_INVALID, HTTPStatus.UNAUTHORIZED
if datetime.utcnow().timestamp() > AUTH_COOKIE["Authorization"]["expires"]:
return messages.TOKEN_HAS_EXPIRED, HTTPStatus.UNAUTHORIZED

try:
response = requests.get(
request_url,
headers={"Authorization": AUTH_COOKIE["Authorization"].value, "Accept": "application/json"},
)
response.raise_for_status()
response_message = response.json()
response_code = response.status_code
except requests.exceptions.ConnectionError as e:
response_message = messages.INTERNAL_SERVER_ERROR
response_code = json.dumps(HTTPStatus.INTERNAL_SERVER_ERROR)
logging.fatal(f"{e}")
except requests.exceptions.HTTPError as e:
response_message = e.response.json()
response_code = e.response.status_code
except Exception as e:
response_message = messages.INTERNAL_SERVER_ERROR
response_code = json.dumps(HTTPStatus.INTERNAL_SERVER_ERROR)
logging.fatal(f"{e}")
finally:
logging.fatal(f"{response_message}")
return response_message, response_code
Expand All @@ -52,6 +85,9 @@ def http_response_checker(result):
# result = http_bad_request_status_checker(result)
# if result.status_code == HTTPStatus.NOT_FOUND:
# result = http_not_found_status_checker(result)
# if result.status_code == json.dumps(HTTPStatus.INTERNAL_SERVER_ERROR) and not AUTH_COOKIE:
# # if not AUTH_COOKIE:
# return messages.TOKEN_IS_INVALID, HTTPStatus.UNAUTHORIZED
return result


Expand Down
9 changes: 9 additions & 0 deletions app/api/resources/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from flask_restx import reqparse

auth_header_parser = reqparse.RequestParser()
auth_header_parser.add_argument(
"Authorization",
required=True,
help="Authentication access token. E.g.: Bearer <access_token>",
location="headers",
)
42 changes: 35 additions & 7 deletions app/api/resources/users.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import json
from http import HTTPStatus
from http import HTTPStatus, cookies
from datetime import datetime, timedelta
from flask import request
from flask_jwt_extended import (
jwt_required,
Expand All @@ -8,12 +9,12 @@
create_refresh_token,
get_jwt_identity,
)
from flask_restx import Resource, Namespace
from app.api.ms_api_utils import post_request, BASE_MS_API_URL, http_response_checker
from flask_restx import Resource, marshal, Namespace
from app.api.ms_api_utils import post_request, get_request, http_response_checker, AUTH_COOKIE
from app import messages
from app.api.models.user import *
from app.api.validations.user import *

from app.api.resources.common import auth_header_parser

users_ns = Namespace("Users", description="Operations related to users")
add_models_to_namespace(users_ns)
Expand Down Expand Up @@ -65,7 +66,7 @@ def post(cls):
if is_valid != {}:
return is_valid, HTTPStatus.BAD_REQUEST

result = post_request(f"{BASE_MS_API_URL}/register", data)
result = post_request("/register", data)

return http_response_checker(result)

Expand All @@ -78,7 +79,7 @@ class LoginUser(Resource):
@users_ns.response(
HTTPStatus.BAD_REQUEST,
f"{messages.USERNAME_FIELD_IS_MISSING}\n"
f"{messages.PASSWORD_FIELD_IS_MISSING}",
f"{messages.PASSWORD_FIELD_IS_MISSING}"
)
@users_ns.response(HTTPStatus.UNAUTHORIZED, f"{messages.WRONG_USERNAME_OR_PASSWORD}")
@users_ns.response(HTTPStatus.FORBIDDEN, f"{messages.USER_HAS_NOT_VERIFIED_EMAIL_BEFORE_LOGIN}")
Expand All @@ -105,6 +106,33 @@ def post(cls):
if not password:
return messages.PASSWORD_FIELD_IS_MISSING, HTTPStatus.BAD_REQUEST

result = post_request(f"{BASE_MS_API_URL}/login", data)
result = post_request("/login", data)

return http_response_checker(result)


@users_ns.route("user")
class MyUserProfile(Resource):
@classmethod
@users_ns.doc("get_user")
@users_ns.response(HTTPStatus.OK, "Successful request", full_user_api_model)
@users_ns.response(
HTTPStatus.UNAUTHORIZED,
f"{messages.TOKEN_HAS_EXPIRED}\n"
f"{messages.TOKEN_IS_INVALID}\n"
f"{messages.AUTHORISATION_TOKEN_IS_MISSING}"
)
@users_ns.response(HTTPStatus.NOT_FOUND, f"{messages.USER_DOES_NOT_EXIST}")
@users_ns.response(HTTPStatus.INTERNAL_SERVER_ERROR, f"{messages.INTERNAL_SERVER_ERROR}")
@users_ns.expect(auth_header_parser, validate=True)
def get(cls):
"""
Returns details of current user.
A user with valid access token can use this endpoint to view his/her own
user details. The endpoint doesn't take any other input.
"""
token = request.headers.environ["HTTP_AUTHORIZATION"]

result = get_request("/user", token)
return http_response_checker(result)
1 change: 1 addition & 0 deletions config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class BaseConfig(object):
# Flask JWT settings
JWT_ACCESS_TOKEN_EXPIRES = timedelta(weeks=1)
JWT_REFRESH_TOKEN_EXPIRES = timedelta(weeks=4)
PROPAGATE_EXCEPTION = True

# Security
SECRET_KEY = os.getenv("SECRET_KEY", None)
Expand Down
Loading

0 comments on commit b2e01a2

Please sign in to comment.