Skip to content

Commit

Permalink
Commit to meal request mutation (#105)
Browse files Browse the repository at this point in the history
* Implement commitToMealRequest mutation; rename meal_description field to food_description

* Use enum for meal statuses

* Basic test for meal request commitment

* Add clarifying comments

* Add failure tests

* Run linter

* Remove get_meal_requests_by_requestor_id pipelined code

* Remove meal request description and mealSugestions fields from test

* Revert food_description to meal_description

* Add Fulfilled meal request status

* renamed doner_id to requester in graphql mutation

---------

Co-authored-by: Shahan Neda <shahan.neda@gmail.com>
  • Loading branch information
williamhpark and shahanneda authored Dec 23, 2023
1 parent 4f83611 commit e80bd10
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 17 deletions.
28 changes: 28 additions & 0 deletions backend/app/graphql/meal_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,37 @@ def mutate(
return UpdateMealRequest(meal_request=result)


class CommitToMealRequest(Mutation):
class Arguments:
requester = graphene.ID(required=True)
meal_request_ids = graphene.List(graphene.ID, required=True)
meal_description = graphene.String(required=True)
additional_info = graphene.String(default_value=None)

meal_requests = graphene.List(MealRequestResponse)

def mutate(
self,
info,
requester,
meal_request_ids,
meal_description,
additional_info=None,
):
result = services["meal_request_service"].commit_to_meal_request(
donor_id=requester,
meal_request_ids=meal_request_ids,
meal_description=meal_description,
additional_info=additional_info,
)

return CommitToMealRequest(meal_requests=result)


class MealRequestMutations(MutationList):
create_meal_request = CreateMealRequests.Field()
update_meal_request = UpdateMealRequest.Field()
commit_to_meal_request = CommitToMealRequest.Field()


class MealRequestQueries(QueryList):
Expand Down
7 changes: 5 additions & 2 deletions backend/app/models/meal_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

class MealStatus(Enum):
OPEN = "Open"
UPCOMING = "Upcoming"
FULFILLED = "Fulfilled"
CANCELLED = "Cancelled"

Expand All @@ -25,13 +26,15 @@ class MealInfo(mg.EmbeddedDocument):
class DonationInfo(mg.EmbeddedDocument):
donor = mg.ReferenceField(User, required=True)
commitment_date = mg.DateTimeField(required=True)
meal_description = mg.StringField(default=None)
meal_description = mg.StringField(required=True)
additional_info = mg.StringField(default=None)


class MealRequest(mg.Document):
requestor = mg.ReferenceField(User, required=True)
status = mg.EnumField(MealStatus, required=True, default=MealStatus.OPEN)
status = mg.StringField(
choices=MEAL_STATUSES, required=True, default=MealStatus.OPEN.value
)
drop_off_datetime = mg.DateTimeField(required=True)
drop_off_location = mg.StringField(required=True)
meal_info = mg.EmbeddedDocumentField(MealInfo, required=True)
Expand Down
12 changes: 8 additions & 4 deletions backend/app/models/user_info.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from enum import Enum
import mongoengine as mg


USERINFO_ROLE_ADMIN = "Admin"
USERINFO_ROLE_DONOR = "Donor"
USERINFO_ROLE_ASP = "ASP"
USERINFO_ROLES = [USERINFO_ROLE_ADMIN, USERINFO_ROLE_DONOR, USERINFO_ROLE_ASP]
class UserInfoRole(Enum):
ADMIN = "Admin"
DONOR = "Donor"
ASP = "ASP"


USERINFO_ROLES = [role.value for role in UserInfoRole]

DONOR_TYPE_RESTAURANT = "Restaurant"
DONOR_TYPE_INDIVIDUAL = "Individual"
Expand Down
14 changes: 5 additions & 9 deletions backend/app/resources/validate_utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from datetime import datetime

from ..models.user_info import (
UserInfoRole,
USERINFO_ROLES,
USERINFO_ROLE_ASP,
USERINFO_ROLE_ADMIN,
USERINFO_ROLE_DONOR,
)


Expand All @@ -28,12 +26,12 @@ def validate_contact(contact, contact_str, error_list):


def validate_role_info(role, role_info, role_info_str, error_list):
if not isinstance(role_info, dict) and role != USERINFO_ROLE_ADMIN:
if not isinstance(role_info, dict) and role != UserInfoRole.ADMIN.value:
error_list.append(f"The {role_info_str} supplied is not a dict.")
return error_list

asp_info_fields = ["num_kids"]
if role == USERINFO_ROLE_ASP:
if role == UserInfoRole.ASP.value:
for field in asp_info_fields:
role_info = role_info["asp_info"]
if field not in role_info:
Expand Down Expand Up @@ -90,7 +88,7 @@ def validate_userinfo(userinfo, error_list):

for field in userinfo_fields:
if field not in userinfo and (
field != "role_info" or userinfo["role"] != USERINFO_ROLE_ADMIN
field != "role_info" or userinfo["role"] != UserInfoRole.ADMIN.value
):
error_list.append(f'The info supplied does not have field "{field}".')
for key, val in userinfo.items():
Expand Down Expand Up @@ -177,7 +175,7 @@ def validate_donation_info(donation_info, error_list):
)
elif key == "donor":
validate_user(val, "donation_info.donor", error_list)
if val["info"]["role"] != USERINFO_ROLE_DONOR:
if val["info"]["role"] != UserInfoRole.DONOR.value:
error_list.append(
"The donation_info.donor supplied is not a donor user."
)
Expand All @@ -186,8 +184,6 @@ def validate_donation_info(donation_info, error_list):
error_list.append(
"The commitment_date supplied is not a datetime object."
)
if val < datetime.now():
error_list.append("The commitment_date supplied is invalid.")
elif key == "meal_description" and type(val) is not str:
error_list.append("The meal_description supplied is not a string.")
elif key == "additional_info" and type(val) is not str:
Expand Down
56 changes: 56 additions & 0 deletions backend/app/services/implementations/meal_request_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from ..interfaces.meal_request_service import IMealRequestService
from datetime import datetime

from ...models.meal_request import DonationInfo, MealStatus
from ...models.user import User
from ...models.user_info import UserInfoRole
from ...graphql.types import SortDirection
from ...resources.meal_request_dto import MealRequestDTO

Expand Down Expand Up @@ -94,6 +96,59 @@ def update_meal_request(
original_meal_request.save()
return meal_request_dto

def commit_to_meal_request(
self,
donor_id: str,
meal_request_ids: [str],
meal_description: str,
additional_info: str,
) -> [MealRequestDTO]:
try:
donor = User.objects(id=donor_id).first()
if not donor:
raise Exception(f'user "{donor_id}" not found')
# The user committing to the meal request must have the "Donor" role
if donor.info.role != UserInfoRole.DONOR.value:
raise Exception(f'user "{donor_id}" is not a donor')

if len(meal_request_ids) == 0:
raise Exception("no meal requests to commit to")

meal_request_dtos = []
for meal_request_id in meal_request_ids:
meal_request = MealRequest.objects(id=meal_request_id).first()
if not meal_request:
raise Exception(f'meal request "{meal_request_id}" not found')
# The meal request must be in the "Open" status
if meal_request.status != MealStatus.OPEN.value:
raise Exception(
f'meal request "{meal_request_id}" is not open for commitment'
)

meal_request.donation_info = DonationInfo(
donor=donor,
commitment_date=datetime.utcnow(),
meal_description=meal_description,
additional_info=additional_info,
)

# Change the meal request's status to "Upcoming"
meal_request.status = MealStatus.UPCOMING.value

meal_request_dtos.append(
self.convert_meal_request_to_dto(
meal_request, meal_request.requestor
)
)

meal_request.save()

return meal_request_dtos

except Exception as error:
self.logger.error(str(error))
raise error

def convert_meal_request_to_dto(
self, request: MealRequest, requestor: User
) -> MealRequestDTO:
Expand All @@ -106,6 +161,7 @@ def convert_meal_request_to_dto(
if not donor:
raise Exception(f'donor "{donor_id}" not found')
request_dict["donation_info"]["donor"] = donor.to_serializable_dict()

return MealRequestDTO(**request_dict)

def get_meal_requests_by_requestor_id(
Expand Down
15 changes: 14 additions & 1 deletion backend/app/services/interfaces/meal_request_service.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from abc import ABC, abstractmethod
from typing import Union

from ...resources.meal_request_dto import MealRequestDTO


class IMealRequestService(ABC):
Expand Down Expand Up @@ -40,6 +43,16 @@ def update_meal_request(
):
pass

@abstractmethod
def commit_to_meal_request(
self,
donor_id: str,
meal_request_ids: [str],
meal_description: str,
additional_info: Union[str, None],
) -> [MealRequestDTO]:
pass

@abstractmethod
def get_meal_requests_by_requestor_id(
self,
Expand All @@ -60,7 +73,7 @@ def get_meal_requests_by_requestor_id(
:type min_drop_off_date: datetime
:param max_drop_off_date: the maximum drop off date
:type max_drop_off_date: datetime
:param status: the status of the MealRequest (Open, Fulfilled, Cancelled)
:param status: the status of the MealRequest
:type status: string
:param offset: the offset to start from
:type offset: int
Expand Down
Loading

0 comments on commit e80bd10

Please sign in to comment.