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: Add post organization/<int:organization_id>/programs/<int:program_id>/send_request endpoint #185

Open
wants to merge 4 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 @@ -19,3 +19,6 @@

from app.api.resources.organizations import organizations_ns as organization_namespace
api.add_namespace(organization_namespace, path="/")

from app.api.resources.mentorship_relation import mentorship_relation_ns as mentorship_relation_namespace
api.add_namespace(mentorship_relation_namespace, path="/")
37 changes: 37 additions & 0 deletions app/api/dao/mentorship_relation_extension.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import ast
from http import HTTPStatus
from flask import json
from app.database.models.bit_schema.mentorship_relation_extension import MentorshipRelationExtensionModel
from app import messages
from app.api.request_api_utils import AUTH_COOKIE
from app.utils.bitschema_utils import Timezone


class MentorshipRelationExtensionDAO:

"""Data Access Object for Users_Extension functionalities"""

@staticmethod
def createMentorshipRelationExtension(program_id, mentorship_relation_id , mentee_request_date):
"""Creates the extending mentorship relation between organization's program and the user which is logged in.

Arguments:
organization_id: The ID organization
program_id: The ID of program

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

try:
mentorship_relation_extension_object = MentorshipRelationExtensionModel(program_id,mentorship_relation_id)
mentorship_relation_extension_object.mentee_request_date = mentee_request_date
mentorship_relation_extension_object.save_to_db()
return messages.MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY, HTTPStatus.CREATED
except:
return messages.INTERNAL_SERVER_ERROR, HTTPStatus.BAD_REQUEST




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

from app.utils.enum_utils import MentorshipRelationState


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


send_mentorship_extension_request_body = Model(
"Send mentorship relation request to organziation model",
{
"mentee_id": fields.Integer(
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if we should use the same model as Mentorship relation request. We could create our own model for BIT by using the request body with action_user_id with value being organization representative_id.

required=True, description="Mentorship relation mentee ID"
),
"mentee_request_date": fields.Float(
required=True, description="Mentorship relation mentee_request_date in UNIX timestamp format"
),
"end_date": fields.Float(
required=True,
description="Mentorship relation end date in UNIX timestamp format",
),
"notes": fields.String(required=True, description="Mentorship relation notes"),

},
)
31 changes: 31 additions & 0 deletions app/api/request_api_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,37 @@ def post_request(request_string, data):
return response_message, response_code


def post_request_with_token(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.post(
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 get_user(token):
request_url = "/user"
return get_request(request_url, token, params=None)
Expand Down
154 changes: 154 additions & 0 deletions app/api/resources/mentorship_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import ast
import json
from http import HTTPStatus, cookies
from datetime import datetime, timedelta
from flask import request
from flask_restx import Resource, marshal, Namespace
from app import messages
from app.api.request_api_utils import (
post_request,
post_request_with_token,
get_request,
put_request,
http_response_checker,
AUTH_COOKIE,
validate_token)
# Common Resources
from app.api.resources.common import auth_header_parser
# Validations
from app.api.validations.user import *
from app.api.validations.task_comment import (
validate_task_comment_request_data,
COMMENT_MAX_LENGTH,
)
from app.api.validations.mentorship_relation import org_mentee_req_body_is_valid_data
from app.utils.validation_utils import get_length_validation_error_message,expected_fields_validator
from app.utils.ms_constants import DEFAULT_PAGE, DEFAULT_USERS_PER_PAGE
# Namespace Models
from app.api.models.mentorship_relation import *
# Databases Models
from app.database.models.bit_schema.user_extension import UserExtensionModel
from app.database.models.ms_schema.mentorship_relation import MentorshipRelationModel
from app.database.models.bit_schema.organization import OrganizationModel
from app.database.models.bit_schema.program import ProgramModel
# DAOs
from app.api.dao.user_extension import UserExtensionDAO
from app.api.dao.personal_background import PersonalBackgroundDAO
from app.api.dao.mentorship_relation_extension import MentorshipRelationExtensionDAO
from app.api.dao.organization import OrganizationDAO
from app.api.dao.program import ProgramDAO

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

mentorshipRelationExtensionDAO = MentorshipRelationExtensionDAO()
userExtensionDAO = UserExtensionDAO()
OrganizationDAO = OrganizationDAO()
ProgramDAO = ProgramDAO()

@mentorship_relation_ns.route("organizations/<int:organization_id>/programs/<int:program_id>/send_request")
class ApplyForProgram(Resource):
@classmethod
@mentorship_relation_ns.doc("send_request")
@mentorship_relation_ns.expect(auth_header_parser, send_mentorship_extension_request_body)
@mentorship_relation_ns.response(
HTTPStatus.CREATED, f"{messages.MENTORSHIP_RELATION_WAS_SENT_SUCCESSFULLY}"
)
@mentorship_relation_ns.response(
HTTPStatus.BAD_REQUEST,
f"{messages.NO_DATA_WAS_SENT}\n"
f"{messages.MATCH_EITHER_MENTOR_OR_MENTEE}\n"
f"{messages.MENTOR_ID_SAME_AS_MENTEE_ID}\n"
f"{messages.END_TIME_BEFORE_PRESENT}\n"
f"{messages.MENTOR_TIME_GREATER_THAN_MAX_TIME}\n"
f"{messages.MENTOR_TIME_LESS_THAN_MIN_TIME}\n"
f"{messages.MENTOR_NOT_AVAILABLE_TO_MENTOR}\n"
f"{messages.MENTEE_NOT_AVAIL_TO_BE_MENTORED}\n"
f"{messages.MENTOR_ALREADY_IN_A_RELATION}\n"
f"{messages.MENTEE_ALREADY_IN_A_RELATION}\n"
f"{messages.MENTOR_ID_FIELD_IS_MISSING}\n"
f"{messages.MENTEE_ID_FIELD_IS_MISSING}\n"
f"{messages.NOTES_FIELD_IS_MISSING}\n"
f"{messages.ORGANIZATION_DOES_NOT_EXIST}\n"
f"{messages.PROGRAM_DOES_NOT_EXIST}"
)
@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.response(
HTTPStatus.NOT_FOUND,
f"{messages.MENTOR_DOES_NOT_EXIST}\n"
f"{messages.MENTEE_DOES_NOT_EXIST}"
)
def post(cls,organization_id,program_id):
"""
Creates a new mentorship relation request.

