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#80 PUT /user/additional_info api endpoints and test cases #81

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
45 changes: 42 additions & 3 deletions app/api/dao/user_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ def create_user_additional_info(data):
"""

try:
user_id = AUTH_COOKIE["user_id"].value
except KeyError:
return messages.USER_ID_IS_NOT_RETRIEVED, HTTPStatus.FORBIDDEN
user_id = int(AUTH_COOKIE["user_id"].value)
except ValueError:
return messages.USER_ID_IS_NOT_RETRIEVED, HTTPStatus.FORBIDDEN

timezone_value = data["timezone"]

Expand Down Expand Up @@ -73,3 +73,42 @@ def get_user_additional_data_info(user_id):
"mobile": result.additional_info["mobile"],
"personal_website": result.additional_info["personal_website"]
}
return

@staticmethod
def update_user_additional_info(data):
"""Updates a user_extension instance.

Arguments:
data: A list containing user's id, boolean value of whether or not
the user is representing an organization, as well as their timezone

Returns:
A dictionary containing "message" which indicates whether or not the user_exension was created successfully and "code" for the HTTP response code.
"""

try:
user_id = int(AUTH_COOKIE["user_id"].value)
except ValueError:
return messages.USER_ID_IS_NOT_RETRIEVED, HTTPStatus.FORBIDDEN

timezone_value = data["timezone"]
timezone = Timezone(timezone_value).name
additional_info_data = {}

existing_additional_info = UserExtensionModel.find_by_user_id(user_id)
if not existing_additional_info:
return messages.ADDITIONAL_INFORMATION_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND
try:
existing_additional_info.is_organization_rep = data["is_organization_rep"]
existing_additional_info.timezone = timezone
additional_info_data["phone"] = data["phone"]
additional_info_data["mobile"] = data["mobile"]
additional_info_data["personal_website"] = data["personal_website"]
except KeyError as e:
return e, HTTPStatus.BAD_REQUEST

existing_additional_info.additional_info = additional_info_data
existing_additional_info.save_to_db()

return messages.ADDITIONAL_INFO_SUCCESSFULLY_UPDATED, HTTPStatus.OK
3 changes: 2 additions & 1 deletion app/api/request_api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def post_request(request_string, data):
access_expiry_cookie = response_message.get("access_expiry")
AUTH_COOKIE["Authorization"] = f"Bearer {access_token_cookie}"
AUTH_COOKIE["Authorization"]["expires"] = access_expiry_cookie
AUTH_COOKIE["user_id"] = None
response_message = {"access_token": response_message.get("access_token"), "access_expiry": response_message.get("access_expiry")}

logging.fatal(f"{response_message}")
Expand Down Expand Up @@ -106,7 +107,7 @@ def put_request(request_string, token, data):


def validate_token(token):
if not token or not AUTH_COOKIE:
if not token:
return messages.AUTHORISATION_TOKEN_IS_MISSING, HTTPStatus.UNAUTHORIZED
if AUTH_COOKIE:
if token != AUTH_COOKIE["Authorization"].value:
Expand Down
83 changes: 67 additions & 16 deletions app/api/resources/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,13 @@ def put(cls):
return http_response_checker(put_request("/user", token, data))


@users_ns.doc(
responses={
HTTPStatus.INTERNAL_SERVER_ERROR: f"{messages.INTERNAL_SERVER_ERROR['message']}"
}
)
@users_ns.response(
HTTPStatus.UNAUTHORIZED,
HTTPStatus.UNAUTHORIZED,
f"{messages.TOKEN_HAS_EXPIRED}\n"
f"{messages.TOKEN_IS_INVALID}\n"
f"{messages.AUTHORISATION_TOKEN_IS_MISSING}"
Expand All @@ -207,8 +212,8 @@ def get(cls):
Returns additional information of current user

A user with valid access token can use this endpoint to view their additional information details.
The endpoint doesn't take any other input. But the user must get their personal details first
before they can send this request for getting the additional information.
The endpoint doesn't take any other input. This request only accessible once user retrieve their user_id
by sending GET /user/personal_details.
"""

token = request.headers.environ["HTTP_AUTHORIZATION"]
Expand All @@ -217,24 +222,19 @@ def get(cls):

if not is_wrong_token:
try:
user_id = AUTH_COOKIE["user_id"].value
except KeyError:
return messages.USER_ID_IS_NOT_RETRIEVED, HTTPStatus.FORBIDDEN

user_id = int(AUTH_COOKIE["user_id"].value)
except ValueError:
return messages.USER_ID_IS_NOT_RETRIEVED, HTTPStatus.FORBIDDEN
result = UserExtensionDAO.get_user_additional_data_info(user_id)
if not result:
return messages.ADDITIONAL_INFORMATION_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND
return result

return is_wrong_token


