Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Issue#62 update user api endpoint #74

Merged
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
35 changes: 33 additions & 2 deletions app/api/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ def add_models_to_namespace(api_namespace):
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

api_namespace.models[update_user_request_body_model.name] = update_user_request_body_model

register_user_api_model = Model(
"User registration model",
{
Expand Down Expand Up @@ -70,7 +71,7 @@ def add_models_to_namespace(api_namespace):
"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"),
"current_organization": fields.String(required=False, description="User current organization"),
"slack_username": fields.String(
required=False, description="User slack username"
),
Expand All @@ -95,3 +96,33 @@ def add_models_to_namespace(api_namespace):
),
},
)

update_user_request_body_model = Model(
"Update User request data model",
{
"name": fields.String(required=False, description="User name"),
"username": fields.String(required=False, description="User username"),
"bio": fields.String(required=False, description="User bio"),
"location": fields.String(required=False, description="User location"),
"occupation": fields.String(required=False, description="User occupation"),
"current_organization": fields.String(required=False, description="User current 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"),
# TODO: This url is generated by the MS backend
"resume_url": fields.String(required=False, description="User resume url"),
# TODO: This url is generated by the MS backend
"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"
),
},
)
90 changes: 64 additions & 26 deletions app/api/ms_api_utils.py → app/api/request_api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,39 +44,77 @@ def post_request(request_string, data):

def get_request(request_string, token):
request_url = f"{BASE_MS_API_URL}{request_string}"
is_wrong_token = validate_token(token)

if not is_wrong_token:
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:
if request_string == "/user" and response_code == HTTPStatus.OK:
AUTH_COOKIE["user_id"] = response_message.get("id")
logging.fatal(f"{response_message}")
return response_message, response_code
return is_wrong_token


def put_request(request_string, token, data):
request_url = f"{BASE_MS_API_URL}{request_string}"
is_wrong_token = validate_token(token)

if not is_wrong_token:
try:
response = requests.put(
request_url,
json=data,
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
return is_wrong_token


def validate_token(token):
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:
if request_string == "/user" and response_code == HTTPStatus.OK:
AUTH_COOKIE["user_id"] = response_message.get("id")
logging.fatal(f"{response_message}")
return response_message, response_code

return ()


@http_response_namedtuple_converter
def http_response_checker(result):
Expand Down
66 changes: 61 additions & 5 deletions app/api/resources/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@
get_jwt_identity,
)
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.api.request_api_utils import post_request, get_request, put_request, http_response_checker, AUTH_COOKIE
from app import messages
from app.api.models.user import *
from app.api.validations.user import *
from app.utils.validation_utils import expected_fields_validator
from app.api.resources.common import auth_header_parser

users_ns = Namespace("Users", description="Operations related to users")
Expand All @@ -38,7 +39,8 @@ class UserRegister(Resource):
f"{messages.USERNAME_INPUT_BY_USER_IS_INVALID}\n"
f"{messages.EMAIL_INPUT_BY_USER_IS_INVALID}\n"
f"{messages.PASSWORD_INPUT_BY_USER_HAS_INVALID_LENGTH}\n"
f"{messages.TERMS_AND_CONDITIONS_ARE_NOT_CHECKED}"
f"{messages.TERMS_AND_CONDITIONS_ARE_NOT_CHECKED}\n"
f"{messages.UNEXPECTED_INPUT}"
)
@users_ns.response(
HTTPStatus.CONFLICT,
Expand All @@ -61,10 +63,13 @@ def post(cls):

data = request.json

is_valid = validate_user_registration_request_data(data)
is_field_valid = expected_fields_validator(data, register_user_api_model)
if not is_field_valid.get("is_field_valid"):
return is_field_valid.get("message"), HTTPStatus.BAD_REQUEST

if is_valid != {}:
return is_valid, HTTPStatus.BAD_REQUEST
is_not_valid = validate_user_registration_request_data(data)
if is_not_valid:
return is_not_valid, HTTPStatus.BAD_REQUEST

result = post_request("/register", data)

Expand Down Expand Up @@ -101,6 +106,10 @@ def post(cls):
username = data.get("username", None)
password = data.get("password", None)

is_field_valid = expected_fields_validator(data, login_request_body_model)
if not is_field_valid.get("is_field_valid"):
return is_field_valid.get("message"), HTTPStatus.BAD_REQUEST

if not username:
return messages.USERNAME_FIELD_IS_MISSING, HTTPStatus.BAD_REQUEST
if not password:
Expand Down Expand Up @@ -135,4 +144,51 @@ def get(cls):
token = request.headers.environ["HTTP_AUTHORIZATION"]

result = get_request("/user", token)

return http_response_checker(result)


@classmethod
@users_ns.doc("update_user_profile")
@users_ns.response(HTTPStatus.OK, f"{messages.USER_SUCCESSFULLY_UPDATED}")
@users_ns.response(HTTPStatus.BAD_REQUEST, "Invalid input.")
@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, update_user_request_body_model, validate=True,)
def put(cls):
"""
Updates user profile

A user with valid access token can use this endpoint to edit his/her own
user details. The endpoint takes any of the given parameters (name, username,
bio, location, occupation, organization, slack_username, social_media_links,
skills, interests, resume_url, photo_url, need_mentoring, available_to_mentor).
The response contains a success message.
"""

data = request.json

if not data:
return messages.NO_DATA_FOR_UPDATING_PROFILE_WAS_SENT

