Skip to content

Commit

Permalink
Yiran li/feature/get unavailability (#178)
Browse files Browse the repository at this point in the history
## Describe your changes
Modified the read feature to filter and return only the records that
represent unavailability times in the future. I have conducted basic
testing on Postman to verify the functionality works as expected without
any apparent issues.
![PFRZ30RTMLX05}
FR(2B5D4](https://github.com/TechlauncherFireApp/backend/assets/51045255/0656d799-0363-4941-b729-797ddbce7b29)
![@GZ9WWX9Z 6S`FM``
K$BK1](https://github.com/TechlauncherFireApp/backend/assets/51045255/2b728267-f164-483c-8baa-d05a77c379a2)
For the security considerations, the userId is extracted from the URL,
and the get function is protected by the @requires_auth decorator, so I
think this setup ensures that users can only access their own
unavailability records. I'm not sure whether I have misunderstanding
about it.
## Issue ticket number and link

https://fireapp-emergiq-2024.atlassian.net/jira/software/projects/FE/boards/1?selectedIssue=FE-145
  • Loading branch information
emilymclean authored Mar 18, 2024
2 parents 997a2fa + c8a3625 commit 72159d4
Show file tree
Hide file tree
Showing 2 changed files with 165 additions and 84 deletions.
119 changes: 55 additions & 64 deletions controllers/v2/unavailability/api.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import json
import uuid

from flask import jsonify
from flask_restful import reqparse, Resource, marshal_with, inputs

from domain.entity import unavailability_time
from .response_models import volunteer_unavailability_time
from domain import session_scope, UserType
from repository.volunteer_unavailability_v2 import *
from repository.unavailability_repository import *
from domain import UserType
from repository.volunteer_unavailability_v2 import EventRepository
from services.jwk import requires_auth, is_user_or_has_role
from controllers.v2.v2_blueprint import v2_api

Expand All @@ -20,49 +14,53 @@


class SpecificVolunteerUnavailabilityV2(Resource):
event_repository: EventRepository

def __init__(self, event_repository: EventRepository = EventRepository()):
self.event_repository = event_repository

@requires_auth
@is_user_or_has_role(None, UserType.ROOT_ADMIN)
def put(self, user_id, event_id):
args = edit_parser.parse_args()
with session_scope() as session:
success = edit_event(session, user_id, event_id, **args)
if success is True:
return {"message": "Updated successfully"}, 200
elif success is False:
return {"message": "Event not found"}, 404
else:
return {"message": "Unexpected Error Occurred"}, 400
success = self.event_repository.edit_event(user_id, event_id, **args)
if success is True:
return {"message": "Updated successfully"}, 200
elif success is False:
return {"message": "Event not found"}, 404
else:
return {"message": "Unexpected Error Occurred"}, 400

@requires_auth
@is_user_or_has_role(None, UserType.ROOT_ADMIN)
def delete(self, user_id, event_id):
with session_scope() as session:
try:
success = remove_event(session, user_id, event_id)
if success:
# If the event is successfully removed, return HTTP 200 OK.
return {"message": "Unavailability event removed successfully."}, 200
else:
# If the event does not exist or could not be removed, return HTTP 404 Not Found.
return {"message": "Unavailability event not found."}, 404
except Exception as e:
# HTTP 500 Internal Server Error
return {"message": "Internal server error", "error": str(e)}, 500
try:
success = self.event_repository.remove_event(user_id, event_id)
if success:
# If the event is successfully removed, return HTTP 200 OK.
return {"message": "Unavailability event removed successfully."}, 200
else:
# If the event does not exist or could not be removed, return HTTP 404 Not Found.
return {"message": "Unavailability event not found."}, 404
except Exception as e:
# HTTP 500 Internal Server Error
return {"message": "Internal server error", "error": str(e)}, 500


class VolunteerUnavailabilityV2(Resource):

def __init__(self):
self.event_repository = EventRepository()

@requires_auth
@marshal_with(volunteer_unavailability_time)
@is_user_or_has_role(None, UserType.ROOT_ADMIN)
def get(self, user_id):
with session_scope() as session:
volunteer_unavailability_record = fetch_event(session, user_id)
if volunteer_unavailability_record is not None:
return volunteer_unavailability_record
else:
return jsonify({'userID': user_id, 'success': False}), 400
volunteer_unavailability_record = self.event_repository.get_event(user_id)
if volunteer_unavailability_record is not None:
return volunteer_unavailability_record
else:
return {"message": "No unavailability record found."}, 400

@requires_auth
@is_user_or_has_role(None, UserType.ROOT_ADMIN)
Expand All @@ -73,40 +71,33 @@ def post(self, user_id):
if args['start'] >= args['end']:
return {"message": "Start time must be earlier than end time"}, 400 # HTTP 400 Bad Request

with session_scope() as session:
# checks if new time frame overlaps with any existing in the database for specific userId
overlapping_events = session.query(UnavailabilityTime).filter(
UnavailabilityTime.userId == user_id,
UnavailabilityTime.start < args['end'],
UnavailabilityTime.end > args['start'],
UnavailabilityTime.periodicity == args['periodicity']
).all()
if overlapping_events:
overlapping_details = []
for event in overlapping_events:
overlapping_details.append({
"eventId": event.eventId})
return {"message": "Time frames overlap with existing events",
"overlapping_events": overlapping_details}, 400 # HTTP 400 Bad Request

eventId = create_event(
session,
user_id,
args['title'],
args['start'],
args['end'],
args['periodicity']
)
if eventId is not None:
return {"eventId": eventId}, 200 # HTTP 200 OK
else:
return {"description": "Failed to create event"}, 400 # HTTP 400 Bad Request
overlapping_events = self.event_repository.check_overlapping_events(user_id, args['start'], args['end'], args['periodicity'])
if overlapping_events:
overlapping_details = []
for event in overlapping_events:
overlapping_details.append({
"eventId": event.eventId})
return {"message": "Time frames overlap with existing events",
"overlapping_events": overlapping_details}, 400 # HTTP 400 Bad Request

eventId = self.event_repository.create_event(
user_id,
args['title'],
args['start'],
args['end'],
args['periodicity']
)
if eventId is not None:
return {"eventId": eventId}, 200 # HTTP 200 OK
else:
return {"description": "Failed to create event"}, 400 # HTTP 400 Bad Request
except Exception as e:
return {"description": "Internal server error", "error": str(e)}, 500 # HTTP 500 Internal Server Error



v2_api.add_resource(SpecificVolunteerUnavailabilityV2, '/v2/volunteers/',
'/v2/volunteers/<user_id>/unavailability/<event_id>')
'/v2/volunteers/<user_id>/unavailability/<event_id>')

v2_api.add_resource(VolunteerUnavailabilityV2, '/v2/volunteers/',
'/v2/volunteers/<user_id>/unavailability')
'/v2/volunteers/<user_id>/unavailability')
130 changes: 110 additions & 20 deletions repository/volunteer_unavailability_v2.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,115 @@
import logging

from repository.unavailability_repository import *
from flask import jsonify

from datetime import datetime

def edit_event(session, userId, eventId, title=None, start=None, end=None, periodicity=None):
try:
event = session.query(UnavailabilityTime).filter(UnavailabilityTime.eventId == eventId,
UnavailabilityTime.userId == userId).first()
if event is None:
from domain import UnavailabilityTime, session_scope


class EventRepository:
def __init__(self):
pass
def edit_event(self, userId, eventId, title=None, start=None, end=None, periodicity=None):
with session_scope() as session:
try:
event = session.query(UnavailabilityTime).filter(UnavailabilityTime.eventId == eventId,
UnavailabilityTime.userId == userId).first()
if event is None:
return False
if title is not None:
event.title = title
if start is not None:
event.start = start
if end is not None:
event.end = end
if end is not None:
event.periodicity = periodicity
session.commit()
return True
except Exception as e:
session.rollback()
logging.error(e)
return None

def get_event(self, userId):
"""
get all the non-availability events of the given user
:param session: session
:param userId: Integer, user id, who want to query the events
"""
now = datetime.now()
with session_scope() as session:
try:
# only show the unavailability time that is end in the future
events = session.query(UnavailabilityTime).filter(
UnavailabilityTime.userId == userId, UnavailabilityTime.status == 1, UnavailabilityTime.end > now).all()
if events:
event_records = []
for event in events:
# if the start time is earlier than now, then show from now to the end time
start_time = max(event.start, now)
event_record = {
"eventId": event.eventId,
"userId": event.userId,
"title": event.title,
"startTime": start_time.isoformat(),
"endTime": event.end.isoformat(),
"periodicity": event.periodicity
}
event_records.append(event_record)
return jsonify(event_records)
else:
return None
except Exception as e:
logging.error(e)
return None

# copy from repository.unavailability_repository.py
def create_event(self, userId, title, startTime, endTime, periodicity):
"""
Function to create an event
:param session: session
:param userId: Integer, user id
:param title: String, reason why unavailable
:param startTime: DateTime, from what time is unavailable
:param endTime: DateTime, to what time is unavailable
:param periodicity: Integer, Daily = 1, Weekly = 2, One-Off = 3
"""
event = UnavailabilityTime(userId=userId, title=title, start=startTime, end=endTime,
periodicity=periodicity)
with session_scope() as session:
session.add(event)
# session.expunge(question)
session.flush()
return event.eventId

# copy from repository.unavailability_repository.py
def remove_event(self, userId, eventId):
"""
Function to remove an event
:param session: session
:param userId: Integer, user id, who want to remove an event
:param eventId: Integer, event id want to remove
:return: True: remove successful
False: remove failed
"""
with session_scope() as session:
existing = session.query(UnavailabilityTime).filter(UnavailabilityTime.userId == userId,
UnavailabilityTime.eventId == eventId).first()
if existing is not None and existing.status is True:
existing.status = False
return True
return False
if title is not None:
event.title = title
if start is not None:
event.start = start
if end is not None:
event.end = end
if end is not None:
event.periodicity = periodicity
session.commit()
return True
except Exception as e:
session.rollback()
logging.error(e)
return None

# copy from post function in api.py written by Steven
def check_overlapping_events(self, userId, startTime, endTime, periodicity):
with session_scope() as session:
# checks if new time frame overlaps with any existing in the database for specific userId
overlapping_events = session.query(UnavailabilityTime).filter(
UnavailabilityTime.userId == userId,
UnavailabilityTime.start < endTime,
UnavailabilityTime.end > startTime,
UnavailabilityTime.periodicity == periodicity
).all()
return overlapping_events

0 comments on commit 72159d4

Please sign in to comment.