From 6b806d1d11fbc42d2b8a53a316e0e8c6c9c7d284 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 14 Jul 2024 12:43:57 -0400 Subject: [PATCH 01/34] refactor: add use_mock parameter to MongoModule constructor --- chalicelib/modules/mongo.py | 13 ++--- chalicelib/services/EventService.py | 74 ++++++++++++++-------------- tests/services/test_event_service.py | 30 +++++++++++ 3 files changed, 74 insertions(+), 43 deletions(-) create mode 100644 tests/services/test_event_service.py diff --git a/chalicelib/modules/mongo.py b/chalicelib/modules/mongo.py index a3783cb..c288a13 100644 --- a/chalicelib/modules/mongo.py +++ b/chalicelib/modules/mongo.py @@ -10,6 +10,7 @@ class MongoModule: def __init__(self, use_mock=False): """Establishes connection to MongoDB server""" + # if use_mock is true -> use monogmock to execute tests with fake db self.use_mock = use_mock if use_mock: self.mongo_client = mongomock.MongoClient() @@ -28,17 +29,17 @@ def __init__(self, use_mock=False): self.mongo_client = MongoClient(self.uri) def add_env_suffix(func): - def wrapper(self, collection_name: str, *args, **kwargs): + def wrapper(self, collection: str, *args, **kwargs): # users collection is dependent on vault so suffix should not be appended - if collection_name == "users": - return func(self, collection_name, *args, **kwargs) + if collection == "users": + return func(self, collection, *args, **kwargs) if self.is_prod: - collection_name += "-prod" + collection += "-prod" else: - collection_name += "-dev" + collection += "-dev" - return func(self, collection_name, *args, **kwargs) + return func(self, collection, *args, **kwargs) return wrapper diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventService.py index b3701ca..5b233d0 100644 --- a/chalicelib/services/EventService.py +++ b/chalicelib/services/EventService.py @@ -5,8 +5,6 @@ import datetime from chalicelib.modules.google_sheets import GoogleSheetsModule from chalicelib.modules.ses import ses, SesDestination -from typing import Optional - class EventService: class BSONEncoder(json.JSONEncoder): @@ -19,17 +17,19 @@ def default(self, o): return str(o) return super().default(o) - def __init__(self): + def __init__(self, mongo_module=mongo_module): + self.mongo_module = mongo_module self.collection_prefix = "events-" def create_timeframe(self, timeframe_data: dict): timeframe_data["dateCreated"] = datetime.datetime.now() - mongo_module.insert_document( - f"{self.collection_prefix}timeframe", timeframe_data + self.mongo_module.insert_document( + collection=f"{self.collection_prefix}timeframe", data=timeframe_data ) + return {"msg": True} def get_timeframe(self, timeframe_id: str): - timeframe = mongo_module.get_document_by_id( + timeframe = self.mongo_module.get_document_by_id( f"{self.collection_prefix}timeframe", timeframe_id ) @@ -37,7 +37,7 @@ def get_timeframe(self, timeframe_id: str): def get_all_timeframes(self): """Retrieve all timeframes from the database.""" - timeframes = mongo_module.get_all_data_from_collection( + timeframes = self.mongo_module.get_all_data_from_collection( f"{self.collection_prefix}timeframe" ) @@ -45,7 +45,7 @@ def get_all_timeframes(self): def delete_timeframe(self, timeframe_id: str): # Check if timeframe exists and if it doesn't return errors - timeframe = mongo_module.get_document_by_id( + timeframe = self.mongo_module.get_document_by_id( f"{self.collection_prefix}timeframe", timeframe_id ) @@ -57,12 +57,12 @@ def delete_timeframe(self, timeframe_id: str): # Delete all the events in the timeframe for event_id in event_ids: - mongo_module.delete_document_by_id( + self.mongo_module.delete_document_by_id( f"{self.collection_prefix}event", event_id ) # If timeframe exists, delete the timeframe document - mongo_module.delete_document_by_id( + self.mongo_module.delete_document_by_id( f"{self.collection_prefix}timeframe", timeframe_id ) @@ -72,7 +72,7 @@ def create_event(self, timeframe_id: str, event_data: dict): event_data["usersAttended"] = [] # Get Google Spreadsheet ID from timeframe - timeframe_doc = mongo_module.get_document_by_id( + timeframe_doc = self.mongo_module.get_document_by_id( f"{self.collection_prefix}timeframe", timeframe_id ) spreadsheet_id = timeframe_doc["spreadsheetId"] @@ -86,14 +86,14 @@ def create_event(self, timeframe_id: str, event_data: dict): event_data["spreadsheetCol"] = col # Insert the event in event collection - event_id = mongo_module.insert_document( + event_id = self.mongo_module.insert_document( f"{self.collection_prefix}event", event_data ) event_data["eventId"] = str(event_id) # Insert child event in timeframe collection - mongo_module.update_document( + self.mongo_module.update_document( f"{self.collection_prefix}timeframe", timeframe_id, {"$push": {"events": event_data}}, @@ -102,7 +102,7 @@ def create_event(self, timeframe_id: str, event_data: dict): return json.dumps(event_data, cls=self.BSONEncoder) def get_event(self, event_id: str): - event = mongo_module.get_document_by_id( + event = self.mongo_module.get_document_by_id( f"{self.collection_prefix}event", event_id ) @@ -119,13 +119,13 @@ def checkin(self, event_id: str, user: dict) -> dict: dict -- Dictionary containing status and message. """ user_id, user_email = user["id"], user["email"] - member = mongo_module.get_document_by_id(f"users", user_id) + member = self.mongo_module.get_document_by_id(f"users", user_id) if member is None: raise NotFoundError(f"User with ID {user_id} does not exist.") user_name = member["name"] - event = mongo_module.get_document_by_id( + event = self.mongo_module.get_document_by_id( f"{self.collection_prefix}event", event_id ) @@ -139,7 +139,7 @@ def checkin(self, event_id: str, user: dict) -> dict: } # Get timeframe document to get Google Sheets info - timeframe = mongo_module.get_document_by_id( + timeframe = self.mongo_module.get_document_by_id( f"{self.collection_prefix}timeframe", event["timeframeId"] ) @@ -173,7 +173,7 @@ def checkin(self, event_id: str, user: dict) -> dict: ) # Update event collection with checkin data - mongo_module.update_document( + self.mongo_module.update_document( f"{self.collection_prefix}event", event_id, {"$push": {"usersAttended": checkin_data}}, @@ -207,7 +207,7 @@ def checkin(self, event_id: str, user: dict) -> dict: def delete(self, event_id: str): # Check if event exists and if it doesn't return errors - event = mongo_module.get_document_by_id( + event = self.mongo_module.get_document_by_id( f"{self.collection_prefix}event", event_id ) @@ -218,17 +218,17 @@ def delete(self, event_id: str): timeframe_id = event["timeframeId"] # Remove event from timeframe - mongo_module.update_document( + self.mongo_module.update_document( f"{self.collection_prefix}timeframe", timeframe_id, {"$pull": {"events": {"_id": ObjectId(event_id)}}}, ) # Delete the event document - mongo_module.delete_document_by_id(f"{self.collection_prefix}event", event_id) + self.mongo_module.delete_document_by_id(f"{self.collection_prefix}event", event_id) def get_timeframe_sheets(self, timeframe_id: str): - timeframe = mongo_module.get_document_by_id( + timeframe = self.mongo_module.get_document_by_id( f"{self.collection_prefix}timeframe", timeframe_id ) @@ -240,7 +240,7 @@ def get_timeframe_sheets(self, timeframe_id: str): return [sheet["title"] for sheet in sheets] def get_rush_categories_and_events(self): - rush_categories = mongo_module.get_all_data_from_collection( + rush_categories = self.mongo_module.get_all_data_from_collection( f"{self.collection_prefix}rush" ) @@ -249,7 +249,7 @@ def get_rush_categories_and_events(self): def create_rush_category(self, data: dict): data["dateCreated"] = datetime.datetime.now() data["events"] = [] - mongo_module.insert_document(f"{self.collection_prefix}rush", data) + self.mongo_module.insert_document(f"{self.collection_prefix}rush", data) return def create_rush_event(self, data: dict): @@ -262,14 +262,14 @@ def create_rush_event(self, data: dict): # Add event to its own collection data["attendees"] = [] data["numAttendees"] = 0 - event_id = mongo_module.insert_document( + event_id = self.mongo_module.insert_document( f"{self.collection_prefix}rush-event", data ) data_copy["eventId"] = str(event_id) # Add event to rush category - mongo_module.update_document( + self.mongo_module.update_document( f"{self.collection_prefix}rush", data["categoryId"], {"$push": {"events": data_copy}}, @@ -285,7 +285,7 @@ def modify_rush_event(self, data: dict): eventId = data["eventId"] # Check if event exists in the rush-event collection - event = mongo_module.get_document_by_id( + event = self.mongo_module.get_document_by_id( f"{self.collection_prefix}rush-event", eventId ) @@ -312,7 +312,7 @@ def modify_rush_event(self, data: dict): ] # Modify the event in its category (rush collection) - mongo_module.update_document( + self.mongo_module.update_document( f"{self.collection_prefix}rush", event_category_id, update_query, @@ -320,7 +320,7 @@ def modify_rush_event(self, data: dict): ) # Modify actual event document (rush-event collection) - mongo_module.update_document( + self.mongo_module.update_document( f"{self.collection_prefix}rush-event", eventId, {"$set": data}, @@ -351,7 +351,7 @@ def modify_rush_settings(self, data: dict): collection = f"{self.collection_prefix}rush" # Set all defaultRushCategory fields to false - mongo_module.update_many_documents( + self.mongo_module.update_many_documents( collection, {}, {"$set": {"defaultRushCategory": False}} @@ -362,7 +362,7 @@ def modify_rush_settings(self, data: dict): return # Update the specified document to set its defaultRushCategory to true - result = mongo_module.update_document_by_id( + result = self.mongo_module.update_document_by_id( collection, default_rush_category_id, {"defaultRushCategory": True} @@ -374,7 +374,7 @@ def modify_rush_settings(self, data: dict): return def get_rush_event(self, event_id: str, hide_attendees: bool = True): - event = mongo_module.get_document_by_id( + event = self.mongo_module.get_document_by_id( f"{self.collection_prefix}rush-event", event_id ) @@ -387,7 +387,7 @@ def get_rush_event(self, event_id: str, hide_attendees: bool = True): return json.dumps(event, cls=self.BSONEncoder) def checkin_rush(self, event_id: str, user_data: dict): - event = mongo_module.get_document_by_id( + event = self.mongo_module.get_document_by_id( f"{self.collection_prefix}rush-event", event_id ) @@ -404,7 +404,7 @@ def checkin_rush(self, event_id: str, user_data: dict): event["attendees"].append(user_data) event["numAttendees"] += 1 - mongo_module.update_document( + self.mongo_module.update_document( f"{self.collection_prefix}rush-event", event_id, {"$set": event}, @@ -428,7 +428,7 @@ def delete_rush_event(self, event_id: str): """ try: # Check if event exists in the rush-event collection - event = mongo_module.get_document_by_id( + event = self.mongo_module.get_document_by_id( f"{self.collection_prefix}rush-event", event_id ) @@ -438,14 +438,14 @@ def delete_rush_event(self, event_id: str): event_category_id = event["categoryId"] # Delete the event from its category - mongo_module.update_document( + self.mongo_module.update_document( f"{self.collection_prefix}rush", event_category_id, {"$pull": {"events": {"eventId": event_id}}}, ) # Delete event data from the rush-event collection - mongo_module.delete_document_by_id( + self.mongo_module.delete_document_by_id( f"{self.collection_prefix}rush-event", event_id ) return diff --git a/tests/services/test_event_service.py b/tests/services/test_event_service.py new file mode 100644 index 0000000..3d67db8 --- /dev/null +++ b/tests/services/test_event_service.py @@ -0,0 +1,30 @@ +import pytest +from unittest.mock import MagicMock, patch +from chalicelib.services.EventService import EventService +import datetime + + +@pytest.fixture +def mock_mongo_module(): + with patch("chalicelib.modules.mongo.MongoModule", autospec=True) as MockMongoModule: + mock_instance = MockMongoModule(use_mock=True) + yield mock_instance + +@pytest.fixture +def event_service(mock_mongo_module): + return EventService(mock_mongo_module) + + +def test_insert_document(event_service, mock_mongo_module): + CREATE_TIMEFRAME = { "name": "testTimeframeName", "spreadsheetId": "testSpreadsheetId" } + dateCreated = datetime.datetime.now() + + with patch("chalicelib.services.EventService.datetime") as mock_datetime: + mock_datetime.datetime.now.return_value = dateCreated + result = event_service.create_timeframe(CREATE_TIMEFRAME) + CREATE_TIMEFRAME["dateCreated"] = dateCreated + mock_mongo_module.insert_document.assert_called_once_with( + collection="events-timeframe", data=CREATE_TIMEFRAME + ) + + assert result == {"msg": True} \ No newline at end of file From b508bf98e90c852ab8bbe187137bffb81801866b Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 14 Jul 2024 13:59:38 -0400 Subject: [PATCH 02/34] get_timeframe covered --- chalicelib/services/EventService.py | 2 +- tests/services/test_event_service.py | 58 +++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventService.py index 5b233d0..8ab3a1e 100644 --- a/chalicelib/services/EventService.py +++ b/chalicelib/services/EventService.py @@ -30,7 +30,7 @@ def create_timeframe(self, timeframe_data: dict): def get_timeframe(self, timeframe_id: str): timeframe = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}timeframe", timeframe_id + collection=f"{self.collection_prefix}timeframe", document_id=timeframe_id ) return json.dumps(timeframe, cls=self.BSONEncoder) diff --git a/tests/services/test_event_service.py b/tests/services/test_event_service.py index 3d67db8..00c6b44 100644 --- a/tests/services/test_event_service.py +++ b/tests/services/test_event_service.py @@ -2,7 +2,19 @@ from unittest.mock import MagicMock, patch from chalicelib.services.EventService import EventService import datetime +import json +with open("tests/fixtures/events/general/sample_timeframes.json") as f: + SAMPLE_TIMEFRAMES: list = json.load(f) + +with open("tests/fixtures/events/general/sample_timeframe_sheets.json") as f: + SAMPLE_TIMEFRAME_SHEETS: list = json.load(f) + +with open("tests/fixtures/events/general/sample_rush_events.json") as f: + SAMPLE_RUSH_EVENTS: list = json.load(f) + +with open("tests/fixtures/events/general/sample_rush_event.json") as f: + SAMPLE_RUSH_EVENT = json.load(f) @pytest.fixture def mock_mongo_module(): @@ -14,7 +26,6 @@ def mock_mongo_module(): def event_service(mock_mongo_module): return EventService(mock_mongo_module) - def test_insert_document(event_service, mock_mongo_module): CREATE_TIMEFRAME = { "name": "testTimeframeName", "spreadsheetId": "testSpreadsheetId" } dateCreated = datetime.datetime.now() @@ -27,4 +38,47 @@ def test_insert_document(event_service, mock_mongo_module): collection="events-timeframe", data=CREATE_TIMEFRAME ) - assert result == {"msg": True} \ No newline at end of file + assert result == {"msg": True} + +def test_get_timeframe(event_service, mock_mongo_module): + mock_mongo_module.get_document_by_id.return_value = SAMPLE_TIMEFRAMES[0] + timeframe_id = SAMPLE_TIMEFRAMES[0]["_id"] + + result = event_service.get_timeframe(timeframe_id=timeframe_id) + mock_mongo_module.get_document_by_id.assert_called_once_with( + collection="events-timeframe", document_id=timeframe_id + ) + + assert result == json.dumps(SAMPLE_TIMEFRAMES[0]) + +# TODO: + +# def get_all_timeframes(self): + +# def delete_timeframe(self, timeframe_id: str): + +# def create_event(self, timeframe_id: str, event_data: dict): + +# def get_event(self, event_id: str): + +# def checkin(self, event_id: str, user: dict) -> dict: + +# def delete(self, event_id: str): + +# def get_timeframe_sheets(self, timeframe_id: str): + +# def get_rush_categories_and_events(self): + +# def create_rush_category(self, data: dict): + +# def create_rush_event(self, data: dict): + +# def modify_rush_event(self, data: dict): + +# def modify_rush_settings(self, data: dict): + +# def get_rush_event(self, event_id: str, hide_attendees: bool = True): + +# def checkin_rush(self, event_id: str, user_data: dict): + +# def delete_rush_event(self, event_id: str): From 72372a03ebcdb6eb9046e9155a71088d1c4a70a5 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 14 Jul 2024 14:50:45 -0400 Subject: [PATCH 03/34] updated variable namings --- chalicelib/services/EventService.py | 2 ++ tests/services/test_event_service.py | 29 +++++++++++++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventService.py index 8ab3a1e..f77170b 100644 --- a/chalicelib/services/EventService.py +++ b/chalicelib/services/EventService.py @@ -65,6 +65,8 @@ def delete_timeframe(self, timeframe_id: str): self.mongo_module.delete_document_by_id( f"{self.collection_prefix}timeframe", timeframe_id ) + + return {"statusCode": 200} def create_event(self, timeframe_id: str, event_data: dict): event_data["dateCreated"] = datetime.datetime.now() diff --git a/tests/services/test_event_service.py b/tests/services/test_event_service.py index 00c6b44..1f25db5 100644 --- a/tests/services/test_event_service.py +++ b/tests/services/test_event_service.py @@ -28,12 +28,12 @@ def event_service(mock_mongo_module): def test_insert_document(event_service, mock_mongo_module): CREATE_TIMEFRAME = { "name": "testTimeframeName", "spreadsheetId": "testSpreadsheetId" } - dateCreated = datetime.datetime.now() + date_created = datetime.datetime.now() with patch("chalicelib.services.EventService.datetime") as mock_datetime: - mock_datetime.datetime.now.return_value = dateCreated + mock_datetime.datetime.now.return_value = date_created result = event_service.create_timeframe(CREATE_TIMEFRAME) - CREATE_TIMEFRAME["dateCreated"] = dateCreated + CREATE_TIMEFRAME["date_created"] = date_created mock_mongo_module.insert_document.assert_called_once_with( collection="events-timeframe", data=CREATE_TIMEFRAME ) @@ -51,13 +51,28 @@ def test_get_timeframe(event_service, mock_mongo_module): assert result == json.dumps(SAMPLE_TIMEFRAMES[0]) -# TODO: +def test_get_all_timeframe(event_service, mock_mongo_module): + mock_mongo_module.get_all_data_from_collection.return_value = SAMPLE_TIMEFRAMES + + result = event_service.get_all_timeframes() + mock_mongo_module.get_all_data_from_collection.assert_called_once_with( + collection="events-timeframe" + ) -# def get_all_timeframes(self): + assert result == json.dumps(SAMPLE_TIMEFRAMES) -# def delete_timeframe(self, timeframe_id: str): +def test_delete_timeframe(event_service, mock_mongo_module): + result = event_service.delete_timeframe(timeframe_id=SAMPLE_TIMEFRAMES[0]["_id"]) + assert result["statusCode"] == 200 -# def create_event(self, timeframe_id: str, event_data: dict): + +# TODO: potentially add mocking for GoogleSheetsModule (otherwise test is not isolated) +# def test_create_event(event_service, mock_mongo_module): +# CREATE_TIMEFRAME = { "name": "eventName", "tags": "tags", "sheetTab": "selectedSheetTab" } +# date_created = datetime.datetime.now() +# timeframe_id = "timeframeId" + +# TODO: # def get_event(self, event_id: str): From 3a966a3aed4692594bd55650f354c147165641cf Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 14 Jul 2024 15:00:26 -0400 Subject: [PATCH 04/34] refactored MongoModule with ternary --- chalicelib/modules/mongo.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/chalicelib/modules/mongo.py b/chalicelib/modules/mongo.py index c288a13..f68b693 100644 --- a/chalicelib/modules/mongo.py +++ b/chalicelib/modules/mongo.py @@ -10,11 +10,6 @@ class MongoModule: def __init__(self, use_mock=False): """Establishes connection to MongoDB server""" - # if use_mock is true -> use monogmock to execute tests with fake db - self.use_mock = use_mock - if use_mock: - self.mongo_client = mongomock.MongoClient() - return self.is_prod = os.environ.get("ENV") == "prod" self.ssm_client = boto3.client("ssm") @@ -26,7 +21,8 @@ def __init__(self, use_mock=False): )["Parameter"]["Value"] self.uri = f"mongodb+srv://{self.user}:{self.password}@cluster0.9gtht.mongodb.net/?retryWrites=true&w=majority" - self.mongo_client = MongoClient(self.uri) + # if use_mock is true -> use monogmock to execute tests with fake db + self.mongo_client = mongomock.MongoClient() if use_mock else MongoClient(self.uri) def add_env_suffix(func): def wrapper(self, collection: str, *args, **kwargs): From aeac99df2e70d9bc6e7fc2e366e317ff34d90096 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Wed, 17 Jul 2024 23:31:02 -0400 Subject: [PATCH 05/34] fixed uploading errors for rush event image --- chalicelib/services/EventService.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventService.py index f77170b..62a96b6 100644 --- a/chalicelib/services/EventService.py +++ b/chalicelib/services/EventService.py @@ -5,6 +5,8 @@ import datetime from chalicelib.modules.google_sheets import GoogleSheetsModule from chalicelib.modules.ses import ses, SesDestination +from chalicelib.s3 import s3 +import uuid class EventService: class BSONEncoder(json.JSONEncoder): @@ -255,8 +257,18 @@ def create_rush_category(self, data: dict): return def create_rush_event(self, data: dict): + event_id = str(uuid.uuid4()) data["dateCreated"] = datetime.datetime.now() data["lastModified"] = data["dateCreated"] + data["eventId"] = event_id + + # upload eventCoverImage to s3 bucket (convert everything to png files for now... can adjust later) + image_path = f"image/rush/{data['categoryId']}/{event_id}/{data['eventCoverImageName']}.png" + print("image path", image_path) + image_url = s3.upload_binary_data(image_path, data["eventCoverImage"]) + + # add image_url to data object (this also replaces the original base64 image url) + data["eventCoverImage"] = image_url data_copy = data.copy() data_copy.pop("categoryId", None) @@ -264,12 +276,10 @@ def create_rush_event(self, data: dict): # Add event to its own collection data["attendees"] = [] data["numAttendees"] = 0 - event_id = self.mongo_module.insert_document( + self.mongo_module.insert_document( f"{self.collection_prefix}rush-event", data ) - data_copy["eventId"] = str(event_id) - # Add event to rush category self.mongo_module.update_document( f"{self.collection_prefix}rush", From c2f5b1f4aacf6fdb0299c3606b0d8f6952ee3afe Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:49:57 -0400 Subject: [PATCH 06/34] s3 delete_object working + switched to using `_id` for events (makes life easier) --- chalicelib/s3.py | 24 ++++++++++++++++- chalicelib/services/EventService.py | 40 +++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/chalicelib/s3.py b/chalicelib/s3.py index b1d09b6..a180280 100644 --- a/chalicelib/s3.py +++ b/chalicelib/s3.py @@ -13,7 +13,7 @@ def __init__(self): self.s3 = boto3.client("s3") def upload_binary_data(self, path: str, data: str) -> str: - """Uploads resume to S3 Bucket and returns path""" + """Uploads object to S3 Bucket and returns path""" # Set path if self.is_prod: path = f"prod/{path}" @@ -39,4 +39,26 @@ def upload_binary_data(self, path: str, data: str) -> str: return object_url + # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/delete_object.html + def delete_binary_data(self, object_id: str) -> str: + """Deletes object from s3 and returns response + Args: + object_id (str): The key (path) of the object to delete from the S3 bucket. + e.g. dev/image/rush/66988908fd70b2c44bf2305d/199bb28f-b54c-48a3-9b94-1c95eab61f7d/infosession2.png + + Returns: + str: A message indicating the result of the deletion operation. + """ + if self.is_prod: + path = f"prod/{object_id}" + else: + path = f"dev/{object_id}" + + # delete binary data given bucket_name and key + response = self.s3.delete_object( + Bucket=self.bucket_name, Key=path + ) + + return response + s3 = S3Client() \ No newline at end of file diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventService.py index 62a96b6..9940c96 100644 --- a/chalicelib/services/EventService.py +++ b/chalicelib/services/EventService.py @@ -6,7 +6,6 @@ from chalicelib.modules.google_sheets import GoogleSheetsModule from chalicelib.modules.ses import ses, SesDestination from chalicelib.s3 import s3 -import uuid class EventService: class BSONEncoder(json.JSONEncoder): @@ -257,14 +256,13 @@ def create_rush_category(self, data: dict): return def create_rush_event(self, data: dict): - event_id = str(uuid.uuid4()) + event_id = ObjectId() data["dateCreated"] = datetime.datetime.now() data["lastModified"] = data["dateCreated"] - data["eventId"] = event_id + data["_id"] = event_id # upload eventCoverImage to s3 bucket (convert everything to png files for now... can adjust later) - image_path = f"image/rush/{data['categoryId']}/{event_id}/{data['eventCoverImageName']}.png" - print("image path", image_path) + image_path = f"image/rush/{data['categoryId']}/{event_id}.png" image_url = s3.upload_binary_data(image_path, data["eventCoverImage"]) # add image_url to data object (this also replaces the original base64 image url) @@ -288,17 +286,17 @@ def create_rush_event(self, data: dict): ) return - + def modify_rush_event(self, data: dict): try: data["lastModified"] = datetime.datetime.now() - eventId = data["eventId"] + event_id = data["_id"] # Check if event exists in the rush-event collection event = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}rush-event", eventId + f"{self.collection_prefix}rush-event", event_id ) if not event: @@ -306,6 +304,23 @@ def modify_rush_event(self, data: dict): event_category_id = event["categoryId"] + # if eventCoverImage contains https://whyphi-zap.s3.amazonaws.com, no need to update anything, otherwise update s3 + if "https://whyphi-zap.s3.amazonaws.com" not in data["eventCoverImage"]: + + # get image path + image_path = f"image/rush/{event_category_id}/{event_id}.png" + print("hello there ", image_path) + + # remove previous eventCoverImage from s3 bucket + s3.delete_binary_data(object_id=image_path) + + # # upload eventCoverImage to s3 bucket + # image_url = s3.upload_binary_data(image_path, data["eventCoverImage"]) + + # # add image_url to data object (this also replaces the original base64 image url) + # data["eventCoverImage"] = image_url + + # Merge the existing event data with the new data updated_event = {**event, **data} @@ -320,7 +335,7 @@ def modify_rush_event(self, data: dict): } array_filters = [ - {"eventElem.eventId": eventId} + {"eventElem._id": event_id} ] # Modify the event in its category (rush collection) @@ -331,10 +346,13 @@ def modify_rush_event(self, data: dict): array_filters=array_filters ) + # cannot include _id on original document (immutable) + data.pop("_id") + # Modify actual event document (rush-event collection) self.mongo_module.update_document( f"{self.collection_prefix}rush-event", - eventId, + event_id, {"$set": data}, ) return @@ -453,7 +471,7 @@ def delete_rush_event(self, event_id: str): self.mongo_module.update_document( f"{self.collection_prefix}rush", event_category_id, - {"$pull": {"events": {"eventId": event_id}}}, + {"$pull": {"events": {"_id": event_id}}}, ) # Delete event data from the rush-event collection From 7ebd07a866c24f77e4abf95b74d48617a5cfe14d Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:05:48 -0400 Subject: [PATCH 07/34] modify s3 image working :) --- chalicelib/services/EventService.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventService.py index 9940c96..38e592c 100644 --- a/chalicelib/services/EventService.py +++ b/chalicelib/services/EventService.py @@ -314,11 +314,11 @@ def modify_rush_event(self, data: dict): # remove previous eventCoverImage from s3 bucket s3.delete_binary_data(object_id=image_path) - # # upload eventCoverImage to s3 bucket - # image_url = s3.upload_binary_data(image_path, data["eventCoverImage"]) + # upload eventCoverImage to s3 bucket + image_url = s3.upload_binary_data(path=image_path, data=data["eventCoverImage"]) - # # add image_url to data object (this also replaces the original base64 image url) - # data["eventCoverImage"] = image_url + # add image_url to data object (this also replaces the original base64 image url) + data["eventCoverImage"] = image_url # Merge the existing event data with the new data From 9c430598dfedcb03065fba18118fd13b44ceba18 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Fri, 19 Jul 2024 11:11:19 -0400 Subject: [PATCH 08/34] fixed documentation for s3 delete --- chalicelib/s3.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chalicelib/s3.py b/chalicelib/s3.py index a180280..239e8f8 100644 --- a/chalicelib/s3.py +++ b/chalicelib/s3.py @@ -39,7 +39,6 @@ def upload_binary_data(self, path: str, data: str) -> str: return object_url - # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/delete_object.html def delete_binary_data(self, object_id: str) -> str: """Deletes object from s3 and returns response Args: @@ -48,6 +47,8 @@ def delete_binary_data(self, object_id: str) -> str: Returns: str: A message indicating the result of the deletion operation. + + Documentation: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/delete_object.html """ if self.is_prod: path = f"prod/{object_id}" From 4f2786d094301371e3704ef89b3052f7aa22a154 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sat, 20 Jul 2024 17:30:52 -0400 Subject: [PATCH 09/34] patched bug not being able to update event --- chalicelib/services/EventService.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventService.py index 38e592c..b0bc8d3 100644 --- a/chalicelib/services/EventService.py +++ b/chalicelib/services/EventService.py @@ -290,9 +290,11 @@ def create_rush_event(self, data: dict): def modify_rush_event(self, data: dict): try: - data["lastModified"] = datetime.datetime.now() - event_id = data["_id"] + object_event_id = ObjectId(event_id) + + data["lastModified"] = datetime.datetime.now() + data["_id"] = object_event_id # Check if event exists in the rush-event collection event = self.mongo_module.get_document_by_id( @@ -335,26 +337,33 @@ def modify_rush_event(self, data: dict): } array_filters = [ - {"eventElem._id": event_id} + {"eventElem._id": object_event_id} ] # Modify the event in its category (rush collection) - self.mongo_module.update_document( - f"{self.collection_prefix}rush", - event_category_id, - update_query, + update_category_result = self.mongo_module.update_document( + collection=f"{self.collection_prefix}rush", + document_id=event_category_id, + query=update_query, array_filters=array_filters ) + + if not update_category_result: + raise Exception("Error updating rush-event-category.") # cannot include _id on original document (immutable) data.pop("_id") # Modify actual event document (rush-event collection) - self.mongo_module.update_document( + updated_event_result = self.mongo_module.update_document( f"{self.collection_prefix}rush-event", event_id, {"$set": data}, ) + + if not updated_event_result: + raise Exception("Error updating rush-event-category.") + return From 8a095b52c15b3bebb2bd584f9ede194798ce8424 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sat, 20 Jul 2024 18:33:32 -0400 Subject: [PATCH 10/34] rush event deletion works properly again --- chalicelib/services/EventService.py | 36 +++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventService.py index b0bc8d3..68cf7c4 100644 --- a/chalicelib/services/EventService.py +++ b/chalicelib/services/EventService.py @@ -268,12 +268,17 @@ def create_rush_event(self, data: dict): # add image_url to data object (this also replaces the original base64 image url) data["eventCoverImage"] = image_url + # initialize attendee info (for rush-event) + data["attendees"] = [] + data["numAttendees"] = 0 + data_copy = data.copy() + # Remove catgoryId, attendees, and numAttendees from rush category (shouldn't be persisted) data_copy.pop("categoryId", None) + data_copy.pop("attendees", None) + data_copy.pop("numAttendees", None) # Add event to its own collection - data["attendees"] = [] - data["numAttendees"] = 0 self.mongo_module.insert_document( f"{self.collection_prefix}rush-event", data ) @@ -311,7 +316,6 @@ def modify_rush_event(self, data: dict): # get image path image_path = f"image/rush/{event_category_id}/{event_id}.png" - print("hello there ", image_path) # remove previous eventCoverImage from s3 bucket s3.delete_binary_data(object_id=image_path) @@ -326,8 +330,10 @@ def modify_rush_event(self, data: dict): # Merge the existing event data with the new data updated_event = {**event, **data} - # categoryId not needed on rush collection array elements - updated_event.pop("categoryId") + # Remove catgoryId, attendees, and numAttendees from rush category (shouldn't be persisted) + updated_event.pop("categoryId", None) + updated_event.pop("attendees", None) + updated_event.pop("numAttendees", None) # Define array update query and filters update_query = { @@ -466,26 +472,38 @@ def delete_rush_event(self, event_id: str): If the event does not exist in the rush-event collection """ try: + # Get event_id as ObjectId + object_event_id = ObjectId(event_id) + # Check if event exists in the rush-event collection event = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}rush-event", event_id + f"{self.collection_prefix}rush-event", object_event_id ) - + if not event: raise Exception("Event does not exist.") event_category_id = event["categoryId"] + # Get eventCoverImage path + image_path = f"image/rush/{event_category_id}/{event_id}.png" + + # remove previous eventCoverImage from s3 bucket + s3.delete_binary_data(object_id=image_path) + + # upload eventCoverImage to s3 bucket + s3.delete_binary_data(object_id=image_path) + # Delete the event from its category self.mongo_module.update_document( f"{self.collection_prefix}rush", event_category_id, - {"$pull": {"events": {"_id": event_id}}}, + {"$pull": {"events": {"_id": object_event_id}}}, ) # Delete event data from the rush-event collection self.mongo_module.delete_document_by_id( - f"{self.collection_prefix}rush-event", event_id + f"{self.collection_prefix}rush-event", object_event_id ) return From d8b83a8592ddb9caec972d61eea49084e8b94265 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sat, 20 Jul 2024 18:36:23 -0400 Subject: [PATCH 11/34] Revert "rush event deletion works properly again" This reverts commit 8a095b52c15b3bebb2bd584f9ede194798ce8424. Undo commit to dev/v1.0 --- chalicelib/services/EventService.py | 36 ++++++++--------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventService.py index 68cf7c4..b0bc8d3 100644 --- a/chalicelib/services/EventService.py +++ b/chalicelib/services/EventService.py @@ -268,17 +268,12 @@ def create_rush_event(self, data: dict): # add image_url to data object (this also replaces the original base64 image url) data["eventCoverImage"] = image_url - # initialize attendee info (for rush-event) - data["attendees"] = [] - data["numAttendees"] = 0 - data_copy = data.copy() - # Remove catgoryId, attendees, and numAttendees from rush category (shouldn't be persisted) data_copy.pop("categoryId", None) - data_copy.pop("attendees", None) - data_copy.pop("numAttendees", None) # Add event to its own collection + data["attendees"] = [] + data["numAttendees"] = 0 self.mongo_module.insert_document( f"{self.collection_prefix}rush-event", data ) @@ -316,6 +311,7 @@ def modify_rush_event(self, data: dict): # get image path image_path = f"image/rush/{event_category_id}/{event_id}.png" + print("hello there ", image_path) # remove previous eventCoverImage from s3 bucket s3.delete_binary_data(object_id=image_path) @@ -330,10 +326,8 @@ def modify_rush_event(self, data: dict): # Merge the existing event data with the new data updated_event = {**event, **data} - # Remove catgoryId, attendees, and numAttendees from rush category (shouldn't be persisted) - updated_event.pop("categoryId", None) - updated_event.pop("attendees", None) - updated_event.pop("numAttendees", None) + # categoryId not needed on rush collection array elements + updated_event.pop("categoryId") # Define array update query and filters update_query = { @@ -472,38 +466,26 @@ def delete_rush_event(self, event_id: str): If the event does not exist in the rush-event collection """ try: - # Get event_id as ObjectId - object_event_id = ObjectId(event_id) - # Check if event exists in the rush-event collection event = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}rush-event", object_event_id + f"{self.collection_prefix}rush-event", event_id ) - + if not event: raise Exception("Event does not exist.") event_category_id = event["categoryId"] - # Get eventCoverImage path - image_path = f"image/rush/{event_category_id}/{event_id}.png" - - # remove previous eventCoverImage from s3 bucket - s3.delete_binary_data(object_id=image_path) - - # upload eventCoverImage to s3 bucket - s3.delete_binary_data(object_id=image_path) - # Delete the event from its category self.mongo_module.update_document( f"{self.collection_prefix}rush", event_category_id, - {"$pull": {"events": {"_id": object_event_id}}}, + {"$pull": {"events": {"_id": event_id}}}, ) # Delete event data from the rush-event collection self.mongo_module.delete_document_by_id( - f"{self.collection_prefix}rush-event", object_event_id + f"{self.collection_prefix}rush-event", event_id ) return From ee07dbd0f1dd047658a59cdd0a065d772c955c04 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sat, 20 Jul 2024 18:40:59 -0400 Subject: [PATCH 12/34] fixed deleting rush event --- chalicelib/services/EventService.py | 36 +++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventService.py index b0bc8d3..68cf7c4 100644 --- a/chalicelib/services/EventService.py +++ b/chalicelib/services/EventService.py @@ -268,12 +268,17 @@ def create_rush_event(self, data: dict): # add image_url to data object (this also replaces the original base64 image url) data["eventCoverImage"] = image_url + # initialize attendee info (for rush-event) + data["attendees"] = [] + data["numAttendees"] = 0 + data_copy = data.copy() + # Remove catgoryId, attendees, and numAttendees from rush category (shouldn't be persisted) data_copy.pop("categoryId", None) + data_copy.pop("attendees", None) + data_copy.pop("numAttendees", None) # Add event to its own collection - data["attendees"] = [] - data["numAttendees"] = 0 self.mongo_module.insert_document( f"{self.collection_prefix}rush-event", data ) @@ -311,7 +316,6 @@ def modify_rush_event(self, data: dict): # get image path image_path = f"image/rush/{event_category_id}/{event_id}.png" - print("hello there ", image_path) # remove previous eventCoverImage from s3 bucket s3.delete_binary_data(object_id=image_path) @@ -326,8 +330,10 @@ def modify_rush_event(self, data: dict): # Merge the existing event data with the new data updated_event = {**event, **data} - # categoryId not needed on rush collection array elements - updated_event.pop("categoryId") + # Remove catgoryId, attendees, and numAttendees from rush category (shouldn't be persisted) + updated_event.pop("categoryId", None) + updated_event.pop("attendees", None) + updated_event.pop("numAttendees", None) # Define array update query and filters update_query = { @@ -466,26 +472,38 @@ def delete_rush_event(self, event_id: str): If the event does not exist in the rush-event collection """ try: + # Get event_id as ObjectId + object_event_id = ObjectId(event_id) + # Check if event exists in the rush-event collection event = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}rush-event", event_id + f"{self.collection_prefix}rush-event", object_event_id ) - + if not event: raise Exception("Event does not exist.") event_category_id = event["categoryId"] + # Get eventCoverImage path + image_path = f"image/rush/{event_category_id}/{event_id}.png" + + # remove previous eventCoverImage from s3 bucket + s3.delete_binary_data(object_id=image_path) + + # upload eventCoverImage to s3 bucket + s3.delete_binary_data(object_id=image_path) + # Delete the event from its category self.mongo_module.update_document( f"{self.collection_prefix}rush", event_category_id, - {"$pull": {"events": {"_id": event_id}}}, + {"$pull": {"events": {"_id": object_event_id}}}, ) # Delete event data from the rush-event collection self.mongo_module.delete_document_by_id( - f"{self.collection_prefix}rush-event", event_id + f"{self.collection_prefix}rush-event", object_event_id ) return From 443d49664c7119d3671f8e211c92291c759d69ab Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 21 Jul 2024 14:29:05 -0400 Subject: [PATCH 13/34] split events member/rush into two separate services/APIs --- app.py | 6 +- chalicelib/api/events.py | 117 --------- chalicelib/api/events_member.py | 68 +++++ chalicelib/api/events_rush.py | 54 ++++ chalicelib/services/EventsMemberService.py | 245 ++++++++++++++++++ .../{EventService.py => EventsRushService.py} | 233 +---------------- tests/services/test_event_service.py | 2 +- 7 files changed, 377 insertions(+), 348 deletions(-) delete mode 100644 chalicelib/api/events.py create mode 100644 chalicelib/api/events_member.py create mode 100644 chalicelib/api/events_rush.py create mode 100644 chalicelib/services/EventsMemberService.py rename chalicelib/services/{EventService.py => EventsRushService.py} (54%) diff --git a/app.py b/app.py index e7be31f..abac8ac 100644 --- a/app.py +++ b/app.py @@ -10,7 +10,8 @@ from chalicelib.api.announcements import announcements_api from chalicelib.api.insights import insights_api from chalicelib.api.members import members_api -from chalicelib.api.events import events_api +from chalicelib.api.events_member import events_member_api +from chalicelib.api.events_rush import events_rush_api from chalicelib.api.accountability import accountability_api from chalicelib.api.monitoring import monitoring_api @@ -37,7 +38,8 @@ app.register_blueprint(applicants_api) app.register_blueprint(insights_api) app.register_blueprint(members_api) -app.register_blueprint(events_api) +app.register_blueprint(events_member_api) +app.register_blueprint(events_rush_api) app.register_blueprint(accountability_api) app.register_blueprint(monitoring_api) diff --git a/chalicelib/api/events.py b/chalicelib/api/events.py deleted file mode 100644 index 90af958..0000000 --- a/chalicelib/api/events.py +++ /dev/null @@ -1,117 +0,0 @@ -from chalice import Blueprint -from chalicelib.decorators import auth -from chalicelib.services.EventService import event_service - -events_api = Blueprint(__name__) - - -@events_api.route("/timeframes", methods=["POST"], cors=True) -@auth(events_api, roles=["admin"]) -def create_timeframe(): - data = events_api.current_request.json_body - return event_service.create_timeframe(data) - - -@events_api.route("/timeframes", methods=["GET"], cors=True) -@auth(events_api, roles=["admin"]) -def get_all_timeframes(): - return event_service.get_all_timeframes() - - -@events_api.route("/timeframes/{timeframe_id}", methods=["GET"], cors=True) -@auth(events_api, roles=["admin"]) -def get_timeframe(timeframe_id: str): - return event_service.get_timeframe(timeframe_id) - - -@events_api.route("/timeframes/{timeframe_id}", methods=["DELETE"], cors=True) -@auth(events_api, roles=["admin"]) -def delete_timeframe(timeframe_id: str): - return event_service.delete_timeframe(timeframe_id) - - -@events_api.route("/timeframes/{timeframe_id}/events", methods=["POST"], cors=True) -@auth(events_api, roles=["admin"]) -def create_event(timeframe_id: str): - data = events_api.current_request.json_body - return event_service.create_event(timeframe_id, data) - - -@events_api.route("/events/{event_id}", methods=["GET"], cors=True) -@auth(events_api, roles=["admin"]) -def get_event(event_id: str): - return event_service.get_event(event_id) - - -@events_api.route("/timeframes/{timeframe_id}/sheets", methods=["GET"], cors=True) -@auth(events_api, roles=["admin"]) -def get_timeframe_sheets(timeframe_id: str): - return event_service.get_timeframe_sheets(timeframe_id) - - -@events_api.route("/events/{event_id}/checkin", methods=["POST"], cors=True) -@auth(events_api, roles=["admin"]) -def checkin(event_id: str): - data = events_api.current_request.json_body - return event_service.checkin(event_id, data) - - -@events_api.route("/events/{event_id}", methods=["PATCH"], cors=True) -@auth(events_api, roles=["admin"]) -def update_event(event_id: str): - pass - - -@events_api.route("/events/{event_id}", methods=["DELETE"], cors=True) -@auth(events_api, roles=["admin"]) -def delete_event(event_id: str): - return event_service.delete(event_id) - - -@events_api.route("/events/rush", methods=["GET"], cors=True) -@auth(events_api, roles=["admin"]) -def get_rush_events(): - return event_service.get_rush_categories_and_events() - - -@events_api.route("/events/rush/{event_id}", methods=["GET"], cors=True) -def get_rush_event(event_id): - return event_service.get_rush_event(event_id) - - -@events_api.route("/events/rush/category", methods=["POST"], cors=True) -@auth(events_api, roles=["admin"]) -def create_rush_category(): - data = events_api.current_request.json_body - return event_service.create_rush_category(data) - - -@events_api.route("/events/rush", methods=["POST"], cors=True) -@auth(events_api, roles=["admin"]) -def create_rush_event(): - data = events_api.current_request.json_body - return event_service.create_rush_event(data) - - -@events_api.route("/events/rush", methods=["PATCH"], cors=True) -@auth(events_api, roles=["admin"]) -def modify_rush_event(): - data = events_api.current_request.json_body - return event_service.modify_rush_event(data) - -@events_api.route("/events/rush/settings", methods=["PATCH"], cors=True) -@auth(events_api, roles=["admin"]) -def modify_rush_settings(): - data = events_api.current_request.json_body - return event_service.modify_rush_settings(data) - - -@events_api.route("/events/rush/checkin/{event_id}", methods=["POST"], cors=True) -def checkin_rush(event_id): - data = events_api.current_request.json_body - return event_service.checkin_rush(event_id, data) - - -@events_api.route("/events/rush/{event_id}", methods=["DELETE"], cors=True) -def delete_rush_event(event_id): - return event_service.delete_rush_event(event_id) \ No newline at end of file diff --git a/chalicelib/api/events_member.py b/chalicelib/api/events_member.py new file mode 100644 index 0000000..9ea805e --- /dev/null +++ b/chalicelib/api/events_member.py @@ -0,0 +1,68 @@ +from chalice import Blueprint +from chalicelib.decorators import auth +from chalicelib.services.EventsMemberService import events_member_service + +events_member_api = Blueprint(__name__) + + +@events_member_api.route("/timeframes", methods=["POST"], cors=True) +@auth(events_member_api, roles=["admin"]) +def create_timeframe(): + data = events_member_api.current_request.json_body + return events_member_service.create_timeframe(data) + + +@events_member_api.route("/timeframes", methods=["GET"], cors=True) +@auth(events_member_api, roles=["admin"]) +def get_all_timeframes(): + return events_member_service.get_all_timeframes() + + +@events_member_api.route("/timeframes/{timeframe_id}", methods=["GET"], cors=True) +@auth(events_member_api, roles=["admin"]) +def get_timeframe(timeframe_id: str): + return events_member_service.get_timeframe(timeframe_id) + + +@events_member_api.route("/timeframes/{timeframe_id}", methods=["DELETE"], cors=True) +@auth(events_member_api, roles=["admin"]) +def delete_timeframe(timeframe_id: str): + return events_member_service.delete_timeframe(timeframe_id) + + +@events_member_api.route("/timeframes/{timeframe_id}/events", methods=["POST"], cors=True) +@auth(events_member_api, roles=["admin"]) +def create_event(timeframe_id: str): + data = events_member_api.current_request.json_body + return events_member_service.create_event(timeframe_id, data) + + +@events_member_api.route("/events/{event_id}", methods=["GET"], cors=True) +@auth(events_member_api, roles=["admin"]) +def get_event(event_id: str): + return events_member_service.get_event(event_id) + + +@events_member_api.route("/timeframes/{timeframe_id}/sheets", methods=["GET"], cors=True) +@auth(events_member_api, roles=["admin"]) +def get_timeframe_sheets(timeframe_id: str): + return events_member_service.get_timeframe_sheets(timeframe_id) + + +@events_member_api.route("/events/{event_id}/checkin", methods=["POST"], cors=True) +@auth(events_member_api, roles=["admin"]) +def checkin(event_id: str): + data = events_member_api.current_request.json_body + return events_member_service.checkin(event_id, data) + + +@events_member_api.route("/events/{event_id}", methods=["PATCH"], cors=True) +@auth(events_member_api, roles=["admin"]) +def update_event(event_id: str): + pass + + +@events_member_api.route("/events/{event_id}", methods=["DELETE"], cors=True) +@auth(events_member_api, roles=["admin"]) +def delete_event(event_id: str): + return events_member_service.delete(event_id) \ No newline at end of file diff --git a/chalicelib/api/events_rush.py b/chalicelib/api/events_rush.py new file mode 100644 index 0000000..f33cecd --- /dev/null +++ b/chalicelib/api/events_rush.py @@ -0,0 +1,54 @@ +from chalice import Blueprint +from chalicelib.decorators import auth +from chalicelib.services.EventsRushService import events_rush_service + +events_rush_api = Blueprint(__name__) + + +@events_rush_api.route("/events/rush", methods=["GET"], cors=True) +@auth(events_rush_api, roles=["admin"]) +def get_rush_events(): + return events_rush_service.get_rush_categories_and_events() + + +@events_rush_api.route("/events/rush/{event_id}", methods=["GET"], cors=True) +def get_rush_event(event_id): + return events_rush_service.get_rush_event(event_id) + + +@events_rush_api.route("/events/rush/category", methods=["POST"], cors=True) +@auth(events_rush_api, roles=["admin"]) +def create_rush_category(): + data = events_rush_api.current_request.json_body + return events_rush_service.create_rush_category(data) + + +@events_rush_api.route("/events/rush", methods=["POST"], cors=True) +@auth(events_rush_api, roles=["admin"]) +def create_rush_event(): + data = events_rush_api.current_request.json_body + return events_rush_service.create_rush_event(data) + + +@events_rush_api.route("/events/rush", methods=["PATCH"], cors=True) +@auth(events_rush_api, roles=["admin"]) +def modify_rush_event(): + data = events_rush_api.current_request.json_body + return events_rush_service.modify_rush_event(data) + +@events_rush_api.route("/events/rush/settings", methods=["PATCH"], cors=True) +@auth(events_rush_api, roles=["admin"]) +def modify_rush_settings(): + data = events_rush_api.current_request.json_body + return events_rush_service.modify_rush_settings(data) + + +@events_rush_api.route("/events/rush/checkin/{event_id}", methods=["POST"], cors=True) +def checkin_rush(event_id): + data = events_rush_api.current_request.json_body + return events_rush_service.checkin_rush(event_id, data) + + +@events_rush_api.route("/events/rush/{event_id}", methods=["DELETE"], cors=True) +def delete_rush_event(event_id): + return events_rush_service.delete_rush_event(event_id) \ No newline at end of file diff --git a/chalicelib/services/EventsMemberService.py b/chalicelib/services/EventsMemberService.py new file mode 100644 index 0000000..8ddeaec --- /dev/null +++ b/chalicelib/services/EventsMemberService.py @@ -0,0 +1,245 @@ +from chalicelib.modules.mongo import mongo_module +from chalice import NotFoundError, BadRequestError +import json +from bson import ObjectId +import datetime +from chalicelib.modules.google_sheets import GoogleSheetsModule +from chalicelib.modules.ses import ses, SesDestination + +class EventsMemberService: + class BSONEncoder(json.JSONEncoder): + """JSON encoder that converts Mongo ObjectIds and datetime.datetime to strings.""" + + def default(self, o): + if isinstance(o, datetime.datetime): + return o.isoformat() + elif isinstance(o, ObjectId): + return str(o) + return super().default(o) + + def __init__(self, mongo_module=mongo_module): + self.mongo_module = mongo_module + self.collection_prefix = "events-" + + def create_timeframe(self, timeframe_data: dict): + timeframe_data["dateCreated"] = datetime.datetime.now() + self.mongo_module.insert_document( + collection=f"{self.collection_prefix}timeframe", data=timeframe_data + ) + return {"msg": True} + + def get_timeframe(self, timeframe_id: str): + timeframe = self.mongo_module.get_document_by_id( + collection=f"{self.collection_prefix}timeframe", document_id=timeframe_id + ) + + return json.dumps(timeframe, cls=self.BSONEncoder) + + def get_all_timeframes(self): + """Retrieve all timeframes from the database.""" + timeframes = self.mongo_module.get_all_data_from_collection( + f"{self.collection_prefix}timeframe" + ) + + return json.dumps(timeframes, cls=self.BSONEncoder) + + def delete_timeframe(self, timeframe_id: str): + # Check if timeframe exists and if it doesn't return errors + timeframe = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}timeframe", timeframe_id + ) + + if timeframe is None: + raise NotFoundError(f"Timeframe with ID {timeframe_id} does not exist.") + + # If timeframe exists, get the eventIds (children) + event_ids = [str(event["_id"]) for event in timeframe["events"]] + + # Delete all the events in the timeframe + for event_id in event_ids: + self.mongo_module.delete_document_by_id( + f"{self.collection_prefix}event", event_id + ) + + # If timeframe exists, delete the timeframe document + self.mongo_module.delete_document_by_id( + f"{self.collection_prefix}timeframe", timeframe_id + ) + + return {"statusCode": 200} + + def create_event(self, timeframe_id: str, event_data: dict): + event_data["dateCreated"] = datetime.datetime.now() + event_data["timeframeId"] = timeframe_id + event_data["usersAttended"] = [] + + # Get Google Spreadsheet ID from timeframe + timeframe_doc = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}timeframe", timeframe_id + ) + spreadsheet_id = timeframe_doc["spreadsheetId"] + + # Add event name to Google Sheets + gs = GoogleSheetsModule() + col = gs.find_next_available_col(spreadsheet_id, event_data["sheetTab"]) + gs.add_event(spreadsheet_id, event_data["sheetTab"], event_data["name"], col) + + # Append next available col value to event data + event_data["spreadsheetCol"] = col + + # Insert the event in event collection + event_id = self.mongo_module.insert_document( + f"{self.collection_prefix}event", event_data + ) + + event_data["eventId"] = str(event_id) + + # Insert child event in timeframe collection + self.mongo_module.update_document( + f"{self.collection_prefix}timeframe", + timeframe_id, + {"$push": {"events": event_data}}, + ) + + return json.dumps(event_data, cls=self.BSONEncoder) + + def get_event(self, event_id: str): + event = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}event", event_id + ) + + return json.dumps(event, cls=self.BSONEncoder) + + def checkin(self, event_id: str, user: dict) -> dict: + """Checks in a user to an event. + + Arguments: + event_id {str} -- ID of the event to check into. + user {dict} -- Dictionary containing user ID and name. + + Returns: + dict -- Dictionary containing status and message. + """ + user_id, user_email = user["id"], user["email"] + member = self.mongo_module.get_document_by_id(f"users", user_id) + if member is None: + raise NotFoundError(f"User with ID {user_id} does not exist.") + + user_name = member["name"] + + event = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}event", event_id + ) + + if any(d["userId"] == user_id for d in event["usersAttended"]): + raise BadRequestError(f"{user_name} has already checked in.") + + checkin_data = { + "userId": user_id, + "name": user_name, + "dateCheckedIn": datetime.datetime.now(), + } + + # Get timeframe document to get Google Sheets info + timeframe = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}timeframe", event["timeframeId"] + ) + + # Get Google Sheets information + ss_id = timeframe["spreadsheetId"] + + # Initialize Google Sheets Module + gs = GoogleSheetsModule() + + # Find row in Google Sheets that matches user's email + row_num = gs.find_matching_email( + spreadsheet_id=ss_id, + sheet_name=event["sheetTab"], + col="C", + email_to_match=user_email, + ) + + if row_num == -1: + return { + "status": False, + "message": f"{user_name} was not found in the sheet.", + } + + # Update Google Sheets cell with a "1" if user has checked in + gs.update_row( + spreadsheet_id=ss_id, + sheet_name=event["sheetTab"], + col=event["spreadsheetCol"], + row=row_num + 1, + data=[["1"]], + ) + + # Update event collection with checkin data + self.mongo_module.update_document( + f"{self.collection_prefix}event", + event_id, + {"$push": {"usersAttended": checkin_data}}, + ) + + # Send email to user that has checked in + email_content = f""" + Hi {user_name},

