Skip to content

Commit 72159d4

Browse files
authored
Yiran li/feature/get unavailability (#178)
## 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
2 parents 997a2fa + c8a3625 commit 72159d4

File tree

2 files changed

+165
-84
lines changed

2 files changed

+165
-84
lines changed

controllers/v2/unavailability/api.py

Lines changed: 55 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
import json
2-
import uuid
3-
4-
from flask import jsonify
51
from flask_restful import reqparse, Resource, marshal_with, inputs
62

7-
from domain.entity import unavailability_time
83
from .response_models import volunteer_unavailability_time
9-
from domain import session_scope, UserType
10-
from repository.volunteer_unavailability_v2 import *
11-
from repository.unavailability_repository import *
4+
from domain import UserType
5+
from repository.volunteer_unavailability_v2 import EventRepository
126
from services.jwk import requires_auth, is_user_or_has_role
137
from controllers.v2.v2_blueprint import v2_api
148

@@ -20,49 +14,53 @@
2014

2115

2216
class SpecificVolunteerUnavailabilityV2(Resource):
17+
event_repository: EventRepository
18+
19+
def __init__(self, event_repository: EventRepository = EventRepository()):
20+
self.event_repository = event_repository
2321

2422
@requires_auth
2523
@is_user_or_has_role(None, UserType.ROOT_ADMIN)
2624
def put(self, user_id, event_id):
2725
args = edit_parser.parse_args()
28-
with session_scope() as session:
29-
success = edit_event(session, user_id, event_id, **args)
30-
if success is True:
31-
return {"message": "Updated successfully"}, 200
32-
elif success is False:
33-
return {"message": "Event not found"}, 404
34-
else:
35-
return {"message": "Unexpected Error Occurred"}, 400
26+
success = self.event_repository.edit_event(user_id, event_id, **args)
27+
if success is True:
28+
return {"message": "Updated successfully"}, 200
29+
elif success is False:
30+
return {"message": "Event not found"}, 404
31+
else:
32+
return {"message": "Unexpected Error Occurred"}, 400
3633

3734
@requires_auth
3835
@is_user_or_has_role(None, UserType.ROOT_ADMIN)
3936
def delete(self, user_id, event_id):
40-
with session_scope() as session:
41-
try:
42-
success = remove_event(session, user_id, event_id)
43-
if success:
44-
# If the event is successfully removed, return HTTP 200 OK.
45-
return {"message": "Unavailability event removed successfully."}, 200
46-
else:
47-
# If the event does not exist or could not be removed, return HTTP 404 Not Found.
48-
return {"message": "Unavailability event not found."}, 404
49-
except Exception as e:
50-
# HTTP 500 Internal Server Error
51-
return {"message": "Internal server error", "error": str(e)}, 500
37+
try:
38+
success = self.event_repository.remove_event(user_id, event_id)
39+
if success:
40+
# If the event is successfully removed, return HTTP 200 OK.
41+
return {"message": "Unavailability event removed successfully."}, 200
42+
else:
43+
# If the event does not exist or could not be removed, return HTTP 404 Not Found.
44+
return {"message": "Unavailability event not found."}, 404
45+
except Exception as e:
46+
# HTTP 500 Internal Server Error
47+
return {"message": "Internal server error", "error": str(e)}, 500
5248

5349

5450
class VolunteerUnavailabilityV2(Resource):
5551

52+
def __init__(self):
53+
self.event_repository = EventRepository()
54+
5655
@requires_auth
5756
@marshal_with(volunteer_unavailability_time)
5857
@is_user_or_has_role(None, UserType.ROOT_ADMIN)
5958
def get(self, user_id):
60-
with session_scope() as session:
61-
volunteer_unavailability_record = fetch_event(session, user_id)
62-
if volunteer_unavailability_record is not None:
63-
return volunteer_unavailability_record
64-
else:
65-
return jsonify({'userID': user_id, 'success': False}), 400
59+
volunteer_unavailability_record = self.event_repository.get_event(user_id)
60+
if volunteer_unavailability_record is not None:
61+
return volunteer_unavailability_record
62+
else:
63+
return {"message": "No unavailability record found."}, 400
6664

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

76-
with session_scope() as session:
77-
# checks if new time frame overlaps with any existing in the database for specific userId
78-
overlapping_events = session.query(UnavailabilityTime).filter(
79-
UnavailabilityTime.userId == user_id,
80-
UnavailabilityTime.start < args['end'],
81-
UnavailabilityTime.end > args['start'],
82-
UnavailabilityTime.periodicity == args['periodicity']
83-
).all()
84-
if overlapping_events:
85-
overlapping_details = []
86-
for event in overlapping_events:
87-
overlapping_details.append({
88-
"eventId": event.eventId})
89-
return {"message": "Time frames overlap with existing events",
90-
"overlapping_events": overlapping_details}, 400 # HTTP 400 Bad Request
91-
92-
eventId = create_event(
93-
session,
94-
user_id,
95-
args['title'],
96-
args['start'],
97-
args['end'],
98-
args['periodicity']
99-
)
100-
if eventId is not None:
101-
return {"eventId": eventId}, 200 # HTTP 200 OK
102-
else:
103-
return {"description": "Failed to create event"}, 400 # HTTP 400 Bad Request
74+
overlapping_events = self.event_repository.check_overlapping_events(user_id, args['start'], args['end'], args['periodicity'])
75+
if overlapping_events:
76+
overlapping_details = []
77+
for event in overlapping_events:
78+
overlapping_details.append({
79+
"eventId": event.eventId})
80+
return {"message": "Time frames overlap with existing events",
81+
"overlapping_events": overlapping_details}, 400 # HTTP 400 Bad Request
82+
83+
eventId = self.event_repository.create_event(
84+
user_id,
85+
args['title'],
86+
args['start'],
87+
args['end'],
88+
args['periodicity']
89+
)
90+
if eventId is not None:
91+
return {"eventId": eventId}, 200 # HTTP 200 OK
92+
else:
93+
return {"description": "Failed to create event"}, 400 # HTTP 400 Bad Request
10494
except Exception as e:
10595
return {"description": "Internal server error", "error": str(e)}, 500 # HTTP 500 Internal Server Error
10696

10797

98+
10899
v2_api.add_resource(SpecificVolunteerUnavailabilityV2, '/v2/volunteers/',
109-
'/v2/volunteers/<user_id>/unavailability/<event_id>')
100+
'/v2/volunteers/<user_id>/unavailability/<event_id>')
110101