Also, sends an email notification to the recipient about new relation request.

Input:
1. Header: valid access token
2. Body: A dict containing
- mentor_request_date,end_date: UNIX timestamp
- notes: description of relation request

Returns:
Success or failure message. A mentorship request is send to the other
person whose ID is mentioned. The relation appears at /pending endpoint.
"""

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

if not is_wrong_token:
try:
user_json = (AUTH_COOKIE["user"].value)
user = ast.literal_eval(user_json)
data = request.json

if not data:
return messages.NO_DATA_WAS_SENT, HTTPStatus.BAD_REQUEST

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

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

# Checking whether organization exists
organization = OrganizationModel.query.filter_by(id=organization_id).first()
if not organization:
return messages.ORGANIZATION_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND

# Checking whether program exists
program = ProgramModel.find_by_id(program_id)
if not program or (program.organization_id != organization_id):
return messages.PROGRAM_DOES_NOT_EXIST, HTTPStatus.NOT_FOUND

mentor_id = organization.rep_id
mentee_id = data['mentee_id']

mentorship_relation_data={}
mentorship_relation_data['mentee_id'] = mentee_id
mentorship_relation_data['mentor_id'] = int(mentor_id)
mentorship_relation_data['end_date'] = data['end_date']
mentorship_relation_data['notes'] = data['notes']

response = http_response_checker(post_request_with_token("/mentorship_relation/send_request",token, mentorship_relation_data))
Copy link
Member

Choose a reason for hiding this comment

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

I noticed that you used the internal call here, that's good.

if response.status_code == 201:
mentorshipRelationId = MentorshipRelationModel.query.filter_by(mentor_id=mentor_id).filter_by(mentee_id=mentee_id).first().id
return MentorshipRelationExtensionDAO.createMentorshipRelationExtension(program_id, mentorshipRelationId ,data['mentee_request_date'])
else:
return response.message, HTTPStatus.BAD_REQUEST
except ValueError as e:
return e, HTTPStatus.BAD_REQUEST

return is_wrong_token
14 changes: 14 additions & 0 deletions app/api/validations/mentorship_relation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from app import messages

def org_mentee_req_body_is_valid_data(data):

# Verify if request body has required fields
if "mentee_id" not in data:
return messages.MENTEE_ID_FIELD_IS_MISSING
if "end_date" not in data:
return messages.END_DATE_FIELD_IS_MISSING
if "notes" not in data:
return messages.NOTES_FIELD_IS_MISSING
if "mentee_request_date" not in data:
return messages.MENTEE_REQUEST_DATE_FIELD_IS_MISSING
return {}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ def find_by_id(cls, _id) -> "MentorshipRelationExtensionModel":
_id: The id of a mentorship_relations_extension.
"""
return cls.query.filter_by(id=_id).first()

@classmethod
def find_by_program_id(cls, _id) -> "MentorshipRelationExtensionModel":

"""Returns the mentorship_relations_extension that has the passed program id.
Args:
_id: The id of a program.
"""
return cls.query.filter_by(program_id=_id).first()

def save_to_db(self) -> None:
"""Saves the model to the database."""
Expand Down
2 changes: 1 addition & 1 deletion app/database/models/bit_schema/program.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def json(self):
def __repr__(self):
"""Returns the program name, creation/start/end date and organization id."""
return (
f"Program id is {self.program.id}\n"
f"Program id is {self.id}\n"
f"Program name is {self.program_name}.\n"
f"Organization's id is {self.organization_id}.\n"
f"Program start date is {self.start_date}\n"
Expand Down
4 changes: 4 additions & 0 deletions app/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
MENTOR_ID_FIELD_IS_MISSING = {"message": "Mentor ID field is missing."}
MENTEE_ID_FIELD_IS_MISSING = {"message": "Mentee ID field is missing."}
END_DATE_FIELD_IS_MISSING = {"message": "End date field is missing."}
MENTEE_REQUEST_DATE_FIELD_IS_MISSING = {"message": "Mentee request date field is missing."}
NOTES_FIELD_IS_MISSING = {"message": "Notes field is missing."}
USERNAME_FIELD_IS_MISSING = {"message": "The field username is missing."}
PASSWORD_FIELD_IS_MISSING = {"message": "Password field is missing."}
Expand Down Expand Up @@ -137,6 +138,9 @@
NO_DATA_FOR_UPDATING_PROFILE_WAS_SENT = {
"message": "No data for updating profile was sent."
}
NO_DATA_WAS_SENT = {
"message": "No data was sent."
}
ADDITIONAL_INFORMATION_DOES_NOT_EXIST = {
"message": "No additional information found with your data. Please provide them now."
}
Expand Down
Empty file.
Loading