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: create GET/mentorship_relations API #168

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions app/api/bit_extension.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
api.namespaces.clear()

# Adding namespaces
from app.api.resources.mentorship_relation import mentorship_relation_ns as mentorship_relation_namespace
api.add_namespace(mentorship_relation_namespace, path="/")

from app.api.resources.users import users_ns as user_namespace
api.add_namespace(user_namespace, path="/")

Expand Down
121 changes: 121 additions & 0 deletions app/api/dao/mentorship_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
from datetime import datetime, timedelta
from typing import Dict
from http import HTTPStatus
from app import messages
from app.database.models.bit_schema.mentorship_relation_extension import MentorshipRelationExtensionModel
from app.database.models.bit_schema.user_extension import UserExtensionModel
from app.utils.decorator_utils import email_verification_required
from app.utils.enum_utils import MentorshipRelationState


class MentorshipRelationDAO:
"""Data Access Object for mentorship relation functionalities.

Provides various functions pertaining to mentorship.

Attributes:
MAXIMUM_MENTORSHIP_DURATION
MINIMUM_MENTORSHIP_DURATION
"""

MAXIMUM_MENTORSHIP_DURATION = timedelta(weeks=24) # 6 months = approximately 6*4
MINIMUM_MENTORSHIP_DURATION = timedelta(weeks=4)

def create_mentorship_relation(self, user_id: int, data: Dict[str, str]):
Copy link
Member