111102
v2_api.add_resource(VolunteerUnavailabilityV2, '/v2/volunteers/',
112-
'/v2/volunteers/<user_id>/unavailability')
103+
'/v2/volunteers/<user_id>/unavailability')
Lines changed: 110 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,115 @@
11
import logging
22

3-
from repository.unavailability_repository import *
3+
from flask import jsonify
44

5+
from datetime import datetime
56

6-
def edit_event(session, userId, eventId, title=None, start=None, end=None, periodicity=None):
7-
try:
8-
event = session.query(UnavailabilityTime).filter(UnavailabilityTime.eventId == eventId,
9-
UnavailabilityTime.userId == userId).first()
10-
if event is None:
7+
from domain import UnavailabilityTime, session_scope
8+
9+
10+
class EventRepository:
11+
def __init__(self):
12+
pass
13+
def edit_event(self, userId, eventId, title=None, start=None, end=None, periodicity=None):
14+
with session_scope() as session:
15+
try:
16+
event = session.query(UnavailabilityTime).filter(UnavailabilityTime.eventId == eventId,
17+
UnavailabilityTime.userId == userId).first()
18+
if event is None:
19+
return False
20+
if title is not None:
21+
event.title = title
22+
if start is not None:
23+
event.start = start
24+
if end is not None:
25+
event.end = end
26+
if end is not None:
27+
event.periodicity = periodicity
28+
session.commit()
29+
return True
30+
except Exception as e:
31+
session.rollback()
32+
logging.error(e)
33+
return None
34+
35+
def get_event(self, userId):
36+
"""
37+
get all the non-availability events of the given user
38+
:param session: session
39+
:param userId: Integer, user id, who want to query the events
40+
"""
41+
now = datetime.now()
42+
with session_scope() as session:
43+
try:
44+
# only show the unavailability time that is end in the future
45+
events = session.query(UnavailabilityTime).filter(
46+
UnavailabilityTime.userId == userId, UnavailabilityTime.status == 1, UnavailabilityTime.end > now).all()
47+
if events:
48+
event_records = []
49+
for event in events:
50+
# if the start time is earlier than now, then show from now to the end time
51+
start_time = max(event.start, now)
52+
event_record = {
53+
"eventId": event.eventId,
54+
"userId": event.userId,
55+
"title": event.title,
56+
"startTime": start_time.isoformat(),
57+
"endTime": event.end.isoformat(),
58+
"periodicity": event.periodicity
59+
}
60+
event_records.append(event_record)
61+
return jsonify(event_records)
62+
else:
63+
return None
64+
except Exception as e:
65+
logging.error(e)
66+
return None
67+
68+
# copy from repository.unavailability_repository.py
69+
def create_event(self, userId, title, startTime, endTime, periodicity):
70+
"""
71+
Function to create an event
72+
:param session: session
73+
:param userId: Integer, user id
74+
:param title: String, reason why unavailable
75+
:param startTime: DateTime, from what time is unavailable
76+
:param endTime: DateTime, to what time is unavailable
77+
:param periodicity: Integer, Daily = 1, Weekly = 2, One-Off = 3
78+
"""
79+
event = UnavailabilityTime(userId=userId, title=title, start=startTime, end=endTime,
80+
periodicity=periodicity)
81+
with session_scope() as session:
82+
session.add(event)
83+
# session.expunge(question)
84+
session.flush()
85+
return event.eventId
86+
87+
# copy from repository.unavailability_repository.py
88+
def remove_event(self, userId, eventId):
89+
"""
90+
Function to remove an event
91+
:param session: session
92+
:param userId: Integer, user id, who want to remove an event
93+
:param eventId: Integer, event id want to remove
94+
:return: True: remove successful
95+
False: remove failed
96+
"""
97+
with session_scope() as session:
98+
existing = session.query(UnavailabilityTime).filter(UnavailabilityTime.userId == userId,
99+
UnavailabilityTime.eventId == eventId).first()
100+
if existing is not None and existing.status is True:
101+
existing.status = False
102+
return True
11103
return False
12-
if title is not None:
13-
event.title = title
14-
if start is not None:
15-
event.start = start
16-
if end is not None:
17-
event.end = end
18-
if end is not None:
19-
event.periodicity = periodicity
20-
session.commit()
21-
return True
22-
except Exception as e:
23-
session.rollback()
24-
logging.error(e)
25-
return None
104+
105+
# copy from post function in api.py written by Steven
106+
def check_overlapping_events(self, userId, startTime, endTime, periodicity):
107+
with session_scope() as session:
108+
# checks if new time frame overlaps with any existing in the database for specific userId
109+
overlapping_events = session.query(UnavailabilityTime).filter(
110+
UnavailabilityTime.userId == userId,
111+
UnavailabilityTime.start < endTime,
112+
UnavailabilityTime.end > startTime,
113+
UnavailabilityTime.periodicity == periodicity
114+
).all()
115+
return overlapping_events

0 commit comments

Comments
 (0)