@classmethod
@users_ns.doc("create_user_additional_info")
@users_ns.doc(
responses={
HTTPStatus.INTERNAL_SERVER_ERROR: f"{messages.INTERNAL_SERVER_ERROR['message']}"
}
)
@users_ns.response(
HTTPStatus.CREATED, f"{messages.ADDITIONAL_INFO_SUCCESSFULLY_CREATED}"
)
Expand All @@ -258,10 +258,9 @@ def post(cls):

A user with valid access token can use this endpoint to add additional information to their own data.
The endpoint takes any of the given parameters (is_organization_rep (true or false value), timezone
(with value as per Timezone Enum Name) and additional_info (dictionary of phone, mobile and personal_website)).
The response contains a success or error message. This request only accessible once user confirm
their additional information have not already exist in the data through sending GET request for
additional information.
(with value as per Timezone Enum Value) and additional_info (dictionary of phone, mobile and personal_website)).
The response contains a success or error message. This request only accessible once user retrieve their user_id
by sending GET /user/personal_details.
"""

token = request.headers.environ["HTTP_AUTHORIZATION"]
Expand All @@ -283,4 +282,56 @@ def post(cls):
return UserExtensionDAO.create_user_additional_info(data)

return is_wrong_token


@classmethod
@users_ns.doc("update_user_additional_info")
@users_ns.response(
HTTPStatus.CREATED, f"{messages.ADDITIONAL_INFO_SUCCESSFULLY_CREATED}"
)
@users_ns.response(
HTTPStatus.BAD_REQUEST,
f"{messages.USER_ID_IS_NOT_VALID}\n"
f"{messages.IS_ORGANIZATION_REP_FIELD_IS_MISSING}\n"
f"{messages.TIMEZONE_FIELD_IS_MISSING}"
f"{messages.UNEXPECTED_INPUT}"
)
@users_ns.response(
HTTPStatus.FORBIDDEN, f"{messages.USER_ID_IS_NOT_RETRIEVED}"
)
@users_ns.response(
HTTPStatus.INTERNAL_SERVER_ERROR, f"{messages.INTERNAL_SERVER_ERROR}"
)
@users_ns.expect(auth_header_parser, user_extension_request_body_model, validate=True)
def put(cls):
"""
Updates user additional information

A user with valid access token can use this endpoint to update additional information to their own data.
The endpoint takes any of the given parameters (is_organization_rep (true or false value), timezone
(with value as per Timezone Enum Value) and additional_info (dictionary of phone, mobile and personal_website)).
The response contains a success or error message. This request only accessible once user retrieve their user_id
by sending GET /user/personal_details.
"""

token = request.headers.environ["HTTP_AUTHORIZATION"]
is_wrong_token = validate_token(token)

if not is_wrong_token:
data = request.json
if not data:
return messages.NO_DATA_FOR_UPDATING_PROFILE_WAS_SENT

is_field_valid = expected_fields_validator(data, user_extension_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_additional_info_request(data)
if is_not_valid:
return is_not_valid, HTTPStatus.BAD_REQUEST

return UserExtensionDAO.update_user_additional_info(data);

return is_wrong_token


6 changes: 6 additions & 0 deletions app/api/validations/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
validate_length,
get_stripped_string,
)
from app.utils.bitschema_utils import Timezone

# Field character limit

Expand Down Expand Up @@ -249,9 +250,14 @@ def validate_update_additional_info_request(data):
if "timezone" not in data:
return messages.TIMEZONE_FIELD_IS_MISSING

timezone_value = data.get("timezone")
phone = data.get("phone", None)
mobile = data.get("mobile", None)

try:
timezone = Timezone(timezone_value).name
except ValueError:
return messages.TIMEZONE_INPUT_IS_INVALID
if phone:
if not is_phone_valid(phone):
return messages.PHONE_OR_MOBILE_IS_NOT_IN_NUMBER_FORMAT
Expand Down
7 changes: 7 additions & 0 deletions app/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
WEBSITE_URL_IS_INVALID = {
"message": "The website url is not in valid format. It must have ""http*"
}
TIMEZONE_INPUT_IS_INVALID = {
"messages": "The Timezone input is not within the approved list"
}

# Not found
MENTORSHIP_RELATION_REQUEST_DOES_NOT_EXIST = {
Expand All @@ -38,6 +41,7 @@
"message": "Task comment with given task id does not exist."
}


# Missing fields
MENTOR_ID_FIELD_IS_MISSING = {"message": "Mentor ID field is missing."}
MENTEE_ID_FIELD_IS_MISSING = {"message": "Mentee ID field is missing."}
Expand Down Expand Up @@ -115,6 +119,9 @@
USER_EXTENSION_DATA_HAS_MISSING_FIELD = {
"message": "Additional information is missing one of its field."
}
ADDITIONAL_INFO_SUCCESSFULLY_UPDATED = {
"message": "Your additional information is successfully updated."
}


# Relation constraints
Expand Down
3 changes: 0 additions & 3 deletions app/utils/bitschema_utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from enum import Enum, unique

# from sqlalchemy import Enum
# from sqlalchemy.dialects.postgresql import ENUM


@unique
class ProgramStatus(Enum):
Expand Down
Loading