+ + Thank you for checking in to {event["name"]}! This is a confirmation that you have checked in.

+ + Regards,
+ PCT Tech Team

+ + ** Please note: Do not reply to this email. This email is sent from an unattended mailbox. Replies will not be read. + """ + + ses_destination = SesDestination(tos=[user_email]) + ses.send_email( + source="checkin-bot@why-phi.com", + destination=ses_destination, + subject=f"Check-in Confirmation: {event['name']}", + text=email_content, + html=email_content, + ) + + return { + "status": True, + "message": f"{user_name} has successfully been checked in.", + } + + def delete(self, event_id: str): + # Check if event exists and if it doesn't return errors + event = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}event", event_id + ) + + if event is None: + raise NotFoundError(f"Event with ID {event_id} does not exist.") + + # If event exists, get the timeframeId (parent) + timeframe_id = event["timeframeId"] + + # Remove event from timeframe + self.mongo_module.update_document( + f"{self.collection_prefix}timeframe", + timeframe_id, + {"$pull": {"events": {"_id": ObjectId(event_id)}}}, + ) + + # Delete the event document + self.mongo_module.delete_document_by_id(f"{self.collection_prefix}event", event_id) + + def get_timeframe_sheets(self, timeframe_id: str): + timeframe = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}timeframe", timeframe_id + ) + + if "spreadsheetId" not in timeframe or timeframe["spreadsheetId"] == "": + return [] + + gs = GoogleSheetsModule() + sheets = gs.get_sheets(timeframe["spreadsheetId"], include_properties=False) + return [sheet["title"] for sheet in sheets] + + +events_member_service = EventsMemberService() diff --git a/chalicelib/services/EventService.py b/chalicelib/services/EventsRushService.py similarity index 54% rename from chalicelib/services/EventService.py rename to chalicelib/services/EventsRushService.py index 68cf7c4..9735887 100644 --- a/chalicelib/services/EventService.py +++ b/chalicelib/services/EventsRushService.py @@ -1,13 +1,11 @@ from chalicelib.modules.mongo import mongo_module -from chalice import NotFoundError, BadRequestError, UnauthorizedError +from chalice import BadRequestError, UnauthorizedError import json from bson import ObjectId import datetime -from chalicelib.modules.google_sheets import GoogleSheetsModule -from chalicelib.modules.ses import ses, SesDestination from chalicelib.s3 import s3 -class EventService: +class EventsRushService: class BSONEncoder(json.JSONEncoder): """JSON encoder that converts Mongo ObjectIds and datetime.datetime to strings.""" @@ -19,228 +17,8 @@ def default(self, o): return super().default(o) def __init__(self, mongo_module=mongo_module): - self.mongo_module = mongo_module - self.collection_prefix = "events-" - - def create_timeframe(self, timeframe_data: dict): - timeframe_data["dateCreated"] = datetime.datetime.now() - self.mongo_module.insert_document( - collection=f"{self.collection_prefix}timeframe", data=timeframe_data - ) - return {"msg": True} - - def get_timeframe(self, timeframe_id: str): - timeframe = self.mongo_module.get_document_by_id( - collection=f"{self.collection_prefix}timeframe", document_id=timeframe_id - ) - - return json.dumps(timeframe, cls=self.BSONEncoder) - - def get_all_timeframes(self): - """Retrieve all timeframes from the database.""" - timeframes = self.mongo_module.get_all_data_from_collection( - f"{self.collection_prefix}timeframe" - ) - - return json.dumps(timeframes, cls=self.BSONEncoder) - - def delete_timeframe(self, timeframe_id: str): - # Check if timeframe exists and if it doesn't return errors - timeframe = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}timeframe", timeframe_id - ) - - if timeframe is None: - raise NotFoundError(f"Timeframe with ID {timeframe_id} does not exist.") - - # If timeframe exists, get the eventIds (children) - event_ids = [str(event["_id"]) for event in timeframe["events"]] - - # Delete all the events in the timeframe - for event_id in event_ids: - self.mongo_module.delete_document_by_id( - f"{self.collection_prefix}event", event_id - ) - - # If timeframe exists, delete the timeframe document - self.mongo_module.delete_document_by_id( - f"{self.collection_prefix}timeframe", timeframe_id - ) - - return {"statusCode": 200} - - def create_event(self, timeframe_id: str, event_data: dict): - event_data["dateCreated"] = datetime.datetime.now() - event_data["timeframeId"] = timeframe_id - event_data["usersAttended"] = [] - - # Get Google Spreadsheet ID from timeframe - timeframe_doc = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}timeframe", timeframe_id - ) - spreadsheet_id = timeframe_doc["spreadsheetId"] - - # Add event name to Google Sheets - gs = GoogleSheetsModule() - col = gs.find_next_available_col(spreadsheet_id, event_data["sheetTab"]) - gs.add_event(spreadsheet_id, event_data["sheetTab"], event_data["name"], col) - - # Append next available col value to event data - event_data["spreadsheetCol"] = col - - # Insert the event in event collection - event_id = self.mongo_module.insert_document( - f"{self.collection_prefix}event", event_data - ) - - event_data["eventId"] = str(event_id) - - # Insert child event in timeframe collection - self.mongo_module.update_document( - f"{self.collection_prefix}timeframe", - timeframe_id, - {"$push": {"events": event_data}}, - ) - - return json.dumps(event_data, cls=self.BSONEncoder) - - def get_event(self, event_id: str): - event = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}event", event_id - ) - - return json.dumps(event, cls=self.BSONEncoder) - - def checkin(self, event_id: str, user: dict) -> dict: - """Checks in a user to an event. - - Arguments: - event_id {str} -- ID of the event to check into. - user {dict} -- Dictionary containing user ID and name. - - Returns: - dict -- Dictionary containing status and message. - """ - user_id, user_email = user["id"], user["email"] - member = self.mongo_module.get_document_by_id(f"users", user_id) - if member is None: - raise NotFoundError(f"User with ID {user_id} does not exist.") - - user_name = member["name"] - - event = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}event", event_id - ) - - if any(d["userId"] == user_id for d in event["usersAttended"]): - raise BadRequestError(f"{user_name} has already checked in.") - - checkin_data = { - "userId": user_id, - "name": user_name, - "dateCheckedIn": datetime.datetime.now(), - } - - # Get timeframe document to get Google Sheets info - timeframe = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}timeframe", event["timeframeId"] - ) - - # Get Google Sheets information - ss_id = timeframe["spreadsheetId"] - - # Initialize Google Sheets Module - gs = GoogleSheetsModule() - - # Find row in Google Sheets that matches user's email - row_num = gs.find_matching_email( - spreadsheet_id=ss_id, - sheet_name=event["sheetTab"], - col="C", - email_to_match=user_email, - ) - - if row_num == -1: - return { - "status": False, - "message": f"{user_name} was not found in the sheet.", - } - - # Update Google Sheets cell with a "1" if user has checked in - gs.update_row( - spreadsheet_id=ss_id, - sheet_name=event["sheetTab"], - col=event["spreadsheetCol"], - row=row_num + 1, - data=[["1"]], - ) - - # Update event collection with checkin data - self.mongo_module.update_document( - f"{self.collection_prefix}event", - event_id, - {"$push": {"usersAttended": checkin_data}}, - ) - - # Send email to user that has checked in - email_content = f""" - Hi {user_name},