@mtreacy002 mtreacy002 Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to duplicate the DAO functionality already existed in Mentorship System Backend for creating mentorship_relation instance. You only need to send GET request to the MS Backend for this. On the BIT side you need to create the dao functionality related to Mentorship Relation Extension table that belongs to bitschema (hence, the name of the file also will reflect the content, mentorship_relation_extension.py - under the dao folder only). Although, the function you need to focus on this PR is only the get functionality (not create) since the create function will be dealt with in other issue (#161).

"""Creates a relationship between two users.

Establishes the mentor-mentee relationship.

Args:
user_id: ID of the user initiating this request. Has to be either the mentor or the mentee.
data: List containing the mentor_id, mentee_id, end_date_timestamp and notes.

Returns:
message: A message corresponding to the completed action; success if mentorship relationship is established, failure if otherwise.
"""
action_user_id = user_id
mentor_id = data["mentor_id"]
mentee_id = data["mentee_id"]
end_date_timestamp = data["end_date"]
notes = data["notes"]

# user_id has to match either mentee_id or mentor_id
is_valid_user_ids = action_user_id == mentor_id or action_user_id == mentee_id
if not is_valid_user_ids:
return messages.MATCH_EITHER_MENTOR_OR_MENTEE, HTTPStatus.BAD_REQUEST

# mentor_id has to be different from mentee_id
if mentor_id == mentee_id:
return messages.MENTOR_ID_SAME_AS_MENTEE_ID, HTTPStatus.BAD_REQUEST

try:
end_date_datetime = datetime.fromtimestamp(end_date_timestamp)
except ValueError:
return messages.INVALID_END_DATE, HTTPStatus.BAD_REQUEST

now_datetime = datetime.now()
if end_date_datetime < now_datetime:
return messages.END_TIME_BEFORE_PRESENT, HTTPStatus.BAD_REQUEST

# business logic constraints

max_relation_duration = end_date_datetime - now_datetime
if max_relation_duration > self.MAXIMUM_MENTORSHIP_DURATION:
return messages.MENTOR_TIME_GREATER_THAN_MAX_TIME, HTTPStatus.BAD_REQUEST

if max_relation_duration < self.MINIMUM_MENTORSHIP_DURATION:
return messages.MENTOR_TIME_LESS_THAN_MIN_TIME, HTTPStatus.BAD_REQUEST

# validate if mentor user exists
mentor_user = UserModel.find_by_id(mentor_id)
if mentor_user is None:
return messages.MENTOR_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND

# validate if mentor is available to mentor
if not mentor_user.available_to_mentor:
return messages.MENTOR_NOT_AVAILABLE_TO_MENTOR, HTTPStatus.BAD_REQUEST

# validate if mentee user exists
mentee_user = UserModel.find_by_id(mentee_id)
if mentee_user is None:
return messages.MENTEE_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND

# validate if mentee is wants to be mentored
if not mentee_user.need_mentoring:
return messages.MENTEE_NOT_AVAIL_TO_BE_MENTORED, HTTPStatus.BAD_REQUEST

# TODO add tests for this portion

all_mentor_relations = (
mentor_user.mentor_relations + mentor_user.mentee_relations
)
for relation in all_mentor_relations:
if relation.state == MentorshipRelationState.ACCEPTED:
return messages.MENTOR_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST

all_mentee_relations = (
mentee_user.mentor_relations + mentee_user.mentee_relations
)
for relation in all_mentee_relations:
if relation.state == MentorshipRelationState.ACCEPTED:
return messages.MENTEE_ALREADY_IN_A_RELATION, HTTPStatus.BAD_REQUEST

# All validations were checked

tasks_list = TasksListModel()
tasks_list.save_to_db()

mentorship_relation = MentorshipRelationModel(
action_user_id=action_user_id,
mentor_user=mentor_user,
mentee_user=mentee_user,
creation_date=datetime.now().timestamp(),
end_date=end_date_timestamp,
state=MentorshipRelationState.PENDING,
notes=notes,
tasks_list=tasks_list,
)

mentorship_relation.save_to_db()

return messages.MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY, HTTPStatus.CREATED
56 changes: 56 additions & 0 deletions app/api/models/mentorship_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from flask_restx import fields, Model

from app.utils.enum_utils import MentorshipRelationState


def add_models_to_namespace(api_namespace):
api_namespace.models[
mentorship_request_response_body.name
] = mentorship_request_response_body
api_namespace.models[relation_user_response_body.name] = relation_user_response_body

relation_user_response_body = Model(
"User",
{
"id": fields.Integer(required=True, description="User ID"),
"name": fields.String(required=True, description="User name"),
},
)

mentorship_request_response_body = Model(
"List mentorship relation request model",
{
"id": fields.Integer(required=True, description="Mentorship relation ID"),
"action_user_id": fields.Integer(
required=True, description="Mentorship relation requester user ID"
),
"sent_by_me": fields.Boolean(
required=True,
description="Mentorship relation sent by current user indication",
),
"mentor": fields.Nested(relation_user_response_body),
"mentee": fields.Nested(relation_user_response_body),
"creation_date": fields.Float(
required=True,
description="Mentorship relation creation date in UNIX timestamp format",
),
"accept_date": fields.Float(
required=True,
description="Mentorship relation acceptance date in UNIX timestamp format",
),
"start_date": fields.Float(
required=True,
description="Mentorship relation start date in UNIX timestamp format",
),
"end_date": fields.Float(
required=True,
description="Mentorship relation end date in UNIX timestamp format",
),
"state": fields.Integer(
required=True,
enum=MentorshipRelationState.values,
description="Mentorship relation state",
),
"notes": fields.String(required=True, description="Mentorship relation notes"),
},
)
78 changes: 78 additions & 0 deletions app/api/resources/mentorship_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import ast
import json
from flask import request
from flask_restx import Resource, Namespace, marshal
from flask_jwt_extended import jwt_required, get_jwt_identity
from http import HTTPStatus
from app import messages
from app.api.request_api_utils import (
post_request,
get_request,
put_request,
http_response_checker,
AUTH_COOKIE,
validate_token)
from app.api.resources.common import auth_header_parser
from app.api.dao.mentorship_relation import MentorshipRelationDAO
from app.api.dao.user_extension import UserExtensionDAO
from app.api.models.mentorship_relation import *
from app.utils.validation_utils import get_length_validation_error_message
from app.database.models.bit_schema.mentorship_relation_extension import MentorshipRelationExtensionModel
from app.utils.ms_constants import DEFAULT_PAGE, DEFAULT_USERS_PER_PAGE

mentorship_relation_ns = Namespace(
"Mentorship Relation",
description="Operations related to " "mentorship relations " "between users",
)
add_models_to_namespace(mentorship_relation_ns)

DAO = MentorshipRelationDAO()
UserExtensionDAO = UserExtensionDAO()


@mentorship_relation_ns.route("mentorship_relations")
class GetAllMyMentorshipRelation(Resource):
@classmethod
@jwt_required
@mentorship_relation_ns.doc("get_all_user_mentorship_relations")
@mentorship_relation_ns.expect(auth_header_parser)
@mentorship_relation_ns.param(
name="relation_state",
description="Mentorship relation state filter.",
_in="query",
)
@mentorship_relation_ns.response(
HTTPStatus.OK,
"Return all user's mentorship relations, filtered by the relation state, was successfully.",
model=mentorship_request_response_body,
)
@mentorship_relation_ns.response(
HTTPStatus.UNAUTHORIZED,
f"{messages.TOKEN_HAS_EXPIRED}\n"
f"{messages.TOKEN_IS_INVALID}\n"
f"{messages.AUTHORISATION_TOKEN_IS_MISSING}"
)
@mentorship_relation_ns.marshal_list_with(mentorship_request_response_body)
def get(cls):
"""
Lists all mentorship relations of current user.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you please give a more informative description on what the endpoint is about. You can use similar style on User's GET /users endpont below.
Screen Shot 2020-10-27 at 1 24 19 pm

Points to show:

  • the need of valid token
  • the type of input
  • what's being returned and what information are given.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thankyou for letting me i was just confused for what needs to show


Input:
1. Header: valid access token

Returns:
JSON array containing user's relations as objects.
"""

user_id = get_jwt_identity()
rel_state_param = request.args
rel_state_filter = None

if rel_state_param:
rel_state_filter = rel_state_param["relation_state"].upper()

response = DAO.list_mentorship_relations(
user_id=user_id, state=rel_state_filter
)

return response