is_field_valid = expected_fields_validator(data, update_user_request_body_model)
if not is_field_valid.get("is_field_valid"):
return is_field_valid.get("message"), HTTPStatus.BAD_REQUEST

is_not_valid = validate_update_profile_request_data(data)
if is_not_valid:
return is_not_valid, HTTPStatus.BAD_REQUEST

token = request.headers.environ["HTTP_AUTHORIZATION"]

result = put_request("/user", token, data)
return http_response_checker(result)



23 changes: 11 additions & 12 deletions app/api/validations/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@


def validate_user_registration_request_data(data):
# Verify if request body has required fields
if "name" not in data:
return messages.NAME_FIELD_IS_MISSING
if "username" not in data:
Expand All @@ -38,13 +37,13 @@ def validate_user_registration_request_data(data):
return messages.EMAIL_FIELD_IS_MISSING
if "terms_and_conditions_checked" not in data:
return messages.TERMS_AND_CONDITIONS_FIELD_IS_MISSING

name = data["name"]
username = data["username"]
password = data["password"]
email = data["email"]
terms_and_conditions_checked = data["terms_and_conditions_checked"]

if not (
isinstance(name, str)
and isinstance(username, str)
Expand Down Expand Up @@ -76,7 +75,6 @@ def validate_user_registration_request_data(data):
if not is_valid.get("is_valid"):
return is_valid.get("message")

# Verify business logic of request body
if not terms_and_conditions_checked:
return messages.TERMS_AND_CONDITIONS_ARE_NOT_CHECKED

Expand All @@ -93,7 +91,6 @@ def validate_user_registration_request_data(data):


def validate_resend_email_request_data(data):
# Verify if request body has required fields
if "email" not in data:
return messages.EMAIL_FIELD_IS_MISSING

Expand All @@ -105,11 +102,13 @@ def validate_resend_email_request_data(data):


def validate_update_profile_request_data(data):
# todo this does not check if non expected fields are being sent

if not data:
return messages.NO_DATA_FOR_UPDATING_PROFILE_WAS_SENT

if "name" not in data:
return messages.NAME_FIELD_IS_MISSING
if "username" not in data:
return messages.USERNAME_FIELD_IS_MISSING

username = data.get("username", None)
if username:
is_valid = validate_length(
Expand Down Expand Up @@ -159,13 +158,13 @@ def validate_update_profile_request_data(data):
if not is_valid.get("is_valid"):
return is_valid.get("message")

organization = data.get("organization", None)
if organization:
current_organization = data.get("current_organization", None)
if current_organization:
is_valid = validate_length(
len(get_stripped_string(organization)),
len(get_stripped_string(current_organization)),
0,
ORGANIZATION_MAX_LENGTH,
"organization",
"current_organization",
)
if not is_valid.get("is_valid"):
return is_valid.get("message")
Expand Down
2 changes: 1 addition & 1 deletion app/database/models/ms_schema/mentorship_relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class MentorshipRelationModel(db.Model):
# Specifying database table used for MentorshipRelationModel
__tablename__ = "mentorship_relations"
__table_args__ = {"schema": "public", "extend_existing": True}

id = db.Column(db.Integer, primary_key=True)


Expand Down
2 changes: 1 addition & 1 deletion app/database/models/ms_schema/task_comment.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class TaskCommentModel(db.Model):
# Specifying database table used for TaskCommentModel
__tablename__ = "tasks_comments"
__table_args__ = {"schema": "public", "extend_existing": True}

id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey("public.users.id"))
task_id = db.Column(db.Integer, db.ForeignKey("public.tasks_list.id"))
Expand Down
6 changes: 3 additions & 3 deletions app/database/models/ms_schema/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class UserModel(db.Model):
# Specifying database table used for UserModel
__tablename__ = "users"
__table_args__ = {"schema": "public", "extend_existing": True}

id = db.Column(db.Integer, primary_key=True)

# personal data
Expand Down Expand Up @@ -52,7 +52,7 @@ class UserModel(db.Model):
bio = db.Column(db.String(500))
location = db.Column(db.String(80))
occupation = db.Column(db.String(80))
organization = db.Column(db.String(80))
current_organization = db.Column(db.String(80))
slack_username = db.Column(db.String(80))
social_media_links = db.Column(db.String(500))
skills = db.Column(db.String(500))
Expand Down Expand Up @@ -121,7 +121,7 @@ def json(self):
"bio": self.bio,
"location": self.location,
"occupation": self.occupation,
"organization": self.organization,
"current_organization": self.organization,
"slack_username": self.slack_username,
"social_media_links": self.social_media_links,
"skills": self.skills,
Expand Down
3 changes: 2 additions & 1 deletion app/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@
"message": "Please verify your" " email before login."
}
NAME_USERNAME_AND_PASSWORD_NOT_IN_STRING_FORMAT = {
"message": "Name, username" " and password must be in" " string format."
"message": "Name, username," " or password must be in" " string format."
}
COMMENT_NOT_IN_STRING_FORMAT = {"message": "Comment must be in string format."}
TERMS_AND_CONDITIONS_ARE_NOT_CHECKED = {
Expand Down Expand Up @@ -290,3 +290,4 @@
"message": "An unexpected server error occurs while processing your request. Please try again later."
}

UNEXPECTED_INPUT = {"message": "Unexpected input is detected. Please check your input to make sure only approved fields are to be submitted."}
Loading