- - Thank you for checking in to {event["name"]}! This is a confirmation that you have checked in.

- - Regards,
- PCT Tech Team

- - ** Please note: Do not reply to this email. This email is sent from an unattended mailbox. Replies will not be read. - """ - - ses_destination = SesDestination(tos=[user_email]) - ses.send_email( - source="checkin-bot@why-phi.com", - destination=ses_destination, - subject=f"Check-in Confirmation: {event['name']}", - text=email_content, - html=email_content, - ) - - return { - "status": True, - "message": f"{user_name} has successfully been checked in.", - } - - def delete(self, event_id: str): - # Check if event exists and if it doesn't return errors - event = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}event", event_id - ) - - if event is None: - raise NotFoundError(f"Event with ID {event_id} does not exist.") - - # If event exists, get the timeframeId (parent) - timeframe_id = event["timeframeId"] - - # Remove event from timeframe - self.mongo_module.update_document( - f"{self.collection_prefix}timeframe", - timeframe_id, - {"$pull": {"events": {"_id": ObjectId(event_id)}}}, - ) - - # Delete the event document - self.mongo_module.delete_document_by_id(f"{self.collection_prefix}event", event_id) - - def get_timeframe_sheets(self, timeframe_id: str): - timeframe = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}timeframe", timeframe_id - ) - - if "spreadsheetId" not in timeframe or timeframe["spreadsheetId"] == "": - return [] - - gs = GoogleSheetsModule() - sheets = gs.get_sheets(timeframe["spreadsheetId"], include_properties=False) - return [sheet["title"] for sheet in sheets] + self.mongo_module = mongo_module + self.collection_prefix = "events-" def get_rush_categories_and_events(self): rush_categories = self.mongo_module.get_all_data_from_collection( @@ -510,5 +288,4 @@ def delete_rush_event(self, event_id: str): except Exception as e: raise BadRequestError(e) - -event_service = EventService() +events_rush_service = EventsRushService() \ No newline at end of file diff --git a/tests/services/test_event_service.py b/tests/services/test_event_service.py index 1f25db5..937fc7a 100644 --- a/tests/services/test_event_service.py +++ b/tests/services/test_event_service.py @@ -1,6 +1,6 @@ import pytest from unittest.mock import MagicMock, patch -from chalicelib.services.EventService import EventService +from zap.chalicelib.services.EventsMemberService import EventService import datetime import json From 766273140c77a7ccabcc3f4300544509bcb03648 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 21 Jul 2024 15:13:13 -0400 Subject: [PATCH 14/34] fixed dictionary unpacking for updating event --- chalicelib/services/EventsRushService.py | 49 ++++++++++++------------ 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py index 9735887..59dbf56 100644 --- a/chalicelib/services/EventsRushService.py +++ b/chalicelib/services/EventsRushService.py @@ -47,14 +47,18 @@ def create_rush_event(self, data: dict): data["eventCoverImage"] = image_url # initialize attendee info (for rush-event) - data["attendees"] = [] - data["numAttendees"] = 0 - - data_copy = data.copy() - # Remove catgoryId, attendees, and numAttendees from rush category (shouldn't be persisted) - data_copy.pop("categoryId", None) - data_copy.pop("attendees", None) - data_copy.pop("numAttendees", None) + attendees_id = ObjectId() + attendees = { + "_id": attendees_id, + "eventId": event_id, + "attendees": [], + "numAtendees": 0 + } + self.mongo_module.insert_document( + collection=f"{self.collection_prefix}rush-event-attendees", + data=attendees + ) + data["attendeesId"] = attendees_id # Add event to its own collection self.mongo_module.insert_document( @@ -65,7 +69,7 @@ def create_rush_event(self, data: dict): self.mongo_module.update_document( f"{self.collection_prefix}rush", data["categoryId"], - {"$push": {"events": data_copy}}, + {"$push": { "events": data }}, ) return @@ -74,10 +78,10 @@ def modify_rush_event(self, data: dict): try: event_id = data["_id"] - object_event_id = ObjectId(event_id) + event_oid = ObjectId(event_id) data["lastModified"] = datetime.datetime.now() - data["_id"] = object_event_id + data["_id"] = event_oid # Check if event exists in the rush-event collection event = self.mongo_module.get_document_by_id( @@ -104,24 +108,19 @@ def modify_rush_event(self, data: dict): # add image_url to data object (this also replaces the original base64 image url) data["eventCoverImage"] = image_url - - # Merge the existing event data with the new data - updated_event = {**event, **data} - - # Remove catgoryId, attendees, and numAttendees from rush category (shouldn't be persisted) - updated_event.pop("categoryId", None) - updated_event.pop("attendees", None) - updated_event.pop("numAttendees", None) + # Merge data with event (from client + mongo) --> NOTE: event must be unpacked first so + # that data overrides the matching keys + data = { **event, **data } # Define array update query and filters update_query = { "$set": { - "events.$[eventElem]": updated_event + "events.$[eventElem]": data } } array_filters = [ - {"eventElem._id": object_event_id} + {"eventElem._id": event_oid} ] # Modify the event in its category (rush collection) @@ -251,11 +250,11 @@ def delete_rush_event(self, event_id: str): """ try: # Get event_id as ObjectId - object_event_id = ObjectId(event_id) + event_oid = ObjectId(event_id) # Check if event exists in the rush-event collection event = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}rush-event", object_event_id + f"{self.collection_prefix}rush-event", event_oid ) if not event: @@ -276,12 +275,12 @@ def delete_rush_event(self, event_id: str): self.mongo_module.update_document( f"{self.collection_prefix}rush", event_category_id, - {"$pull": {"events": {"_id": object_event_id}}}, + {"$pull": {"events": {"_id": event_oid}}}, ) # Delete event data from the rush-event collection self.mongo_module.delete_document_by_id( - f"{self.collection_prefix}rush-event", object_event_id + f"{self.collection_prefix}rush-event", event_oid ) return From 1c751e314d25621378ea4870f88934e7c7e9ce61 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 21 Jul 2024 15:37:38 -0400 Subject: [PATCH 15/34] checkin and delete event fully integrated with new attendees collection --- chalicelib/services/EventsRushService.py | 42 ++++++++++++++++-------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py index 59dbf56..6d3af39 100644 --- a/chalicelib/services/EventsRushService.py +++ b/chalicelib/services/EventsRushService.py @@ -52,7 +52,7 @@ def create_rush_event(self, data: dict): "_id": attendees_id, "eventId": event_id, "attendees": [], - "numAtendees": 0 + "numAttendees": 0 } self.mongo_module.insert_document( collection=f"{self.collection_prefix}rush-event-attendees", @@ -149,7 +149,6 @@ def modify_rush_event(self, data: dict): return - except Exception as e: print("error is ", e) raise BadRequestError(e) @@ -201,8 +200,7 @@ def get_rush_event(self, event_id: str, hide_attendees: bool = True): ) if hide_attendees: - event.pop("attendees", None) - event.pop("numAttendees", None) + event.pop("attendeesId", None) event.pop("code") @@ -212,6 +210,12 @@ def checkin_rush(self, event_id: str, user_data: dict): event = self.mongo_module.get_document_by_id( f"{self.collection_prefix}rush-event", event_id ) + + attendees_oid = event["attendeesId"] + + attendees = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}rush-event-attendees", attendees_oid + ) code = user_data["code"] user_data.pop("code") @@ -219,17 +223,18 @@ def checkin_rush(self, event_id: str, user_data: dict): if code != event["code"]: raise UnauthorizedError("Invalid code.") - if any(d["email"] == user_data["email"] for d in event["attendees"]): + if any(d["email"] == user_data["email"] for d in attendees["attendees"]): raise BadRequestError("User has already checked in.") user_data["checkinTime"] = datetime.datetime.now() - event["attendees"].append(user_data) - event["numAttendees"] += 1 + attendees["attendees"].append(user_data) + attendees["numAttendees"] += 1 + # Update attendees collection with data self.mongo_module.update_document( - f"{self.collection_prefix}rush-event", - event_id, - {"$set": event}, + collection=f"{self.collection_prefix}rush-event-attendees", + document_id=attendees_oid, + query={"$set": attendees}, ) return @@ -260,6 +265,7 @@ def delete_rush_event(self, event_id: str): if not event: raise Exception("Event does not exist.") + attendees_oid = event["attendeesId"] event_category_id = event["categoryId"] # Get eventCoverImage path @@ -273,15 +279,23 @@ def delete_rush_event(self, event_id: str): # Delete the event from its category self.mongo_module.update_document( - f"{self.collection_prefix}rush", - event_category_id, - {"$pull": {"events": {"_id": event_oid}}}, + collection=f"{self.collection_prefix}rush", + document_id=event_category_id, + query={"$pull": {"events": {"_id": event_oid}}}, ) # Delete event data from the rush-event collection self.mongo_module.delete_document_by_id( - f"{self.collection_prefix}rush-event", event_oid + collection=f"{self.collection_prefix}rush-event", + document_id=event_oid + ) + + # Delete attendees data from rush-event-attendees collection + self.mongo_module.delete_document_by_id( + collection=f"{self.collection_prefix}rush-event-attendees", + document_id=attendees_oid ) + return except Exception as e: From a3bb047e1b7294ea98d8d91cdc63165e502079b4 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 21 Jul 2024 15:42:33 -0400 Subject: [PATCH 16/34] edited pytest APIs --- tests/api/test_events copy.py | 153 +++++++++++++ tests/api/test_events.py | 326 --------------------------- tests/api/test_events_members.py | 178 +++++++++++++++ tests/services/test_event_service.py | 156 ++++++------- 4 files changed, 409 insertions(+), 404 deletions(-) create mode 100644 tests/api/test_events copy.py delete mode 100644 tests/api/test_events.py create mode 100644 tests/api/test_events_members.py diff --git a/tests/api/test_events copy.py b/tests/api/test_events copy.py new file mode 100644 index 0000000..e79cad3 --- /dev/null +++ b/tests/api/test_events copy.py @@ -0,0 +1,153 @@ +from chalice.test import Client +from unittest.mock import patch +import json + +from app import app + +with open("tests/fixtures/events/general/sample_rush_events.json") as f: + SAMPLE_RUSH_EVENTS = json.load(f) + +with open("tests/fixtures/events/general/sample_rush_event.json") as f: + SAMPLE_RUSH_EVENT = json.load(f) + +def test_get_rush_events(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.get_rush_categories_and_events", + ) as mock_get_rush_categories_and_events: + mock_get_rush_categories_and_events.return_value = SAMPLE_RUSH_EVENTS + response = client.http.get( + f"/events/rush", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_RUSH_EVENTS + +def test_get_rush_event(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.get_rush_event", + ) as mock_get_rush_event: + mock_get_rush_event.return_value = SAMPLE_RUSH_EVENT + response = client.http.get( + f"/events/rush/test_event_id", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_RUSH_EVENT + +def test_create_rush_category(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.create_rush_category" + ) as mock_create_rush_category: + mock_create_rush_category.return_value = {"msg": True} + response = client.http.post( + f"/events/rush/category", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + +def test_create_rush_event(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.create_rush_event" + ) as mock_create_rush_event: + mock_create_rush_event.return_value = {"msg": True} + response = client.http.post( + f"/events/rush", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + +def test_modify_rush_event(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.modify_rush_event" + ) as mock_modify_rush_event: + mock_modify_rush_event.return_value = {"msg": True} + response = client.http.patch( + f"/events/rush", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + +def test_modify_rush_settings(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.modify_rush_settings" + ) as mock_modify_rush_settings: + mock_modify_rush_settings.return_value = {"msg": True} + response = client.http.patch( + f"/events/rush/settings", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + +def test_checkin_rush(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.checkin_rush" + ) as mock_checkin_rush: + mock_checkin_rush.return_value = {"msg": True} + response = client.http.post( + f"/events/rush/checkin/test_event_id", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + +def test_delete_rush_event(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsRushService.events_rush_service.delete_rush_event", + ) as mock_delete_rush_event: + mock_delete_rush_event.return_value = {"status": True} + response = client.http.delete( + f"/events/rush/test_event_id", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {'status': True} \ No newline at end of file diff --git a/tests/api/test_events.py b/tests/api/test_events.py deleted file mode 100644 index c6a1b58..0000000 --- a/tests/api/test_events.py +++ /dev/null @@ -1,326 +0,0 @@ -from chalice.test import Client -from unittest.mock import patch -import json - -from app import app - -with open("tests/fixtures/events/general/sample_timeframes.json") as f: - SAMPLE_TIMEFRAMES = json.load(f) - -with open("tests/fixtures/events/general/sample_timeframe_sheets.json") as f: - SAMPLE_TIMEFRAME_SHEETS = json.load(f) - -with open("tests/fixtures/events/general/sample_rush_events.json") as f: - SAMPLE_RUSH_EVENTS = json.load(f) - -with open("tests/fixtures/events/general/sample_rush_event.json") as f: - SAMPLE_RUSH_EVENT = json.load(f) - -def test_create_timeframe(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.create_timeframe" - ) as mock_create_timeframe: - mock_create_timeframe.return_value = {"msg": True} - response = client.http.post( - f"/timeframes", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - - -def test_get_all_timeframes(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_all_timeframes", - ) as mock_get_all_timeframes: - mock_get_all_timeframes.return_value = SAMPLE_TIMEFRAMES - response = client.http.get( - f"/timeframes", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_TIMEFRAMES - -def test_get_timeframe(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_timeframe", - ) as mock_get_timeframe: - mock_get_timeframe.return_value = SAMPLE_TIMEFRAMES[0] - response = client.http.get( - f"/timeframes/test_timeframe_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_TIMEFRAMES[0] - -def test_delete_timeframe(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.delete_timeframe", - ) as mock_delete: - mock_delete.return_value = {"status": True} - response = client.http.delete( - f"/timeframes/{SAMPLE_TIMEFRAMES[0]['_id']}", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {'status': True} - - -def test_create_event(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.create_event" - ) as mock_create_event: - mock_create_event.return_value = {"msg": True} - response = client.http.post( - f"/timeframes/{SAMPLE_TIMEFRAMES[0]['_id']}/events", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_get_event(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_timeframe", - ) as mock_get_timeframe: - mock_get_timeframe.return_value = SAMPLE_TIMEFRAMES[0] - response = client.http.get( - f"/timeframes/test_timeframe_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_TIMEFRAMES[0] - -def test_get_timeframe_sheets(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_timeframe_sheets", - ) as mock_get_timeframe_sheets: - mock_get_timeframe_sheets.return_value = SAMPLE_TIMEFRAME_SHEETS - response = client.http.get( - f"/timeframes/test_timeframe_id/sheets", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_TIMEFRAME_SHEETS - -def test_checkin(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.checkin" - ) as mock_checkin: - mock_checkin.return_value = {"msg": True} - response = client.http.post( - f"/events/test_event_id/checkin", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_delete_event(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.delete", - ) as mock_delete: - mock_delete.return_value = {"status": True} - response = client.http.delete( - f"/events/test_event_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {'status': True} - -def test_get_rush_events(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_rush_categories_and_events", - ) as mock_get_rush_categories_and_events: - mock_get_rush_categories_and_events.return_value = SAMPLE_RUSH_EVENTS - response = client.http.get( - f"/events/rush", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_RUSH_EVENTS - -def test_get_rush_event(): - # Create a Chalice test client - with Client(app) as client: - # Mock applicant_service's get method - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.get_rush_event", - ) as mock_get_rush_event: - mock_get_rush_event.return_value = SAMPLE_RUSH_EVENT - response = client.http.get( - f"/events/rush/test_event_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - # Check the response status code and body - assert response.status_code == 200 - assert response.json_body == SAMPLE_RUSH_EVENT - -def test_create_rush_category(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.create_rush_category" - ) as mock_create_rush_category: - mock_create_rush_category.return_value = {"msg": True} - response = client.http.post( - f"/events/rush/category", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_create_rush_event(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.create_rush_event" - ) as mock_create_rush_event: - mock_create_rush_event.return_value = {"msg": True} - response = client.http.post( - f"/events/rush", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_modify_rush_event(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.modify_rush_event" - ) as mock_modify_rush_event: - mock_modify_rush_event.return_value = {"msg": True} - response = client.http.patch( - f"/events/rush", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_modify_rush_settings(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.modify_rush_settings" - ) as mock_modify_rush_settings: - mock_modify_rush_settings.return_value = {"msg": True} - response = client.http.patch( - f"/events/rush/settings", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_checkin_rush(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.checkin_rush" - ) as mock_checkin_rush: - mock_checkin_rush.return_value = {"msg": True} - response = client.http.post( - f"/events/rush/checkin/test_event_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {"msg": True} - -def test_delete_rush_event(): - with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: - # Assuming the decoded token has the required role - mock_decode.return_value = {"role": "admin"} - with patch( - "chalicelib.services.EventService.event_service.delete_rush_event", - ) as mock_delete_rush_event: - mock_delete_rush_event.return_value = {"status": True} - response = client.http.delete( - f"/events/rush/test_event_id", - headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, - ) - - assert response.status_code == 200 - assert response.json_body == {'status': True} \ No newline at end of file diff --git a/tests/api/test_events_members.py b/tests/api/test_events_members.py new file mode 100644 index 0000000..a16ec19 --- /dev/null +++ b/tests/api/test_events_members.py @@ -0,0 +1,178 @@ +from chalice.test import Client +from unittest.mock import patch +import json + +from app import app + +with open("tests/fixtures/events/general/sample_timeframes.json") as f: + SAMPLE_TIMEFRAMES = json.load(f) + +with open("tests/fixtures/events/general/sample_timeframe_sheets.json") as f: + SAMPLE_TIMEFRAME_SHEETS = json.load(f) + +def test_create_timeframe(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.create_timeframe" + ) as mock_create_timeframe: + mock_create_timeframe.return_value = {"msg": True} + response = client.http.post( + f"/timeframes", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + + +def test_get_all_timeframes(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.get_all_timeframes", + ) as mock_get_all_timeframes: + mock_get_all_timeframes.return_value = SAMPLE_TIMEFRAMES + response = client.http.get( + f"/timeframes", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_TIMEFRAMES + +def test_get_timeframe(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.get_timeframe", + ) as mock_get_timeframe: + mock_get_timeframe.return_value = SAMPLE_TIMEFRAMES[0] + response = client.http.get( + f"/timeframes/test_timeframe_id", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_TIMEFRAMES[0] + +def test_delete_timeframe(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.delete_timeframe", + ) as mock_delete: + mock_delete.return_value = {"status": True} + response = client.http.delete( + f"/timeframes/{SAMPLE_TIMEFRAMES[0]['_id']}", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {'status': True} + + +def test_create_event(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.create_event" + ) as mock_create_event: + mock_create_event.return_value = {"msg": True} + response = client.http.post( + f"/timeframes/{SAMPLE_TIMEFRAMES[0]['_id']}/events", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + +def test_get_event(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.get_timeframe", + ) as mock_get_timeframe: + mock_get_timeframe.return_value = SAMPLE_TIMEFRAMES[0] + response = client.http.get( + f"/timeframes/test_timeframe_id", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_TIMEFRAMES[0] + +def test_get_timeframe_sheets(): + # Create a Chalice test client + with Client(app) as client: + # Mock applicant_service's get method + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.get_timeframe_sheets", + ) as mock_get_timeframe_sheets: + mock_get_timeframe_sheets.return_value = SAMPLE_TIMEFRAME_SHEETS + response = client.http.get( + f"/timeframes/test_timeframe_id/sheets", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == SAMPLE_TIMEFRAME_SHEETS + +def test_checkin(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.checkin" + ) as mock_checkin: + mock_checkin.return_value = {"msg": True} + response = client.http.post( + f"/events/test_event_id/checkin", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {"msg": True} + +def test_delete_event(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + # Assuming the decoded token has the required role + mock_decode.return_value = {"role": "admin"} + with patch( + "chalicelib.services.EventsMemberService.events_member_service.delete", + ) as mock_delete: + mock_delete.return_value = {"status": True} + response = client.http.delete( + f"/events/test_event_id", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + + assert response.status_code == 200 + assert response.json_body == {'status': True} \ No newline at end of file diff --git a/tests/services/test_event_service.py b/tests/services/test_event_service.py index 937fc7a..bd8ce44 100644 --- a/tests/services/test_event_service.py +++ b/tests/services/test_event_service.py @@ -1,99 +1,99 @@ -import pytest -from unittest.mock import MagicMock, patch -from zap.chalicelib.services.EventsMemberService import EventService -import datetime -import json - -with open("tests/fixtures/events/general/sample_timeframes.json") as f: - SAMPLE_TIMEFRAMES: list = json.load(f) - -with open("tests/fixtures/events/general/sample_timeframe_sheets.json") as f: - SAMPLE_TIMEFRAME_SHEETS: list = json.load(f) - -with open("tests/fixtures/events/general/sample_rush_events.json") as f: - SAMPLE_RUSH_EVENTS: list = json.load(f) - -with open("tests/fixtures/events/general/sample_rush_event.json") as f: - SAMPLE_RUSH_EVENT = json.load(f) - -@pytest.fixture -def mock_mongo_module(): - with patch("chalicelib.modules.mongo.MongoModule", autospec=True) as MockMongoModule: - mock_instance = MockMongoModule(use_mock=True) - yield mock_instance - -@pytest.fixture -def event_service(mock_mongo_module): - return EventService(mock_mongo_module) - -def test_insert_document(event_service, mock_mongo_module): - CREATE_TIMEFRAME = { "name": "testTimeframeName", "spreadsheetId": "testSpreadsheetId" } - date_created = datetime.datetime.now() - - with patch("chalicelib.services.EventService.datetime") as mock_datetime: - mock_datetime.datetime.now.return_value = date_created - result = event_service.create_timeframe(CREATE_TIMEFRAME) - CREATE_TIMEFRAME["date_created"] = date_created - mock_mongo_module.insert_document.assert_called_once_with( - collection="events-timeframe", data=CREATE_TIMEFRAME - ) - - assert result == {"msg": True} - -def test_get_timeframe(event_service, mock_mongo_module): - mock_mongo_module.get_document_by_id.return_value = SAMPLE_TIMEFRAMES[0] - timeframe_id = SAMPLE_TIMEFRAMES[0]["_id"] +# import pytest +# from unittest.mock import MagicMock, patch +# from zap.chalicelib.services.EventsMemberService import EventService +# import datetime +# import json + +# with open("tests/fixtures/events/general/sample_timeframes.json") as f: +# SAMPLE_TIMEFRAMES: list = json.load(f) + +# with open("tests/fixtures/events/general/sample_timeframe_sheets.json") as f: +# SAMPLE_TIMEFRAME_SHEETS: list = json.load(f) + +# with open("tests/fixtures/events/general/sample_rush_events.json") as f: +# SAMPLE_RUSH_EVENTS: list = json.load(f) + +# with open("tests/fixtures/events/general/sample_rush_event.json") as f: +# SAMPLE_RUSH_EVENT = json.load(f) + +# @pytest.fixture +# def mock_mongo_module(): +# with patch("chalicelib.modules.mongo.MongoModule", autospec=True) as MockMongoModule: +# mock_instance = MockMongoModule(use_mock=True) +# yield mock_instance + +# @pytest.fixture +# def event_service(mock_mongo_module): +# return EventService(mock_mongo_module) + +# def test_insert_document(event_service, mock_mongo_module): +# CREATE_TIMEFRAME = { "name": "testTimeframeName", "spreadsheetId": "testSpreadsheetId" } +# date_created = datetime.datetime.now() + +# with patch("chalicelib.services.EventService.datetime") as mock_datetime: +# mock_datetime.datetime.now.return_value = date_created +# result = event_service.create_timeframe(CREATE_TIMEFRAME) +# CREATE_TIMEFRAME["date_created"] = date_created +# mock_mongo_module.insert_document.assert_called_once_with( +# collection="events-timeframe", data=CREATE_TIMEFRAME +# ) + +# assert result == {"msg": True} + +# def test_get_timeframe(event_service, mock_mongo_module): +# mock_mongo_module.get_document_by_id.return_value = SAMPLE_TIMEFRAMES[0] +# timeframe_id = SAMPLE_TIMEFRAMES[0]["_id"] - result = event_service.get_timeframe(timeframe_id=timeframe_id) - mock_mongo_module.get_document_by_id.assert_called_once_with( - collection="events-timeframe", document_id=timeframe_id - ) +# result = event_service.get_timeframe(timeframe_id=timeframe_id) +# mock_mongo_module.get_document_by_id.assert_called_once_with( +# collection="events-timeframe", document_id=timeframe_id +# ) - assert result == json.dumps(SAMPLE_TIMEFRAMES[0]) +# assert result == json.dumps(SAMPLE_TIMEFRAMES[0]) -def test_get_all_timeframe(event_service, mock_mongo_module): - mock_mongo_module.get_all_data_from_collection.return_value = SAMPLE_TIMEFRAMES +# def test_get_all_timeframe(event_service, mock_mongo_module): +# mock_mongo_module.get_all_data_from_collection.return_value = SAMPLE_TIMEFRAMES - result = event_service.get_all_timeframes() - mock_mongo_module.get_all_data_from_collection.assert_called_once_with( - collection="events-timeframe" - ) +# result = event_service.get_all_timeframes() +# mock_mongo_module.get_all_data_from_collection.assert_called_once_with( +# collection="events-timeframe" +# ) - assert result == json.dumps(SAMPLE_TIMEFRAMES) +# assert result == json.dumps(SAMPLE_TIMEFRAMES) -def test_delete_timeframe(event_service, mock_mongo_module): - result = event_service.delete_timeframe(timeframe_id=SAMPLE_TIMEFRAMES[0]["_id"]) - assert result["statusCode"] == 200 +# def test_delete_timeframe(event_service, mock_mongo_module): +# result = event_service.delete_timeframe(timeframe_id=SAMPLE_TIMEFRAMES[0]["_id"]) +# assert result["statusCode"] == 200 -# TODO: potentially add mocking for GoogleSheetsModule (otherwise test is not isolated) -# def test_create_event(event_service, mock_mongo_module): -# CREATE_TIMEFRAME = { "name": "eventName", "tags": "tags", "sheetTab": "selectedSheetTab" } -# date_created = datetime.datetime.now() -# timeframe_id = "timeframeId" +# # TODO: potentially add mocking for GoogleSheetsModule (otherwise test is not isolated) +# # def test_create_event(event_service, mock_mongo_module): +# # CREATE_TIMEFRAME = { "name": "eventName", "tags": "tags", "sheetTab": "selectedSheetTab" } +# # date_created = datetime.datetime.now() +# # timeframe_id = "timeframeId" -# TODO: +# # TODO: -# def get_event(self, event_id: str): +# # def get_event(self, event_id: str): -# def checkin(self, event_id: str, user: dict) -> dict: +# # def checkin(self, event_id: str, user: dict) -> dict: -# def delete(self, event_id: str): +# # def delete(self, event_id: str): -# def get_timeframe_sheets(self, timeframe_id: str): +# # def get_timeframe_sheets(self, timeframe_id: str): -# def get_rush_categories_and_events(self): +# # def get_rush_categories_and_events(self): -# def create_rush_category(self, data: dict): +# # def create_rush_category(self, data: dict): -# def create_rush_event(self, data: dict): +# # def create_rush_event(self, data: dict): -# def modify_rush_event(self, data: dict): +# # def modify_rush_event(self, data: dict): -# def modify_rush_settings(self, data: dict): +# # def modify_rush_settings(self, data: dict): -# def get_rush_event(self, event_id: str, hide_attendees: bool = True): +# # def get_rush_event(self, event_id: str, hide_attendees: bool = True): -# def checkin_rush(self, event_id: str, user_data: dict): +# # def checkin_rush(self, event_id: str, user_data: dict): -# def delete_rush_event(self, event_id: str): +# # def delete_rush_event(self, event_id: str): From 46087fece4090e55de8c75a552d87b9214639808 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 21 Jul 2024 15:45:52 -0400 Subject: [PATCH 17/34] refactored events testing --- ...est_events copy.py => test_events_rush.py} | 0 tests/services/test_event_service.py | 99 ----------------- tests/services/test_events_member_service.py | 101 ++++++++++++++++++ 3 files changed, 101 insertions(+), 99 deletions(-) rename tests/api/{test_events copy.py => test_events_rush.py} (100%) delete mode 100644 tests/services/test_event_service.py create mode 100644 tests/services/test_events_member_service.py diff --git a/tests/api/test_events copy.py b/tests/api/test_events_rush.py similarity index 100% rename from tests/api/test_events copy.py rename to tests/api/test_events_rush.py diff --git a/tests/services/test_event_service.py b/tests/services/test_event_service.py deleted file mode 100644 index bd8ce44..0000000 --- a/tests/services/test_event_service.py +++ /dev/null @@ -1,99 +0,0 @@ -# import pytest -# from unittest.mock import MagicMock, patch -# from zap.chalicelib.services.EventsMemberService import EventService -# import datetime -# import json - -# with open("tests/fixtures/events/general/sample_timeframes.json") as f: -# SAMPLE_TIMEFRAMES: list = json.load(f) - -# with open("tests/fixtures/events/general/sample_timeframe_sheets.json") as f: -# SAMPLE_TIMEFRAME_SHEETS: list = json.load(f) - -# with open("tests/fixtures/events/general/sample_rush_events.json") as f: -# SAMPLE_RUSH_EVENTS: list = json.load(f) - -# with open("tests/fixtures/events/general/sample_rush_event.json") as f: -# SAMPLE_RUSH_EVENT = json.load(f) - -# @pytest.fixture -# def mock_mongo_module(): -# with patch("chalicelib.modules.mongo.MongoModule", autospec=True) as MockMongoModule: -# mock_instance = MockMongoModule(use_mock=True) -# yield mock_instance - -# @pytest.fixture -# def event_service(mock_mongo_module): -# return EventService(mock_mongo_module) - -# def test_insert_document(event_service, mock_mongo_module): -# CREATE_TIMEFRAME = { "name": "testTimeframeName", "spreadsheetId": "testSpreadsheetId" } -# date_created = datetime.datetime.now() - -# with patch("chalicelib.services.EventService.datetime") as mock_datetime: -# mock_datetime.datetime.now.return_value = date_created -# result = event_service.create_timeframe(CREATE_TIMEFRAME) -# CREATE_TIMEFRAME["date_created"] = date_created -# mock_mongo_module.insert_document.assert_called_once_with( -# collection="events-timeframe", data=CREATE_TIMEFRAME -# ) - -# assert result == {"msg": True} - -# def test_get_timeframe(event_service, mock_mongo_module): -# mock_mongo_module.get_document_by_id.return_value = SAMPLE_TIMEFRAMES[0] -# timeframe_id = SAMPLE_TIMEFRAMES[0]["_id"] - -# result = event_service.get_timeframe(timeframe_id=timeframe_id) -# mock_mongo_module.get_document_by_id.assert_called_once_with( -# collection="events-timeframe", document_id=timeframe_id -# ) - -# assert result == json.dumps(SAMPLE_TIMEFRAMES[0]) - -# def test_get_all_timeframe(event_service, mock_mongo_module): -# mock_mongo_module.get_all_data_from_collection.return_value = SAMPLE_TIMEFRAMES - -# result = event_service.get_all_timeframes() -# mock_mongo_module.get_all_data_from_collection.assert_called_once_with( -# collection="events-timeframe" -# ) - -# assert result == json.dumps(SAMPLE_TIMEFRAMES) - -# def test_delete_timeframe(event_service, mock_mongo_module): -# result = event_service.delete_timeframe(timeframe_id=SAMPLE_TIMEFRAMES[0]["_id"]) -# assert result["statusCode"] == 200 - - -# # TODO: potentially add mocking for GoogleSheetsModule (otherwise test is not isolated) -# # def test_create_event(event_service, mock_mongo_module): -# # CREATE_TIMEFRAME = { "name": "eventName", "tags": "tags", "sheetTab": "selectedSheetTab" } -# # date_created = datetime.datetime.now() -# # timeframe_id = "timeframeId" - -# # TODO: - -# # def get_event(self, event_id: str): - -# # def checkin(self, event_id: str, user: dict) -> dict: - -# # def delete(self, event_id: str): - -# # def get_timeframe_sheets(self, timeframe_id: str): - -# # def get_rush_categories_and_events(self): - -# # def create_rush_category(self, data: dict): - -# # def create_rush_event(self, data: dict): - -# # def modify_rush_event(self, data: dict): - -# # def modify_rush_settings(self, data: dict): - -# # def get_rush_event(self, event_id: str, hide_attendees: bool = True): - -# # def checkin_rush(self, event_id: str, user_data: dict): - -# # def delete_rush_event(self, event_id: str): diff --git a/tests/services/test_events_member_service.py b/tests/services/test_events_member_service.py new file mode 100644 index 0000000..6b6c481 --- /dev/null +++ b/tests/services/test_events_member_service.py @@ -0,0 +1,101 @@ +import pytest +from unittest.mock import MagicMock, patch +from chalicelib.services.EventsMemberService import EventsMemberService +import datetime +import json + +with open("tests/fixtures/events/general/sample_timeframes.json") as f: + SAMPLE_TIMEFRAMES: list = json.load(f) + +with open("tests/fixtures/events/general/sample_timeframe_sheets.json") as f: + SAMPLE_TIMEFRAME_SHEETS: list = json.load(f) + +with open("tests/fixtures/events/general/sample_rush_events.json") as f: + SAMPLE_RUSH_EVENTS: list = json.load(f) + +with open("tests/fixtures/events/general/sample_rush_event.json") as f: + SAMPLE_RUSH_EVENT = json.load(f) + +@pytest.fixture +def mock_mongo_module(): + with patch("chalicelib.modules.mongo.MongoModule", autospec=True) as MockMongoModule: + mock_instance = MockMongoModule(use_mock=True) + yield mock_instance + +@pytest.fixture +def event_service(mock_mongo_module): + return EventsMemberService(mock_mongo_module) + +def test_insert_document(event_service, mock_mongo_module): + CREATE_TIMEFRAME = { "name": "testTimeframeName", "spreadsheetId": "testSpreadsheetId" } + date_created = datetime.datetime.now() + + with patch("chalicelib.services.EventsMemberService.datetime") as mock_datetime: + mock_datetime.datetime.now.return_value = date_created + result = event_service.create_timeframe(CREATE_TIMEFRAME) + CREATE_TIMEFRAME["date_created"] = date_created + mock_mongo_module.insert_document.assert_called_once_with( + collection="events-timeframe", data=CREATE_TIMEFRAME + ) + + assert result == {"msg": True} + +def test_get_timeframe(event_service, mock_mongo_module): + mock_mongo_module.get_document_by_id.return_value = SAMPLE_TIMEFRAMES[0] + timeframe_id = SAMPLE_TIMEFRAMES[0]["_id"] + + result = event_service.get_timeframe(timeframe_id=timeframe_id) + mock_mongo_module.get_document_by_id.assert_called_once_with( + collection="events-timeframe", document_id=timeframe_id + ) + + assert result == json.dumps(SAMPLE_TIMEFRAMES[0]) + +def test_get_all_timeframe(event_service, mock_mongo_module): + mock_mongo_module.get_all_data_from_collection.return_value = SAMPLE_TIMEFRAMES + + result = event_service.get_all_timeframes() + mock_mongo_module.get_all_data_from_collection.assert_called_once_with( + collection="events-timeframe" + ) + + assert result == json.dumps(SAMPLE_TIMEFRAMES) + +def test_delete_timeframe(event_service, mock_mongo_module): + result = event_service.delete_timeframe(timeframe_id=SAMPLE_TIMEFRAMES[0]["_id"]) + assert result["statusCode"] == 200 + + +# TODO: potentially add mocking for GoogleSheetsModule (otherwise test is not isolated) +# def test_create_event(event_service, mock_mongo_module): +# CREATE_TIMEFRAME = { "name": "eventName", "tags": "tags", "sheetTab": "selectedSheetTab" } +# date_created = datetime.datetime.now() +# timeframe_id = "timeframeId" + +# TODO: test_events_member + +# def get_event(self, event_id: str): + +# def checkin(self, event_id: str, user: dict) -> dict: + +# def delete(self, event_id: str): + +# def get_timeframe_sheets(self, timeframe_id: str): + +# TODO: test_events_rush + +# def get_rush_categories_and_events(self): + +# def create_rush_category(self, data: dict): + +# def create_rush_event(self, data: dict): + +# def modify_rush_event(self, data: dict): + +# def modify_rush_settings(self, data: dict): + +# def get_rush_event(self, event_id: str, hide_attendees: bool = True): + +# def checkin_rush(self, event_id: str, user_data: dict): + +# def delete_rush_event(self, event_id: str): From 04e8c5d05a77f22c1bb9308c8bae281a03602e1e Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Wed, 24 Jul 2024 21:22:43 -0400 Subject: [PATCH 18/34] added functionality to get all events for rush category --- chalicelib/api/events_rush.py | 5 +++++ chalicelib/modules/mongo.py | 17 +++++++++++----- chalicelib/services/EventsMemberService.py | 2 +- chalicelib/services/EventsRushService.py | 21 ++++++++++++++++++-- chalicelib/services/MemberService.py | 2 +- tests/services/test_events_member_service.py | 4 ++-- 6 files changed, 40 insertions(+), 11 deletions(-) diff --git a/chalicelib/api/events_rush.py b/chalicelib/api/events_rush.py index f33cecd..2385503 100644 --- a/chalicelib/api/events_rush.py +++ b/chalicelib/api/events_rush.py @@ -49,6 +49,11 @@ def checkin_rush(event_id): return events_rush_service.checkin_rush(event_id, data) +@events_rush_api.route("/events/rush/default", methods=["GET"], cors=True) +def get_rush_events_default_category(): + return events_rush_service.get_rush_events_default_category() + + @events_rush_api.route("/events/rush/{event_id}", methods=["DELETE"], cors=True) def delete_rush_event(event_id): return events_rush_service.delete_rush_event(event_id) \ No newline at end of file diff --git a/chalicelib/modules/mongo.py b/chalicelib/modules/mongo.py index f68b693..1bbc465 100644 --- a/chalicelib/modules/mongo.py +++ b/chalicelib/modules/mongo.py @@ -105,14 +105,21 @@ def find_one_document(self, collection: str, query: dict): raise @add_env_suffix - def get_all_data_from_collection(self, collection: str): - """Fetches all data from the specified collection.""" - if collection is None: - raise ValueError("The 'collection' parameter cannot be None") + def get_data_from_collection(self, collection: str, filter: dict = {}): + """ + Fetches all data from the specified collection. + + Args: + collection (str): The name of the collection to update the document in. + filter (dict, optional): Dictionary of filters to be applied to collection query. + + Returns: + list: List of documents in the collection that match filter. + """ try: # Use the specified collection to fetch data - cursor = self.mongo_client.vault[collection].find() + cursor = self.mongo_client.vault[collection].find(filter) data = list(cursor) # Convert the cursor to a list if data is None: diff --git a/chalicelib/services/EventsMemberService.py b/chalicelib/services/EventsMemberService.py index 8ddeaec..b2c61cc 100644 --- a/chalicelib/services/EventsMemberService.py +++ b/chalicelib/services/EventsMemberService.py @@ -37,7 +37,7 @@ def get_timeframe(self, timeframe_id: str): def get_all_timeframes(self): """Retrieve all timeframes from the database.""" - timeframes = self.mongo_module.get_all_data_from_collection( + timeframes = self.mongo_module.get_data_from_collection( f"{self.collection_prefix}timeframe" ) diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py index 6d3af39..f2513d8 100644 --- a/chalicelib/services/EventsRushService.py +++ b/chalicelib/services/EventsRushService.py @@ -21,8 +21,8 @@ def __init__(self, mongo_module=mongo_module): self.collection_prefix = "events-" def get_rush_categories_and_events(self): - rush_categories = self.mongo_module.get_all_data_from_collection( - f"{self.collection_prefix}rush" + rush_categories = self.mongo_module.get_data_from_collection( + collection=f"{self.collection_prefix}rush" ) return json.dumps(rush_categories, cls=self.BSONEncoder) @@ -238,6 +238,23 @@ def checkin_rush(self, event_id: str, user_data: dict): ) return + + def get_rush_events_default_category(self): + rush_categories = self.mongo_module.get_data_from_collection( + collection=f"{self.collection_prefix}rush", + filter={"defaultRushCategory": True} + ) + + if len(rush_categories) == 0: + return [] + + rush_category = rush_categories[0] + + # remove code from every rush event + for event in rush_category["events"]: + event.pop("code", None) + + return json.dumps(rush_category, cls=self.BSONEncoder) def delete_rush_event(self, event_id: str): """ diff --git a/chalicelib/services/MemberService.py b/chalicelib/services/MemberService.py index 92c70e6..245605d 100644 --- a/chalicelib/services/MemberService.py +++ b/chalicelib/services/MemberService.py @@ -73,7 +73,7 @@ def delete(self, data: list[str]) -> dict: def get_all(self): - data = mongo_module.get_all_data_from_collection(self.collection) + data = mongo_module.get_data_from_collection(self.collection) return json.dumps(data, cls=self.BSONEncoder) def onboard(self, document_id=str, data=dict) -> bool: diff --git a/tests/services/test_events_member_service.py b/tests/services/test_events_member_service.py index 6b6c481..850e8ae 100644 --- a/tests/services/test_events_member_service.py +++ b/tests/services/test_events_member_service.py @@ -52,10 +52,10 @@ def test_get_timeframe(event_service, mock_mongo_module): assert result == json.dumps(SAMPLE_TIMEFRAMES[0]) def test_get_all_timeframe(event_service, mock_mongo_module): - mock_mongo_module.get_all_data_from_collection.return_value = SAMPLE_TIMEFRAMES + mock_mongo_module.get_data_from_collection.return_value = SAMPLE_TIMEFRAMES result = event_service.get_all_timeframes() - mock_mongo_module.get_all_data_from_collection.assert_called_once_with( + mock_mongo_module.get_data_from_collection.assert_called_once_with( collection="events-timeframe" ) From baa1ed8ac7d10d9dab1920110c9748bf2a5217a1 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:18:24 -0400 Subject: [PATCH 19/34] re-embedded applicants and fixed checkin bug --- chalicelib/services/EventsRushService.py | 70 ++++++++++++------------ 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py index f2513d8..143711a 100644 --- a/chalicelib/services/EventsRushService.py +++ b/chalicelib/services/EventsRushService.py @@ -38,6 +38,8 @@ def create_rush_event(self, data: dict): data["dateCreated"] = datetime.datetime.now() data["lastModified"] = data["dateCreated"] data["_id"] = event_id + data["attendees"] = [] + data["numAttendees"] = 0 # upload eventCoverImage to s3 bucket (convert everything to png files for now... can adjust later) image_path = f"image/rush/{data['categoryId']}/{event_id}.png" @@ -46,20 +48,6 @@ def create_rush_event(self, data: dict): # add image_url to data object (this also replaces the original base64 image url) data["eventCoverImage"] = image_url - # initialize attendee info (for rush-event) - attendees_id = ObjectId() - attendees = { - "_id": attendees_id, - "eventId": event_id, - "attendees": [], - "numAttendees": 0 - } - self.mongo_module.insert_document( - collection=f"{self.collection_prefix}rush-event-attendees", - data=attendees - ) - data["attendeesId"] = attendees_id - # Add event to its own collection self.mongo_module.insert_document( f"{self.collection_prefix}rush-event", data @@ -207,34 +195,55 @@ def get_rush_event(self, event_id: str, hide_attendees: bool = True): return json.dumps(event, cls=self.BSONEncoder) def checkin_rush(self, event_id: str, user_data: dict): + event_oid = ObjectId(event_id) + event = self.mongo_module.get_document_by_id( f"{self.collection_prefix}rush-event", event_id ) - - attendees_oid = event["attendeesId"] - - attendees = self.mongo_module.get_document_by_id( - f"{self.collection_prefix}rush-event-attendees", attendees_oid - ) code = user_data["code"] user_data.pop("code") + + # Parse the timestamp string to a datetime object + deadline = datetime.datetime.strptime(event["deadline"], "%Y-%m-%dT%H:%M:%S.%fZ") + + if datetime.datetime.now() > deadline: + raise UnauthorizedError("Event deadline has passed.") if code != event["code"]: raise UnauthorizedError("Invalid code.") - if any(d["email"] == user_data["email"] for d in attendees["attendees"]): + if any(d["email"] == user_data["email"] for d in event["attendees"]): raise BadRequestError("User has already checked in.") user_data["checkinTime"] = datetime.datetime.now() - attendees["attendees"].append(user_data) - attendees["numAttendees"] += 1 + event["attendees"].append(user_data) + event["numAttendees"] += 1 + + # STEP 1: update events-rush-event collection + mongo_module.update_document( + f"{self.collection_prefix}rush-event", + event_id, + {"$set": event}, + ) + + # Define array update query and filters + update_query = { + "$set": { + "events.$[eventElem]": event + } + } + + array_filters = [ + {"eventElem._id": event_oid} + ] - # Update attendees collection with data + # STEP 2: Modify the event in its category (rush collection) self.mongo_module.update_document( - collection=f"{self.collection_prefix}rush-event-attendees", - document_id=attendees_oid, - query={"$set": attendees}, + collection=f"{self.collection_prefix}rush", + document_id=event["categoryId"], + query=update_query, + array_filters=array_filters ) return @@ -282,7 +291,6 @@ def delete_rush_event(self, event_id: str): if not event: raise Exception("Event does not exist.") - attendees_oid = event["attendeesId"] event_category_id = event["categoryId"] # Get eventCoverImage path @@ -307,12 +315,6 @@ def delete_rush_event(self, event_id: str): document_id=event_oid ) - # Delete attendees data from rush-event-attendees collection - self.mongo_module.delete_document_by_id( - collection=f"{self.collection_prefix}rush-event-attendees", - document_id=attendees_oid - ) - return except Exception as e: From 1b8e1c8475b3631d74cbf4a6e798edfc57a94e12 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:31:17 -0400 Subject: [PATCH 20/34] added recent-old sorting on rush events --- chalicelib/services/EventsRushService.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py index 143711a..4ca83aa 100644 --- a/chalicelib/services/EventsRushService.py +++ b/chalicelib/services/EventsRushService.py @@ -262,6 +262,16 @@ def get_rush_events_default_category(self): # remove code from every rush event for event in rush_category["events"]: event.pop("code", None) + + # Sort events by the date field + try: + rush_category["events"].sort( + key=lambda e: datetime.datetime.strptime(e["date"], "%Y-%m-%dT%H:%M:%S.%fZ"), + reverse=True + ) + except ValueError as ve: + # Handle the case where the date format might be different or invalid + print(f"Date format error: {ve}") return json.dumps(rush_category, cls=self.BSONEncoder) From af5e1c8652130ab7a74adae21abcc9ba2ff82306 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:50:38 -0400 Subject: [PATCH 21/34] added code to check if user has checkedIn to an event --- chalicelib/api/events_rush.py | 6 +++--- chalicelib/services/EventsRushService.py | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/chalicelib/api/events_rush.py b/chalicelib/api/events_rush.py index 2385503..37a8ce5 100644 --- a/chalicelib/api/events_rush.py +++ b/chalicelib/api/events_rush.py @@ -49,9 +49,9 @@ def checkin_rush(event_id): return events_rush_service.checkin_rush(event_id, data) -@events_rush_api.route("/events/rush/default", methods=["GET"], cors=True) -def get_rush_events_default_category(): - return events_rush_service.get_rush_events_default_category() +@events_rush_api.route("/events/rush/default/{email}", methods=["GET"], cors=True) +def get_rush_events_default_category(email): + return events_rush_service.get_rush_events_default_category(email) @events_rush_api.route("/events/rush/{event_id}", methods=["DELETE"], cors=True) diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py index 4ca83aa..2a708df 100644 --- a/chalicelib/services/EventsRushService.py +++ b/chalicelib/services/EventsRushService.py @@ -248,7 +248,7 @@ def checkin_rush(self, event_id: str, user_data: dict): return - def get_rush_events_default_category(self): + def get_rush_events_default_category(self, email: str): rush_categories = self.mongo_module.get_data_from_collection( collection=f"{self.collection_prefix}rush", filter={"defaultRushCategory": True} @@ -263,6 +263,10 @@ def get_rush_events_default_category(self): for event in rush_category["events"]: event.pop("code", None) + # check if user attended event (boolean) + checkedIn = any(attendee["email"] == email for attendee in event["attendees"]) + event["checkedIn"] = checkedIn + # Sort events by the date field try: rush_category["events"].sort( From ee59136fbf255fcc7fa0d593b3f6109d35a0ddff Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Thu, 25 Jul 2024 20:56:47 -0400 Subject: [PATCH 22/34] switched GET to POST to send email to backend --- chalicelib/api/events_rush.py | 7 ++++--- chalicelib/services/EventsRushService.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/chalicelib/api/events_rush.py b/chalicelib/api/events_rush.py index 37a8ce5..d15c645 100644 --- a/chalicelib/api/events_rush.py +++ b/chalicelib/api/events_rush.py @@ -49,9 +49,10 @@ def checkin_rush(event_id): return events_rush_service.checkin_rush(event_id, data) -@events_rush_api.route("/events/rush/default/{email}", methods=["GET"], cors=True) -def get_rush_events_default_category(email): - return events_rush_service.get_rush_events_default_category(email) +@events_rush_api.route("/events/rush/default", methods=["POST"], cors=True) +def get_rush_events_default_category(): + data = events_rush_api.current_request.json_body + return events_rush_service.get_rush_events_default_category(data) @events_rush_api.route("/events/rush/{event_id}", methods=["DELETE"], cors=True) diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py index 2a708df..71933de 100644 --- a/chalicelib/services/EventsRushService.py +++ b/chalicelib/services/EventsRushService.py @@ -248,7 +248,7 @@ def checkin_rush(self, event_id: str, user_data: dict): return - def get_rush_events_default_category(self, email: str): + def get_rush_events_default_category(self, data: dict): rush_categories = self.mongo_module.get_data_from_collection( collection=f"{self.collection_prefix}rush", filter={"defaultRushCategory": True} @@ -264,7 +264,7 @@ def get_rush_events_default_category(self, email: str): event.pop("code", None) # check if user attended event (boolean) - checkedIn = any(attendee["email"] == email for attendee in event["attendees"]) + checkedIn = any(attendee["email"] == data["email"] for attendee in event["attendees"]) event["checkedIn"] = checkedIn # Sort events by the date field From a7d64163e64c3640b8673a44e131491c85b74263 Mon Sep 17 00:00:00 2001 From: Jin Young Bang Date: Sat, 27 Jul 2024 15:10:06 -0400 Subject: [PATCH 23/34] feat: implement get member API for user update --- chalicelib/api/members.py | 13 +++++++++++-- chalicelib/services/MemberService.py | 7 +++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/chalicelib/api/members.py b/chalicelib/api/members.py index f8b19c7..3254fb9 100644 --- a/chalicelib/api/members.py +++ b/chalicelib/api/members.py @@ -6,6 +6,13 @@ members_api = Blueprint(__name__) +@members_api.route("/member/{user_id}", methods=["GET"], cors=True) +@auth(members_api, roles=["admin", "member"]) +def get_member(user_id): + member = member_service.get_by_id(user_id) + return member if member else {} + + @members_api.route("/members", methods=["GET"], cors=True) @auth(members_api, roles=["admin", "member"]) def get_all_members(): @@ -25,7 +32,7 @@ def onboard_member(user_id): "message": "User updated successfully.", } else: - { "status": False} + {"status": False} @members_api.route("/members", methods=["POST"], cors=True) @@ -34,14 +41,16 @@ def create_member(): data = members_api.current_request.json_body return member_service.create(data) + @members_api.route("/members", methods=["DELETE"], cors=True) @auth(members_api, roles=[Roles.ADMIN]) def delete_members(): data = members_api.current_request.json_body return member_service.delete(data) + @members_api.route("/members/{user_id}/roles", methods=["PATCH"], cors=True) @auth(members_api, roles=[Roles.ADMIN]) def update_member_roles(user_id): data = members_api.current_request.json_body - return member_service.update_roles(user_id, data["roles"]) \ No newline at end of file + return member_service.update_roles(user_id, data["roles"]) diff --git a/chalicelib/services/MemberService.py b/chalicelib/services/MemberService.py index 245605d..52a012f 100644 --- a/chalicelib/services/MemberService.py +++ b/chalicelib/services/MemberService.py @@ -40,7 +40,7 @@ def create(self, data): "success": True, "message": "User created successfully", } - + def delete(self, data: list[str]) -> dict: """ Deletes user documents based on the provided IDs. @@ -70,7 +70,10 @@ def delete(self, data: list[str]) -> dict: "success": True, "message": "Documents deleted successfully", } - + + def get_by_id(self, user_id: str): + data = mongo_module.get_document_by_id(self.collection, user_id) + return json.dumps(data, cls=self.BSONEncoder) def get_all(self): data = mongo_module.get_data_from_collection(self.collection) From 0c02bc27bf388b21fd1bfab300223bc435539115 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 28 Jul 2024 13:20:33 -0400 Subject: [PATCH 24/34] added function to return rush event analytics --- chalicelib/api/events_rush.py | 8 +++++++- chalicelib/services/EventsRushService.py | 23 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/chalicelib/api/events_rush.py b/chalicelib/api/events_rush.py index d15c645..8159289 100644 --- a/chalicelib/api/events_rush.py +++ b/chalicelib/api/events_rush.py @@ -57,4 +57,10 @@ def get_rush_events_default_category(): @events_rush_api.route("/events/rush/{event_id}", methods=["DELETE"], cors=True) def delete_rush_event(event_id): - return events_rush_service.delete_rush_event(event_id) \ No newline at end of file + return events_rush_service.delete_rush_event(event_id) + + +@events_rush_api.route("/events/rush/{category_id}/analytics", methods=["GET"], cors=True) +@auth(events_rush_api, roles=["admin"]) +def get_rush_category_analytics(category_id): + return events_rush_service.get_rush_category_analytics(category_id=category_id) \ No newline at end of file diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py index 71933de..a934afd 100644 --- a/chalicelib/services/EventsRushService.py +++ b/chalicelib/services/EventsRushService.py @@ -334,4 +334,27 @@ def delete_rush_event(self, event_id: str): except Exception as e: raise BadRequestError(e) + def get_rush_category_analytics(self, category_id: str): + category = self.mongo_module.get_document_by_id( + f"{self.collection_prefix}rush", category_id + ) + + # attendees : dict of all users (user: { name, email, eventsAttended: list of objects }) + attendees = {} + + for event in category["events"]: + for attendee in event["attendees"]: + email =attendee["email"] + new_event = { + "eventId": event["_id"], + "eventName": event["name"] + } + if email in attendees: + attendees[email]["eventsAttended"].append(new_event) + else: + attendees[email] = { **attendee, "eventsAttended": [new_event] } + + + return json.dumps(attendees, cls=self.BSONEncoder) + events_rush_service = EventsRushService() \ No newline at end of file From fe45cfd746f09343d6112ffa1bd252d2112f28a9 Mon Sep 17 00:00:00 2001 From: Jin Young Bang Date: Sun, 28 Jul 2024 13:34:54 -0400 Subject: [PATCH 25/34] feat: implement update member API --- chalicelib/api/members.py | 9 ++++++++ chalicelib/services/MemberService.py | 33 ++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/chalicelib/api/members.py b/chalicelib/api/members.py index 3254fb9..e1267ad 100644 --- a/chalicelib/api/members.py +++ b/chalicelib/api/members.py @@ -13,6 +13,15 @@ def get_member(user_id): return member if member else {} +@members_api.route("/member/{user_id}", methods=["PUT"], cors=True) +@auth(members_api, roles=[Roles.MEMBER, Roles.ADMIN]) +def update_member(user_id): + data = members_api.current_request.json_body + return member_service.update( + user_id=user_id, data=data, headers=members_api.current_request.headers + ) + + @members_api.route("/members", methods=["GET"], cors=True) @auth(members_api, roles=["admin", "member"]) def get_all_members(): diff --git a/chalicelib/services/MemberService.py b/chalicelib/services/MemberService.py index 52a012f..c5f8943 100644 --- a/chalicelib/services/MemberService.py +++ b/chalicelib/services/MemberService.py @@ -1,8 +1,10 @@ from chalicelib.modules.mongo import mongo_module -from chalice import ConflictError, NotFoundError +from chalice import ConflictError, NotFoundError, UnauthorizedError -import json from bson import ObjectId +import json +import jwt +import boto3 class MemberService: @@ -82,6 +84,33 @@ def get_all(self): def onboard(self, document_id=str, data=dict) -> bool: return mongo_module.update_document_by_id(self.collection, document_id, data) + def update(self, user_id: str, data: dict, headers: dict) -> bool: + ssm_client = boto3.client("ssm") + auth_header = headers.get("Authorization", None) + + if not auth_header: + raise UnauthorizedError("Authorization header is missing.") + + _, token = auth_header.split(" ", 1) if " " in auth_header else (None, None) + + if not token: + raise UnauthorizedError("Token is missing.") + + auth_secret = ssm_client.get_parameter( + Name="/Zap/AUTH_SECRET", WithDecryption=True + )["Parameter"]["Value"] + decoded = jwt.decode(token, auth_secret, algorithms=["HS256"]) + + if user_id != decoded["_id"]: + raise UnauthorizedError( + "User {user_id} is not authorized to update this user." + ) + + # NOTE: Performing an update on the path '_id' would modify the immutable field '_id' + data.pop("_id", None) + + return mongo_module.update_document_by_id(self.collection, user_id, data) + def update_roles(self, document_id=str, roles=list) -> bool: return mongo_module.update_document( self.collection, From 70dea1a8c997099920c7bc5dcf70f6c8c625c1c6 Mon Sep 17 00:00:00 2001 From: Jin Young Bang Date: Sun, 28 Jul 2024 14:14:13 -0400 Subject: [PATCH 26/34] test: create unit tests for member API --- chalicelib/api/members.py | 3 +- tests/api/test_members.py | 256 ++++++++++++++++++++++++++++++++++++++ tests/test_decorators.py | 5 +- tests/test_dynamodb.py | 4 +- 4 files changed, 262 insertions(+), 6 deletions(-) create mode 100644 tests/api/test_members.py diff --git a/chalicelib/api/members.py b/chalicelib/api/members.py index e1267ad..57b3f32 100644 --- a/chalicelib/api/members.py +++ b/chalicelib/api/members.py @@ -33,6 +33,7 @@ def get_all_members(): @auth(members_api, roles=[]) def onboard_member(user_id): data = members_api.current_request.json_body + # TODO: If isNewUser is False, reject onboarding data["isNewUser"] = False if member_service.onboard(user_id, data): @@ -41,7 +42,7 @@ def onboard_member(user_id): "message": "User updated successfully.", } else: - {"status": False} + return {"status": False} @members_api.route("/members", methods=["POST"], cors=True) diff --git a/tests/api/test_members.py b/tests/api/test_members.py new file mode 100644 index 0000000..6e264bb --- /dev/null +++ b/tests/api/test_members.py @@ -0,0 +1,256 @@ +from chalice.test import Client +from unittest.mock import patch +from chalice.config import Config +from chalice.local import LocalGateway + +from app import app +import json + + +lg = LocalGateway(app, Config()) + + +TEST_MEMBER_DATA = [ + { + "_id": "12a34bc678df27ead9388708", + "name": "Name Name", + "email": "whyphi@bu.edu", + "class": "Lambda", + "college": "CAS", + "family": "Poseidon", + "graduationYear": "2026", + "isEboard": "no", + "major": "Computer Science", + "minor": "", + "isNewUser": False, + "team": "technology", + "roles": ["admin", "eboard", "member"], + "big": "Name Name", + }, + { + "_id": "12a34bc678df27ead9388709", + "name": "Name Name", + "email": "whyphi1@bu.edu", + "class": "Lambda", + "college": "QST", + "family": "Atlas", + "graduationYear": "2027", + "isEboard": "no", + "major": "Business Administration", + "minor": "", + "isNewUser": True, + "team": "operations", + "roles": ["member"], + "big": "Name Name", + }, +] + + +def test_get_member(): + # Create a Chalice test client + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.get_by_id" + ) as mock_get: + mock_get.return_value = TEST_MEMBER_DATA[0] + response = client.http.get( + f"/member/{TEST_MEMBER_DATA[0]['_id']}", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == TEST_MEMBER_DATA[0] + + +def test_get_member_non_existent(): + # Create a Chalice test client + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.get_by_id" + ) as mock_get: + mock_get.return_value = {} + response = client.http.get( + "/member/123", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == {} + + +def test_update_member(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.update" + ) as mock_update: + # Make copy of TEST_MEMBER_DATA[0] + update_member_data = TEST_MEMBER_DATA[0].copy() + update_member_data["name"] = "New Name" + mock_update.return_value = update_member_data + + response = client.http.put( + f"/member/{TEST_MEMBER_DATA[0]['_id']}", + body=update_member_data, + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == update_member_data + + +def test_get_all_members(): + # Create a Chalice test client + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.get_all" + ) as mock_get_all: + mock_get_all.return_value = TEST_MEMBER_DATA + response = client.http.get( + "/members", + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == TEST_MEMBER_DATA + + +def test_onboard_member(): + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.onboard" + ) as mock_onboard: + mock_onboard.return_value = True + + # Utilize local gateway for passing in body + response = lg.handle_request( + method="POST", + path=f"/members/onboard/{TEST_MEMBER_DATA[1]['_id']}", + headers={ + "Content-Type": "application/json", + "Authorization": "Bearer SAMPLE_TOKEN_STRING", + }, + body=json.dumps(TEST_MEMBER_DATA[1]), + ) + + # Check the response status code and body + assert response["statusCode"] == 200 + assert json.loads(response["body"]) == { + "status": True, + "message": "User updated successfully.", + } + + +def test_onboard_member_fail_on_mongo(): + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "member"} + + with patch( + "chalicelib.services.MemberService.member_service.onboard" + ) as mock_onboard: + mock_onboard.return_value = False + response = lg.handle_request( + method="POST", + path=f"/members/onboard/{TEST_MEMBER_DATA[1]['_id']}", + headers={ + "Content-Type": "application/json", + "Authorization": "Bearer SAMPLE_TOKEN_STRING", + }, + body=json.dumps(TEST_MEMBER_DATA[1]), + ) + print(response) + # Check the response status code and body + assert response["statusCode"] == 200 + assert json.loads(response["body"]) == { + "status": False, + } + + +def test_create_member(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "admin"} + + with patch( + "chalicelib.services.MemberService.member_service.create" + ) as mock_create: + mock_create.return_value = { + "success": True, + "message": "User created successfully", + } + response = client.http.post( + "/members", + body=json.dumps(TEST_MEMBER_DATA[0]), + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == { + "success": True, + "message": "User created successfully", + } + + +def test_delete_members(): + with Client(app) as client: + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "admin"} + + with patch( + "chalicelib.services.MemberService.member_service.delete" + ) as mock_delete: + mock_delete.return_value = { + "success": True, + "message": "Documents deleted successfully", + } + response = client.http.delete( + "/members", + body=json.dumps(TEST_MEMBER_DATA), + headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, + ) + # Check the response status code and body + assert response.status_code == 200 + assert response.json_body == { + "success": True, + "message": "Documents deleted successfully", + } + + +def test_update_member_roles(): + with patch("chalicelib.decorators.jwt.decode") as mock_decode: + mock_decode.return_value = {"role": "admin"} + + with patch( + "chalicelib.services.MemberService.member_service.update_roles" + ) as mock_update: + update_member_data = TEST_MEMBER_DATA[0].copy() + update_member_data["roles"] = ["admin"] + mock_update.return_value = update_member_data + + response = lg.handle_request( + method="PATCH", + path=f"/members/{TEST_MEMBER_DATA[0]['_id']}/roles", + headers={ + "Content-Type": "application/json", + "Authorization": "Bearer SAMPLE_TOKEN_STRING", + }, + body=json.dumps(update_member_data), + ) + + print(response) + # Check the response status code and body + assert response["statusCode"] == 200 + assert json.loads(response["body"]) == update_member_data diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 5b352c5..c52f0c5 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,6 +1,6 @@ -from unittest.mock import patch from chalicelib.decorators import add_env_suffix + def test_add_env_suffix_dev(): def mocked_function(self, table_name: str, *args, **kwargs): return table_name @@ -17,6 +17,7 @@ def mocked_function(self, table_name: str, *args, **kwargs): # Check if the suffix is added correctly assert result == "test-table-dev" + def test_add_env_suffix_prod(): def mocked_function(self, table_name: str, *args, **kwargs): return table_name @@ -31,4 +32,4 @@ def mocked_function(self, table_name: str, *args, **kwargs): result = decorated_function(instance_mock, "test-table", env=True) # Check if the suffix is added correctly - assert result == "test-table-prod" \ No newline at end of file + assert result == "test-table-prod" diff --git a/tests/test_dynamodb.py b/tests/test_dynamodb.py index da22c36..b77a3c1 100644 --- a/tests/test_dynamodb.py +++ b/tests/test_dynamodb.py @@ -101,10 +101,8 @@ def test_delete_item(db): db.put_data("test-table", SAMPLE_DATA[0]) response = db.delete_item("test-table", {"id": 123}) - assert response == True + assert response # TODO: Test case should fail, but isn't # response = db.delete_item("test-table", {"id": 124}) # assert response == False - - From 255f48194b7216c1a2dbaa19d53e10377187537c Mon Sep 17 00:00:00 2001 From: Jin Young Bang Date: Sun, 28 Jul 2024 14:44:42 -0400 Subject: [PATCH 27/34] lint: fix lint warnings --- chalicelib/api/applicants.py | 2 - chalicelib/api/insights.py | 2 - chalicelib/api/listings.py | 1 - chalicelib/s3.py | 33 +++++---- chalicelib/services/AccountabilityService.py | 1 - chalicelib/services/EventsMemberService.py | 9 ++- chalicelib/services/InsightsService.py | 74 ++++++++++++-------- chalicelib/services/ListingService.py | 4 +- chalicelib/utils.py | 10 +-- tests/api/test_applicants.py | 6 +- tests/api/test_events_members.py | 25 ++++--- tests/api/test_events_rush.py | 26 ++++--- tests/api/test_insights.py | 2 +- tests/api/test_listings.py | 10 +-- tests/services/test_applicant_service.py | 12 ++-- tests/services/test_events_member_service.py | 21 ++++-- tests/services/test_insights_service.py | 31 ++++---- tests/services/test_listing_service.py | 19 ++--- tests/test_decorators.py | 5 +- tests/test_dynamodb.py | 4 +- 20 files changed, 165 insertions(+), 132 deletions(-) diff --git a/chalicelib/api/applicants.py b/chalicelib/api/applicants.py index 7542162..18b3926 100644 --- a/chalicelib/api/applicants.py +++ b/chalicelib/api/applicants.py @@ -1,10 +1,8 @@ from chalice import Blueprint from chalicelib.services.ApplicantService import applicant_service -from chalicelib.handlers.error_handler import handle_exceptions from chalicelib.decorators import auth from chalicelib.models.roles import Roles -from pydantic import ValidationError applicants_api = Blueprint(__name__) diff --git a/chalicelib/api/insights.py b/chalicelib/api/insights.py index 17d3f0f..5cb376c 100644 --- a/chalicelib/api/insights.py +++ b/chalicelib/api/insights.py @@ -1,11 +1,9 @@ # TO BE COMPLETED: create api routes for analytics from chalice import Blueprint from chalicelib.services.InsightsService import insights_service -from chalicelib.handlers.error_handler import handle_exceptions from chalicelib.decorators import auth from chalicelib.models.roles import Roles -from pydantic import ValidationError insights_api = Blueprint(__name__) diff --git a/chalicelib/api/listings.py b/chalicelib/api/listings.py index 7cd9c9c..f9d58f6 100644 --- a/chalicelib/api/listings.py +++ b/chalicelib/api/listings.py @@ -4,7 +4,6 @@ from chalicelib.decorators import auth from chalicelib.models.roles import Roles -from pydantic import ValidationError listings_api = Blueprint(__name__) diff --git a/chalicelib/s3.py b/chalicelib/s3.py index 239e8f8..49fd510 100644 --- a/chalicelib/s3.py +++ b/chalicelib/s3.py @@ -1,11 +1,8 @@ import boto3 import os -import json -from datetime import datetime from chalicelib.utils import decode_base64 - class S3Client: def __init__(self): self.bucket_name = "whyphi-zap" @@ -19,24 +16,27 @@ def upload_binary_data(self, path: str, data: str) -> str: path = f"prod/{path}" else: path = f"dev/{path}" - + # Split parts of base64 data - parts = data.split(',') - metadata, base64_data = parts[0], parts[1] + parts = data.split(",") + metadata, base64_data = parts[0], parts[1] # Extract content type from metadata - content_type = metadata.split(';')[0][5:] # Remove "data:" prefix + content_type = metadata.split(";")[0][5:] # Remove "data:" prefix binary_data = decode_base64(base64_data) - + # Upload binary data as object with content type set self.s3.put_object( - Bucket=self.bucket_name, Key=path, Body=binary_data, ContentType=content_type + Bucket=self.bucket_name, + Key=path, + Body=binary_data, + ContentType=content_type, ) # Retrieve endpoint of object s3_endpoint = f"https://{self.bucket_name}.s3.amazonaws.com/" object_url = s3_endpoint + path - + return object_url def delete_binary_data(self, object_id: str) -> str: @@ -47,19 +47,18 @@ def delete_binary_data(self, object_id: str) -> str: Returns: str: A message indicating the result of the deletion operation. - + Documentation: https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/s3/client/delete_object.html """ if self.is_prod: path = f"prod/{object_id}" else: path = f"dev/{object_id}" - + # delete binary data given bucket_name and key - response = self.s3.delete_object( - Bucket=self.bucket_name, Key=path - ) - + response = self.s3.delete_object(Bucket=self.bucket_name, Key=path) + return response -s3 = S3Client() \ No newline at end of file + +s3 = S3Client() diff --git a/chalicelib/services/AccountabilityService.py b/chalicelib/services/AccountabilityService.py index b6f56c6..8478da0 100644 --- a/chalicelib/services/AccountabilityService.py +++ b/chalicelib/services/AccountabilityService.py @@ -1,4 +1,3 @@ -from chalice import NotFoundError, BadRequestError from chalicelib.modules.google_sheets import GoogleSheetsModule diff --git a/chalicelib/services/EventsMemberService.py b/chalicelib/services/EventsMemberService.py index b2c61cc..e70d43e 100644 --- a/chalicelib/services/EventsMemberService.py +++ b/chalicelib/services/EventsMemberService.py @@ -6,6 +6,7 @@ from chalicelib.modules.google_sheets import GoogleSheetsModule from chalicelib.modules.ses import ses, SesDestination + class EventsMemberService: class BSONEncoder(json.JSONEncoder): """JSON encoder that converts Mongo ObjectIds and datetime.datetime to strings.""" @@ -65,7 +66,7 @@ def delete_timeframe(self, timeframe_id: str): self.mongo_module.delete_document_by_id( f"{self.collection_prefix}timeframe", timeframe_id ) - + return {"statusCode": 200} def create_event(self, timeframe_id: str, event_data: dict): @@ -121,7 +122,7 @@ def checkin(self, event_id: str, user: dict) -> dict: dict -- Dictionary containing status and message. """ user_id, user_email = user["id"], user["email"] - member = self.mongo_module.get_document_by_id(f"users", user_id) + member = self.mongo_module.get_document_by_id("users", user_id) if member is None: raise NotFoundError(f"User with ID {user_id} does not exist.") @@ -227,7 +228,9 @@ def delete(self, event_id: str): ) # Delete the event document - self.mongo_module.delete_document_by_id(f"{self.collection_prefix}event", event_id) + self.mongo_module.delete_document_by_id( + f"{self.collection_prefix}event", event_id + ) def get_timeframe_sheets(self, timeframe_id: str): timeframe = self.mongo_module.get_document_by_id( diff --git a/chalicelib/services/InsightsService.py b/chalicelib/services/InsightsService.py index d5d2466..71219a0 100644 --- a/chalicelib/services/InsightsService.py +++ b/chalicelib/services/InsightsService.py @@ -1,12 +1,13 @@ # TO BE COMPLETED: create service to perform analytics (used in API...) from chalicelib.db import db + class InsightsService: def __init__(self): pass def get_insights_from_listing(self, id: str): - ''' driver function of insights (returns both `dashboard` and `distribution`) ''' + """driver function of insights (returns both `dashboard` and `distribution`)""" # fetch applicants from `get_applicants` endpoint in `db.py` data = db.get_applicants(table_name="zap-applications", listing_id=id) @@ -43,7 +44,11 @@ def _get_dashboard_insights(data): applicant["major"] = applicant["major"].title() applicant["minor"] = applicant["minor"].title() - gpa, grad_year, major = applicant["gpa"], applicant["gradYear"], applicant["major"] + gpa, grad_year, major = ( + applicant["gpa"], + applicant["gradYear"], + applicant["major"], + ) # attempt conversions (if fail, then skip) try: @@ -62,14 +67,14 @@ def _get_dashboard_insights(data): except ValueError: print("skipping gradYear: ", grad_year) pass - + # parse majors (if non-empty) if major: if major in majors: majors[major] += 1 else: majors[major] = 1 - + if count_gpa: avg_gpa /= count_gpa else: @@ -78,33 +83,33 @@ def _get_dashboard_insights(data): # calculate most common major/gradYear # Check if majors dictionary is not empty if majors: - common_major, count_common_major = max(majors.items(), key=lambda x: x[1]) + common_major, _ = max(majors.items(), key=lambda x: x[1]) else: # Handle the case when majors dictionary is empty - common_major, count_common_major = "N/A", 0 + common_major, _ = "N/A", 0 # Check if grad_years dictionary is not empty if grad_years: - common_grad_year, count_common_grad_year = max(grad_years.items(), key=lambda x: x[1]) + common_grad_year, _ = max(grad_years.items(), key=lambda x: x[1]) else: # Handle the case when grad_years dictionary is empty - common_grad_year, count_common_grad_year = "N/A", 0 - + common_grad_year, _ = "N/A", 0 dashboard = { "applicantCount": num_applicants, "avgGpa": round(avg_gpa, 1) if avg_gpa != "N/A" else avg_gpa, "commonMajor": common_major.title(), # "countCommonMajor": count_common_major, # TO-DO: maybe do something with common major counts - "commonGradYear": int(common_grad_year) if common_grad_year != 'N/A' else common_grad_year, + "commonGradYear": int(common_grad_year) + if common_grad_year != "N/A" + else common_grad_year, # "avgResponseLength": 0 # TO-DO: maybe implement parsing for response lengths } return dashboard - def _get_pie_chart_insights(data): - ''' helper function for pie charts (should be function, not method within InsightsService) ''' + """helper function for pie charts (should be function, not method within InsightsService)""" # initialize return object # value (list) structure : [ {name: string, value: int, applicants: Applicant[]}, ... , ... ] @@ -117,12 +122,20 @@ def _get_pie_chart_insights(data): "linkedin": [], "website": [], } - + # list of fields we want to consider - fields = ["colleges", "gpa", "gradYear", "major", "minor", "linkedin", "website"] + fields = [ + "colleges", + "gpa", + "gradYear", + "major", + "minor", + "linkedin", + "website", + ] def findInsightsObject(metric, metric_val): - ''' helper to the helper lol -> checks for previously added metric_name ''' + """helper to the helper lol -> checks for previously added metric_name""" # check if college exists in `distribution["colleges"]` found_object = None @@ -130,26 +143,27 @@ def findInsightsObject(metric, metric_val): if distribution_object["name"] == metric_val: found_object = distribution_object break - + return found_object for applicant in data: # iterate over applicant dictionary for metric, val in applicant.items(): - # case 1: ignore irrelevant metrics if metric not in fields: continue - + # case 2: metric is a url if metric in ["linkedin", "website"]: - val = 'N/A' if (not val or val == 'N/A') else 'hasURL' - + val = "N/A" if (not val or val == "N/A") else "hasURL" + # case 3: handle other metrics with mepty val (attempt to handle some edge cases) # TO-DO: update Form.tsx in frontend to prevent bad inputs - elif metric in ['minor', 'gpa'] and (not val or val.lower() in ['na', 'n/a', 'n a', 'n / a']): + elif metric in ["minor", "gpa"] and ( + not val or val.lower() in ["na", "n/a", "n a", "n / a"] + ): # general case - val = 'N/A' - + val = "N/A" + # case 4: colleges -> iterate over colleges object elif metric == "colleges": for college, status in val.items(): @@ -159,20 +173,24 @@ def findInsightsObject(metric, metric_val): # check if college exists in `distribution["colleges"]` found_college = findInsightsObject(metric, college) - + if found_college: found_college["value"] += 1 found_college["applicants"] += [applicant] else: - newCollege = {"name": college, "value": 1, "applicants": [applicant]} + newCollege = { + "name": college, + "value": 1, + "applicants": [applicant], + } distribution[metric] += [newCollege] # skip to next metric - continue - + continue + # handle remaining fields found_object = findInsightsObject(metric, val) - + if found_object: found_object["value"] += 1 found_object["applicants"] += [applicant] diff --git a/chalicelib/services/ListingService.py b/chalicelib/services/ListingService.py index fca8378..64d7f79 100644 --- a/chalicelib/services/ListingService.py +++ b/chalicelib/services/ListingService.py @@ -122,11 +122,11 @@ def delete(self, id: str): else: raise NotFoundError("Listing not found") - except NotFoundError as e: + except NotFoundError: # app.log.error(f"An error occurred: {str(e)}") return {"statusCode": 404, "message": "Listing not found"} - except Exception as e: + except Exception: # app.log.error(f"An error occurred: {str(e)}") return {"statusCode": 500, "message": "Internal Server Error"} diff --git a/chalicelib/utils.py b/chalicelib/utils.py index 9dfab87..5926eef 100644 --- a/chalicelib/utils.py +++ b/chalicelib/utils.py @@ -1,5 +1,4 @@ import base64 -import imghdr def decode_base64(base64_data): @@ -7,15 +6,16 @@ def decode_base64(base64_data): binary_data = base64.b64decode(base64_data) return binary_data + def get_file_extension_from_base64(base64_data): - parts = base64_data.split(',') + parts = base64_data.split(",") if len(parts) != 2: return None # Invalid data URI format metadata = parts[0] # Extract content type from metadata. - content_type = metadata.split(';')[0][5:] # Remove "data:" prefix + content_type = metadata.split(";")[0][5:] # Remove "data:" prefix # Map content type to file extension. extension_map = { @@ -26,6 +26,6 @@ def get_file_extension_from_base64(base64_data): } # Use the mapping to get the extension (default to 'dat' if not found). - extension = extension_map.get(content_type, 'dat') + extension = extension_map.get(content_type, "dat") - return extension \ No newline at end of file + return extension diff --git a/tests/api/test_applicants.py b/tests/api/test_applicants.py index 02b22c8..65cd4cc 100644 --- a/tests/api/test_applicants.py +++ b/tests/api/test_applicants.py @@ -41,10 +41,9 @@ def test_get_all_applicants(): with patch( "chalicelib.services.ApplicantService.applicant_service.get_all" ) as mock_get_all: - mock_get_all.return_value = TEST_APPLICANTS response = client.http.get( - f"/applicants", + "/applicants", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -56,7 +55,6 @@ def test_get_all_applicants(): def test_get_all_applicants_from_listing(): # Create a Chalice test client with Client(app) as client: - with patch("chalicelib.decorators.jwt.decode") as mock_decode: # Assuming the decoded token has the required role mock_decode.return_value = {"role": "admin"} @@ -65,7 +63,7 @@ def test_get_all_applicants_from_listing(): ) as mock_get_all_from_listing: mock_get_all_from_listing.return_value = TEST_APPLICANTS response = client.http.get( - f"/applicants/test_listing_id", + "/applicants/test_listing_id", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) diff --git a/tests/api/test_events_members.py b/tests/api/test_events_members.py index a16ec19..90ac3a2 100644 --- a/tests/api/test_events_members.py +++ b/tests/api/test_events_members.py @@ -10,6 +10,7 @@ with open("tests/fixtures/events/general/sample_timeframe_sheets.json") as f: SAMPLE_TIMEFRAME_SHEETS = json.load(f) + def test_create_timeframe(): with Client(app) as client: with patch("chalicelib.decorators.jwt.decode") as mock_decode: @@ -20,7 +21,7 @@ def test_create_timeframe(): ) as mock_create_timeframe: mock_create_timeframe.return_value = {"msg": True} response = client.http.post( - f"/timeframes", + "/timeframes", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -40,7 +41,7 @@ def test_get_all_timeframes(): ) as mock_get_all_timeframes: mock_get_all_timeframes.return_value = SAMPLE_TIMEFRAMES response = client.http.get( - f"/timeframes", + "/timeframes", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -48,6 +49,7 @@ def test_get_all_timeframes(): assert response.status_code == 200 assert response.json_body == SAMPLE_TIMEFRAMES + def test_get_timeframe(): # Create a Chalice test client with Client(app) as client: @@ -60,7 +62,7 @@ def test_get_timeframe(): ) as mock_get_timeframe: mock_get_timeframe.return_value = SAMPLE_TIMEFRAMES[0] response = client.http.get( - f"/timeframes/test_timeframe_id", + "/timeframes/test_timeframe_id", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -68,6 +70,7 @@ def test_get_timeframe(): assert response.status_code == 200 assert response.json_body == SAMPLE_TIMEFRAMES[0] + def test_delete_timeframe(): with Client(app) as client: with patch("chalicelib.decorators.jwt.decode") as mock_decode: @@ -83,7 +86,7 @@ def test_delete_timeframe(): ) assert response.status_code == 200 - assert response.json_body == {'status': True} + assert response.json_body == {"status": True} def test_create_event(): @@ -103,6 +106,7 @@ def test_create_event(): assert response.status_code == 200 assert response.json_body == {"msg": True} + def test_get_event(): # Create a Chalice test client with Client(app) as client: @@ -115,7 +119,7 @@ def test_get_event(): ) as mock_get_timeframe: mock_get_timeframe.return_value = SAMPLE_TIMEFRAMES[0] response = client.http.get( - f"/timeframes/test_timeframe_id", + "/timeframes/test_timeframe_id", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -123,6 +127,7 @@ def test_get_event(): assert response.status_code == 200 assert response.json_body == SAMPLE_TIMEFRAMES[0] + def test_get_timeframe_sheets(): # Create a Chalice test client with Client(app) as client: @@ -135,7 +140,7 @@ def test_get_timeframe_sheets(): ) as mock_get_timeframe_sheets: mock_get_timeframe_sheets.return_value = SAMPLE_TIMEFRAME_SHEETS response = client.http.get( - f"/timeframes/test_timeframe_id/sheets", + "/timeframes/test_timeframe_id/sheets", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -143,6 +148,7 @@ def test_get_timeframe_sheets(): assert response.status_code == 200 assert response.json_body == SAMPLE_TIMEFRAME_SHEETS + def test_checkin(): with Client(app) as client: with patch("chalicelib.decorators.jwt.decode") as mock_decode: @@ -153,13 +159,14 @@ def test_checkin(): ) as mock_checkin: mock_checkin.return_value = {"msg": True} response = client.http.post( - f"/events/test_event_id/checkin", + "/events/test_event_id/checkin", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) assert response.status_code == 200 assert response.json_body == {"msg": True} + def test_delete_event(): with Client(app) as client: with patch("chalicelib.decorators.jwt.decode") as mock_decode: @@ -170,9 +177,9 @@ def test_delete_event(): ) as mock_delete: mock_delete.return_value = {"status": True} response = client.http.delete( - f"/events/test_event_id", + "/events/test_event_id", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) assert response.status_code == 200 - assert response.json_body == {'status': True} \ No newline at end of file + assert response.json_body == {"status": True} diff --git a/tests/api/test_events_rush.py b/tests/api/test_events_rush.py index e79cad3..8ebecc4 100644 --- a/tests/api/test_events_rush.py +++ b/tests/api/test_events_rush.py @@ -10,6 +10,7 @@ with open("tests/fixtures/events/general/sample_rush_event.json") as f: SAMPLE_RUSH_EVENT = json.load(f) + def test_get_rush_events(): # Create a Chalice test client with Client(app) as client: @@ -22,7 +23,7 @@ def test_get_rush_events(): ) as mock_get_rush_categories_and_events: mock_get_rush_categories_and_events.return_value = SAMPLE_RUSH_EVENTS response = client.http.get( - f"/events/rush", + "/events/rush", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -30,6 +31,7 @@ def test_get_rush_events(): assert response.status_code == 200 assert response.json_body == SAMPLE_RUSH_EVENTS + def test_get_rush_event(): # Create a Chalice test client with Client(app) as client: @@ -42,7 +44,7 @@ def test_get_rush_event(): ) as mock_get_rush_event: mock_get_rush_event.return_value = SAMPLE_RUSH_EVENT response = client.http.get( - f"/events/rush/test_event_id", + "/events/rush/test_event_id", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -50,6 +52,7 @@ def test_get_rush_event(): assert response.status_code == 200 assert response.json_body == SAMPLE_RUSH_EVENT + def test_create_rush_category(): with Client(app) as client: with patch("chalicelib.decorators.jwt.decode") as mock_decode: @@ -60,13 +63,14 @@ def test_create_rush_category(): ) as mock_create_rush_category: mock_create_rush_category.return_value = {"msg": True} response = client.http.post( - f"/events/rush/category", + "/events/rush/category", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) assert response.status_code == 200 assert response.json_body == {"msg": True} + def test_create_rush_event(): with Client(app) as client: with patch("chalicelib.decorators.jwt.decode") as mock_decode: @@ -77,13 +81,14 @@ def test_create_rush_event(): ) as mock_create_rush_event: mock_create_rush_event.return_value = {"msg": True} response = client.http.post( - f"/events/rush", + "/events/rush", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) assert response.status_code == 200 assert response.json_body == {"msg": True} + def test_modify_rush_event(): with Client(app) as client: with patch("chalicelib.decorators.jwt.decode") as mock_decode: @@ -94,13 +99,14 @@ def test_modify_rush_event(): ) as mock_modify_rush_event: mock_modify_rush_event.return_value = {"msg": True} response = client.http.patch( - f"/events/rush", + "/events/rush", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) assert response.status_code == 200 assert response.json_body == {"msg": True} + def test_modify_rush_settings(): with Client(app) as client: with patch("chalicelib.decorators.jwt.decode") as mock_decode: @@ -111,13 +117,14 @@ def test_modify_rush_settings(): ) as mock_modify_rush_settings: mock_modify_rush_settings.return_value = {"msg": True} response = client.http.patch( - f"/events/rush/settings", + "/events/rush/settings", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) assert response.status_code == 200 assert response.json_body == {"msg": True} + def test_checkin_rush(): with Client(app) as client: with patch("chalicelib.decorators.jwt.decode") as mock_decode: @@ -128,13 +135,14 @@ def test_checkin_rush(): ) as mock_checkin_rush: mock_checkin_rush.return_value = {"msg": True} response = client.http.post( - f"/events/rush/checkin/test_event_id", + "/events/rush/checkin/test_event_id", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) assert response.status_code == 200 assert response.json_body == {"msg": True} + def test_delete_rush_event(): with Client(app) as client: with patch("chalicelib.decorators.jwt.decode") as mock_decode: @@ -145,9 +153,9 @@ def test_delete_rush_event(): ) as mock_delete_rush_event: mock_delete_rush_event.return_value = {"status": True} response = client.http.delete( - f"/events/rush/test_event_id", + "/events/rush/test_event_id", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) assert response.status_code == 200 - assert response.json_body == {'status': True} \ No newline at end of file + assert response.json_body == {"status": True} diff --git a/tests/api/test_insights.py b/tests/api/test_insights.py index ccec7b3..2bc7069 100644 --- a/tests/api/test_insights.py +++ b/tests/api/test_insights.py @@ -26,7 +26,7 @@ def test_get_insights_from_listing(): SAMPLE_DISTRIBUTION, ] response = client.http.get( - f"/insights/listing/test_listing_id", + "/insights/listing/test_listing_id", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) diff --git a/tests/api/test_listings.py b/tests/api/test_listings.py index b3728a6..6ad636a 100644 --- a/tests/api/test_listings.py +++ b/tests/api/test_listings.py @@ -41,7 +41,7 @@ def test_create_listing(): ) as mock_create: mock_create.return_value = {"msg": True} response = client.http.post( - f"/create", + "/create", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -77,7 +77,7 @@ def test_get_all_listings(): ) as mock_get_all: mock_get_all.return_value = TEST_LISTINGS response = client.http.get( - f"/listings", + "/listings", headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) @@ -162,7 +162,7 @@ def test_update_listing_field_route_not_found(): headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) - assert response.json_body == None + assert response.json_body is None def test_update_listing_field_route_bad_request(): @@ -182,7 +182,7 @@ def test_update_listing_field_route_bad_request(): # body, status_code = response.json_body - assert response.json_body == None + assert response.json_body is None def test_update_listing_field_route_exception(): @@ -200,4 +200,4 @@ def test_update_listing_field_route_exception(): headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, ) - assert response.json_body == None + assert response.json_body is None diff --git a/tests/services/test_applicant_service.py b/tests/services/test_applicant_service.py index 66f1f18..41f29ee 100644 --- a/tests/services/test_applicant_service.py +++ b/tests/services/test_applicant_service.py @@ -1,5 +1,5 @@ import pytest -from unittest.mock import MagicMock, patch +from unittest.mock import patch from chalicelib.services.ApplicantService import ApplicantService @@ -33,13 +33,12 @@ def test_get_all_applicants(service): mock_db.get_all.return_value = sample_data result = applicant_service.get_all() - mock_db.get_all.assert_called_once_with( - table_name="zap-applications" - ) + mock_db.get_all.assert_called_once_with(table_name="zap-applications") assert result == sample_data assert len(result) == 2 + def test_get_all_applicants_from_listing(service): applicant_service, mock_db = service @@ -52,9 +51,8 @@ def test_get_all_applicants_from_listing(service): result = applicant_service.get_all_from_listing(listing_id) mock_db.get_applicants.assert_called_once_with( - table_name="zap-applications", - listing_id=listing_id + table_name="zap-applications", listing_id=listing_id ) assert result == sample_data - assert len(result) == 2 \ No newline at end of file + assert len(result) == 2 diff --git a/tests/services/test_events_member_service.py b/tests/services/test_events_member_service.py index 850e8ae..989fc7c 100644 --- a/tests/services/test_events_member_service.py +++ b/tests/services/test_events_member_service.py @@ -1,5 +1,5 @@ import pytest -from unittest.mock import MagicMock, patch +from unittest.mock import patch from chalicelib.services.EventsMemberService import EventsMemberService import datetime import json @@ -16,18 +16,26 @@ with open("tests/fixtures/events/general/sample_rush_event.json") as f: SAMPLE_RUSH_EVENT = json.load(f) + @pytest.fixture def mock_mongo_module(): - with patch("chalicelib.modules.mongo.MongoModule", autospec=True) as MockMongoModule: + with patch( + "chalicelib.modules.mongo.MongoModule", autospec=True + ) as MockMongoModule: mock_instance = MockMongoModule(use_mock=True) yield mock_instance + @pytest.fixture def event_service(mock_mongo_module): return EventsMemberService(mock_mongo_module) + def test_insert_document(event_service, mock_mongo_module): - CREATE_TIMEFRAME = { "name": "testTimeframeName", "spreadsheetId": "testSpreadsheetId" } + CREATE_TIMEFRAME = { + "name": "testTimeframeName", + "spreadsheetId": "testSpreadsheetId", + } date_created = datetime.datetime.now() with patch("chalicelib.services.EventsMemberService.datetime") as mock_datetime: @@ -40,10 +48,11 @@ def test_insert_document(event_service, mock_mongo_module): assert result == {"msg": True} + def test_get_timeframe(event_service, mock_mongo_module): mock_mongo_module.get_document_by_id.return_value = SAMPLE_TIMEFRAMES[0] timeframe_id = SAMPLE_TIMEFRAMES[0]["_id"] - + result = event_service.get_timeframe(timeframe_id=timeframe_id) mock_mongo_module.get_document_by_id.assert_called_once_with( collection="events-timeframe", document_id=timeframe_id @@ -51,9 +60,10 @@ def test_get_timeframe(event_service, mock_mongo_module): assert result == json.dumps(SAMPLE_TIMEFRAMES[0]) + def test_get_all_timeframe(event_service, mock_mongo_module): mock_mongo_module.get_data_from_collection.return_value = SAMPLE_TIMEFRAMES - + result = event_service.get_all_timeframes() mock_mongo_module.get_data_from_collection.assert_called_once_with( collection="events-timeframe" @@ -61,6 +71,7 @@ def test_get_all_timeframe(event_service, mock_mongo_module): assert result == json.dumps(SAMPLE_TIMEFRAMES) + def test_delete_timeframe(event_service, mock_mongo_module): result = event_service.delete_timeframe(timeframe_id=SAMPLE_TIMEFRAMES[0]["_id"]) assert result["statusCode"] == 200 diff --git a/tests/services/test_insights_service.py b/tests/services/test_insights_service.py index d4953e0..3e4e4a6 100644 --- a/tests/services/test_insights_service.py +++ b/tests/services/test_insights_service.py @@ -1,37 +1,37 @@ import pytest -from unittest.mock import MagicMock, patch +from unittest.mock import patch from chalicelib.services.InsightsService import InsightsService import copy import json # Load JSON data from a file (insights test - general) -with open('tests/fixtures/insights/general/sample_applicants.json') as f: +with open("tests/fixtures/insights/general/sample_applicants.json") as f: SAMPLE_APPLICANTS = json.load(f) -with open('tests/fixtures/insights/general/sample_dashboard.json') as f: +with open("tests/fixtures/insights/general/sample_dashboard.json") as f: SAMPLE_DASHBOARD = json.load(f) -with open('tests/fixtures/insights/general/sample_distribution.json') as f: +with open("tests/fixtures/insights/general/sample_distribution.json") as f: SAMPLE_DISTRIBUTION = json.load(f) # Load JSON data from a file (insights test - no applicants) -with open('tests/fixtures/insights/noApplicants/sample_applicants.json') as f: +with open("tests/fixtures/insights/noApplicants/sample_applicants.json") as f: SAMPLE_APPLICANTS_NO_APP = json.load(f) -with open('tests/fixtures/insights/noApplicants/sample_dashboard.json') as f: +with open("tests/fixtures/insights/noApplicants/sample_dashboard.json") as f: SAMPLE_DASHBOARD_NO_APP = json.load(f) -with open('tests/fixtures/insights/noApplicants/sample_distribution.json') as f: +with open("tests/fixtures/insights/noApplicants/sample_distribution.json") as f: SAMPLE_DISTRIBUTION_NO_APP = json.load(f) # Load JSON data from a file (insights test - applicant with no gpa) -with open('tests/fixtures/insights/noGPAs/sample_applicants.json') as f: +with open("tests/fixtures/insights/noGPAs/sample_applicants.json") as f: SAMPLE_APPLICANTS_NO_GPA = json.load(f) -with open('tests/fixtures/insights/noGPAs/sample_dashboard.json') as f: +with open("tests/fixtures/insights/noGPAs/sample_dashboard.json") as f: SAMPLE_DASHBOARD_NO_GPA = json.load(f) -with open('tests/fixtures/insights/noGPAs/sample_distribution.json') as f: +with open("tests/fixtures/insights/noGPAs/sample_distribution.json") as f: SAMPLE_DISTRIBUTION_NO_GPA = json.load(f) @@ -51,8 +51,7 @@ def test_get_insights(service): result = insights_service.get_insights_from_listing(listing_id) # confirm that database was called once with correct inputs mock_db.get_applicants.assert_called_once_with( - table_name="zap-applications", - listing_id=listing_id + table_name="zap-applications", listing_id=listing_id ) # Convert Python dictionary to JSON format for comparison @@ -78,8 +77,7 @@ def test_get_insights_no_applicants(service): result = insights_service.get_insights_from_listing(listing_id) # confirm that database was called once with correct inputs mock_db.get_applicants.assert_called_once_with( - table_name="zap-applications", - listing_id=listing_id + table_name="zap-applications", listing_id=listing_id ) # Convert Python dictionary to JSON format for comparison @@ -105,8 +103,7 @@ def test_get_insights_no_gpas(service): result = insights_service.get_insights_from_listing(listing_id) # confirm that database was called once with correct inputs mock_db.get_applicants.assert_called_once_with( - table_name="zap-applications", - listing_id=listing_id + table_name="zap-applications", listing_id=listing_id ) # Convert Python dictionary to JSON format for comparison @@ -119,4 +116,4 @@ def test_get_insights_no_gpas(service): assert len(result) == 2 assert result_dash == json.dumps(SAMPLE_DASHBOARD_NO_GPA, sort_keys=True) - assert result_dist == json.dumps(SAMPLE_DISTRIBUTION_NO_GPA, sort_keys=True) \ No newline at end of file + assert result_dist == json.dumps(SAMPLE_DISTRIBUTION_NO_GPA, sort_keys=True) diff --git a/tests/services/test_listing_service.py b/tests/services/test_listing_service.py index 1362cbc..a608dfd 100644 --- a/tests/services/test_listing_service.py +++ b/tests/services/test_listing_service.py @@ -1,5 +1,5 @@ import pytest -from unittest.mock import MagicMock, patch +from unittest.mock import patch, Mock from chalicelib.services.ListingService import ListingService from chalice import NotFoundError from pydantic import ValidationError @@ -151,7 +151,9 @@ def test_toggle_visibility_exception(service): mock_db.toggle_visibility.side_effect = Exception("Error") - result = json.loads(listing_service.toggle_visibility(SAMPLE_LISTINGS[0]["listingId"])) + result = json.loads( + listing_service.toggle_visibility(SAMPLE_LISTINGS[0]["listingId"]) + ) assert result["statusCode"] == 500 @@ -172,10 +174,12 @@ def test_update_field_route(service): mock_db.update_listing_field.return_value = mock_updated_listing - result = json.loads(listing_service.update_field_route( - SAMPLE_LISTINGS[0]["listingId"], - {"field": "title", "value": "new test title"}, - )) + result = json.loads( + listing_service.update_field_route( + SAMPLE_LISTINGS[0]["listingId"], + {"field": "title", "value": "new test title"}, + ) + ) assert result["statusCode"] == 200 assert result["updated_listing"] == mock_updated_listing @@ -227,9 +231,6 @@ def test_update_field_updated_listing_not_found(service): assert str(exc_info.value) == "Listing not found" -from unittest.mock import Mock - - def test_update_field_validation_error(service): listing_service, _ = service diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 5b352c5..c52f0c5 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -1,6 +1,6 @@ -from unittest.mock import patch from chalicelib.decorators import add_env_suffix + def test_add_env_suffix_dev(): def mocked_function(self, table_name: str, *args, **kwargs): return table_name @@ -17,6 +17,7 @@ def mocked_function(self, table_name: str, *args, **kwargs): # Check if the suffix is added correctly assert result == "test-table-dev" + def test_add_env_suffix_prod(): def mocked_function(self, table_name: str, *args, **kwargs): return table_name @@ -31,4 +32,4 @@ def mocked_function(self, table_name: str, *args, **kwargs): result = decorated_function(instance_mock, "test-table", env=True) # Check if the suffix is added correctly - assert result == "test-table-prod" \ No newline at end of file + assert result == "test-table-prod" diff --git a/tests/test_dynamodb.py b/tests/test_dynamodb.py index da22c36..b77a3c1 100644 --- a/tests/test_dynamodb.py +++ b/tests/test_dynamodb.py @@ -101,10 +101,8 @@ def test_delete_item(db): db.put_data("test-table", SAMPLE_DATA[0]) response = db.delete_item("test-table", {"id": 123}) - assert response == True + assert response # TODO: Test case should fail, but isn't # response = db.delete_item("test-table", {"id": 124}) # assert response == False - - From c416c192d9553061044a48f0cc6d48fc56ab62a7 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sat, 3 Aug 2024 09:42:16 -0400 Subject: [PATCH 28/34] returned categoryName in API --- chalicelib/services/EventsRushService.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py index a934afd..952c063 100644 --- a/chalicelib/services/EventsRushService.py +++ b/chalicelib/services/EventsRushService.py @@ -354,7 +354,8 @@ def get_rush_category_analytics(self, category_id: str): else: attendees[email] = { **attendee, "eventsAttended": [new_event] } + result = { "categoryName": category["name"], "attendees": attendees } - return json.dumps(attendees, cls=self.BSONEncoder) + return json.dumps(result, cls=self.BSONEncoder) events_rush_service = EventsRushService() \ No newline at end of file From 3271fe45873d0eec26b619489c8fc6c7f906dc99 Mon Sep 17 00:00:00 2001 From: Jin Young Bang Date: Sat, 3 Aug 2024 22:06:53 -0400 Subject: [PATCH 29/34] feat: implement family tree get filter api --- chalicelib/api/members.py | 6 ++++++ chalicelib/services/MemberService.py | 15 +++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/chalicelib/api/members.py b/chalicelib/api/members.py index 57b3f32..37734f9 100644 --- a/chalicelib/api/members.py +++ b/chalicelib/api/members.py @@ -64,3 +64,9 @@ def delete_members(): def update_member_roles(user_id): data = members_api.current_request.json_body return member_service.update_roles(user_id, data["roles"]) + + +@members_api.route("/members/family-tree", methods=["GET"], cors=True) +@auth(members_api, roles=[Roles.MEMBER]) +def get_family_tree(): + return member_service.get_family_tree() diff --git a/chalicelib/services/MemberService.py b/chalicelib/services/MemberService.py index c5f8943..91297e3 100644 --- a/chalicelib/services/MemberService.py +++ b/chalicelib/services/MemberService.py @@ -2,6 +2,7 @@ from chalice import ConflictError, NotFoundError, UnauthorizedError from bson import ObjectId +from collections import defaultdict import json import jwt import boto3 @@ -118,5 +119,19 @@ def update_roles(self, document_id=str, roles=list) -> bool: [{"$set": {"roles": roles}}], ) + def get_family_tree(self): + data = mongo_module.get_data_from_collection(self.collection) + + # Group by family + family_groups = defaultdict(list) + + for member in data: + if "big" not in member or member["big"] == "": + continue + member["_id"] = str(member["_id"]) + family_groups[member["family"]].append(member) + + return family_groups + member_service = MemberService() From 4cb5535fde44b0806d9a1b2c5350a9d44ffd00c9 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 4 Aug 2024 12:43:34 -0400 Subject: [PATCH 30/34] swapped order of decorators in api (need to check auth before handling exceptions --> otherwise user will not get 401 error even if they're unauthenticated) --- chalicelib/api/events_rush.py | 4 +++- chalicelib/api/listings.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/chalicelib/api/events_rush.py b/chalicelib/api/events_rush.py index 8159289..7d62ca5 100644 --- a/chalicelib/api/events_rush.py +++ b/chalicelib/api/events_rush.py @@ -1,12 +1,14 @@ from chalice import Blueprint from chalicelib.decorators import auth from chalicelib.services.EventsRushService import events_rush_service +from chalicelib.models.roles import Roles + events_rush_api = Blueprint(__name__) @events_rush_api.route("/events/rush", methods=["GET"], cors=True) -@auth(events_rush_api, roles=["admin"]) +@auth(events_rush_api, roles=[Roles.ADMIN, Roles.MEMBER]) def get_rush_events(): return events_rush_service.get_rush_categories_and_events() diff --git a/chalicelib/api/listings.py b/chalicelib/api/listings.py index f9d58f6..482f24f 100644 --- a/chalicelib/api/listings.py +++ b/chalicelib/api/listings.py @@ -55,8 +55,8 @@ def toggle_visibility(id): @listings_api.route("/listings/{id}/update-field", methods=["PATCH"], cors=True) -@handle_exceptions @auth(listings_api, roles=[Roles.ADMIN, Roles.MEMBER]) +@handle_exceptions def update_listing_field_route(id): try: return listing_service.update_field_route( From 5a649a47a4babfd29905928fa41fc314916b8f3c Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 4 Aug 2024 12:52:09 -0400 Subject: [PATCH 31/34] adjusted roles to use Roles object --- chalicelib/api/accountability.py | 3 ++- chalicelib/api/events_member.py | 21 +++++++++++---------- chalicelib/api/events_rush.py | 11 ++++++----- chalicelib/api/members.py | 4 ++-- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/chalicelib/api/accountability.py b/chalicelib/api/accountability.py index 464c148..e82c988 100644 --- a/chalicelib/api/accountability.py +++ b/chalicelib/api/accountability.py @@ -1,12 +1,13 @@ from chalice import Blueprint from chalicelib.decorators import auth from chalicelib.services.AccountabilityService import accountability_service +from chalicelib.models.roles import Roles accountability_api = Blueprint(__name__) @accountability_api.route("/accountability", methods=["GET"], cors=True) -@auth(accountability_api, roles=["admin"]) +@auth(accountability_api, roles=[Roles.ADMIN, Roles.MEMBER]) def get_accountability(): if accountability_api.current_request.query_params: page = int(accountability_api.current_request.query_params.get("page", 0)) diff --git a/chalicelib/api/events_member.py b/chalicelib/api/events_member.py index 9ea805e..cba9fad 100644 --- a/chalicelib/api/events_member.py +++ b/chalicelib/api/events_member.py @@ -1,68 +1,69 @@ from chalice import Blueprint from chalicelib.decorators import auth from chalicelib.services.EventsMemberService import events_member_service +from chalicelib.models.roles import Roles events_member_api = Blueprint(__name__) @events_member_api.route("/timeframes", methods=["POST"], cors=True) -@auth(events_member_api, roles=["admin"]) +@auth(events_member_api, roles=[Roles.ADMIN]) def create_timeframe(): data = events_member_api.current_request.json_body return events_member_service.create_timeframe(data) @events_member_api.route("/timeframes", methods=["GET"], cors=True) -@auth(events_member_api, roles=["admin"]) +@auth(events_member_api, roles=[Roles.ADMIN, Roles.MEMBER]) def get_all_timeframes(): return events_member_service.get_all_timeframes() @events_member_api.route("/timeframes/{timeframe_id}", methods=["GET"], cors=True) -@auth(events_member_api, roles=["admin"]) +@auth(events_member_api, roles=[Roles.ADMIN, Roles.MEMBER]) def get_timeframe(timeframe_id: str): return events_member_service.get_timeframe(timeframe_id) @events_member_api.route("/timeframes/{timeframe_id}", methods=["DELETE"], cors=True) -@auth(events_member_api, roles=["admin"]) +@auth(events_member_api, roles=[Roles.ADMIN]) def delete_timeframe(timeframe_id: str): return events_member_service.delete_timeframe(timeframe_id) @events_member_api.route("/timeframes/{timeframe_id}/events", methods=["POST"], cors=True) -@auth(events_member_api, roles=["admin"]) +@auth(events_member_api, roles=[Roles.ADMIN]) def create_event(timeframe_id: str): data = events_member_api.current_request.json_body return events_member_service.create_event(timeframe_id, data) @events_member_api.route("/events/{event_id}", methods=["GET"], cors=True) -@auth(events_member_api, roles=["admin"]) +@auth(events_member_api, roles=[Roles.ADMIN, Roles.MEMBER]) def get_event(event_id: str): return events_member_service.get_event(event_id) @events_member_api.route("/timeframes/{timeframe_id}/sheets", methods=["GET"], cors=True) -@auth(events_member_api, roles=["admin"]) +@auth(events_member_api, roles=[Roles.ADMIN, Roles.MEMBER]) def get_timeframe_sheets(timeframe_id: str): return events_member_service.get_timeframe_sheets(timeframe_id) @events_member_api.route("/events/{event_id}/checkin", methods=["POST"], cors=True) -@auth(events_member_api, roles=["admin"]) +@auth(events_member_api, roles=[Roles.ADMIN, Roles.MEMBER]) def checkin(event_id: str): data = events_member_api.current_request.json_body return events_member_service.checkin(event_id, data) @events_member_api.route("/events/{event_id}", methods=["PATCH"], cors=True) -@auth(events_member_api, roles=["admin"]) +@auth(events_member_api, roles=[Roles.ADMIN]) def update_event(event_id: str): pass @events_member_api.route("/events/{event_id}", methods=["DELETE"], cors=True) -@auth(events_member_api, roles=["admin"]) +@auth(events_member_api, roles=[Roles.ADMIN]) def delete_event(event_id: str): return events_member_service.delete(event_id) \ No newline at end of file diff --git a/chalicelib/api/events_rush.py b/chalicelib/api/events_rush.py index 7d62ca5..44191c6 100644 --- a/chalicelib/api/events_rush.py +++ b/chalicelib/api/events_rush.py @@ -19,27 +19,27 @@ def get_rush_event(event_id): @events_rush_api.route("/events/rush/category", methods=["POST"], cors=True) -@auth(events_rush_api, roles=["admin"]) +@auth(events_rush_api, roles=[Roles.ADMIN]) def create_rush_category(): data = events_rush_api.current_request.json_body return events_rush_service.create_rush_category(data) @events_rush_api.route("/events/rush", methods=["POST"], cors=True) -@auth(events_rush_api, roles=["admin"]) +@auth(events_rush_api, roles=[Roles.ADMIN]) def create_rush_event(): data = events_rush_api.current_request.json_body return events_rush_service.create_rush_event(data) @events_rush_api.route("/events/rush", methods=["PATCH"], cors=True) -@auth(events_rush_api, roles=["admin"]) +@auth(events_rush_api, roles=[Roles.ADMIN]) def modify_rush_event(): data = events_rush_api.current_request.json_body return events_rush_service.modify_rush_event(data) @events_rush_api.route("/events/rush/settings", methods=["PATCH"], cors=True) -@auth(events_rush_api, roles=["admin"]) +@auth(events_rush_api, roles=[Roles.ADMIN]) def modify_rush_settings(): data = events_rush_api.current_request.json_body return events_rush_service.modify_rush_settings(data) @@ -58,11 +58,12 @@ def get_rush_events_default_category(): @events_rush_api.route("/events/rush/{event_id}", methods=["DELETE"], cors=True) +@auth(events_rush_api, roles=[Roles.ADMIN]) def delete_rush_event(event_id): return events_rush_service.delete_rush_event(event_id) @events_rush_api.route("/events/rush/{category_id}/analytics", methods=["GET"], cors=True) -@auth(events_rush_api, roles=["admin"]) +@auth(events_rush_api, roles=[Roles.ADMIN]) def get_rush_category_analytics(category_id): return events_rush_service.get_rush_category_analytics(category_id=category_id) \ No newline at end of file diff --git a/chalicelib/api/members.py b/chalicelib/api/members.py index 57b3f32..6300ebf 100644 --- a/chalicelib/api/members.py +++ b/chalicelib/api/members.py @@ -7,7 +7,7 @@ @members_api.route("/member/{user_id}", methods=["GET"], cors=True) -@auth(members_api, roles=["admin", "member"]) +@auth(members_api, roles=[Roles.ADMIN, Roles.MEMBER]) def get_member(user_id): member = member_service.get_by_id(user_id) return member if member else {} @@ -23,7 +23,7 @@ def update_member(user_id): @members_api.route("/members", methods=["GET"], cors=True) -@auth(members_api, roles=["admin", "member"]) +@auth(members_api, roles=[Roles.ADMIN, Roles.MEMBER]) def get_all_members(): """Fetches all members who have access to WhyPhi.""" return member_service.get_all() From c95cf8c35aaece38d1c3e06ad531d74c2c3640e5 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 11 Aug 2024 13:52:15 -0400 Subject: [PATCH 32/34] returned list of all events in object --- chalicelib/services/EventsRushService.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py index 952c063..f3a1268 100644 --- a/chalicelib/services/EventsRushService.py +++ b/chalicelib/services/EventsRushService.py @@ -342,19 +342,31 @@ def get_rush_category_analytics(self, category_id: str): # attendees : dict of all users (user: { name, email, eventsAttended: list of objects }) attendees = {} + # events: list of objects (event: { name, eventId }) + events = [] + for event in category["events"]: + new_event = { + "eventId": event["_id"], + "eventName": event["name"] + } + + # accumulate list of events + events.append(new_event) + + # accumulate attendance for attendee in event["attendees"]: - email =attendee["email"] - new_event = { - "eventId": event["_id"], - "eventName": event["name"] - } + email = attendee["email"] if email in attendees: attendees[email]["eventsAttended"].append(new_event) else: attendees[email] = { **attendee, "eventsAttended": [new_event] } - result = { "categoryName": category["name"], "attendees": attendees } + result = { + "categoryName": category["name"], + "attendees": attendees, + "events": events, + } return json.dumps(result, cls=self.BSONEncoder) From 4b9a5dbc27dc496dc6f951b483b8283164d7fe65 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Sun, 11 Aug 2024 16:08:03 -0400 Subject: [PATCH 33/34] adjusted backend to handle flags for hiding code and attendee data --- chalicelib/api/events_rush.py | 5 +++-- chalicelib/services/EventsRushService.py | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/chalicelib/api/events_rush.py b/chalicelib/api/events_rush.py index 44191c6..9ae5120 100644 --- a/chalicelib/api/events_rush.py +++ b/chalicelib/api/events_rush.py @@ -13,9 +13,10 @@ def get_rush_events(): return events_rush_service.get_rush_categories_and_events() -@events_rush_api.route("/events/rush/{event_id}", methods=["GET"], cors=True) +@events_rush_api.route("/events/rush/{event_id}", methods=["POST"], cors=True) def get_rush_event(event_id): - return events_rush_service.get_rush_event(event_id) + data = events_rush_api.current_request.json_body + return events_rush_service.get_rush_event(event_id=event_id, data=data) @events_rush_api.route("/events/rush/category", methods=["POST"], cors=True) diff --git a/chalicelib/services/EventsRushService.py b/chalicelib/services/EventsRushService.py index f3a1268..f7545f7 100644 --- a/chalicelib/services/EventsRushService.py +++ b/chalicelib/services/EventsRushService.py @@ -182,7 +182,10 @@ def modify_rush_settings(self, data: dict): return - def get_rush_event(self, event_id: str, hide_attendees: bool = True): + def get_rush_event(self, event_id: str, data: dict): + hide_attendees = data.get("hideAttendees", True) + hide_code = data.get("hideCode", True) + event = self.mongo_module.get_document_by_id( f"{self.collection_prefix}rush-event", event_id ) @@ -190,7 +193,8 @@ def get_rush_event(self, event_id: str, hide_attendees: bool = True): if hide_attendees: event.pop("attendeesId", None) - event.pop("code") + if hide_code: + event.pop("code") return json.dumps(event, cls=self.BSONEncoder) From e343ecf279f5335b434cf48687dc0a8cfe0fe005 Mon Sep 17 00:00:00 2001 From: William De Rocco <93288641+wderocco8@users.noreply.github.com> Date: Wed, 14 Aug 2024 21:31:13 -0400 Subject: [PATCH 34/34] updated unit tests --- tests/api/test_events_rush.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/api/test_events_rush.py b/tests/api/test_events_rush.py index 8ebecc4..914e22c 100644 --- a/tests/api/test_events_rush.py +++ b/tests/api/test_events_rush.py @@ -43,8 +43,9 @@ def test_get_rush_event(): "chalicelib.services.EventsRushService.events_rush_service.get_rush_event", ) as mock_get_rush_event: mock_get_rush_event.return_value = SAMPLE_RUSH_EVENT - response = client.http.get( + response = client.http.post( "/events/rush/test_event_id", + body={ "hideCode": False, "hideAttendees": False }, headers={"Authorization": "Bearer SAMPLE_TOKEN_STRING"}, )