Skip to content

Commit f3e1775

Browse files
Windsor/routes (#4)
* added feature 1 for dining_venues * add: scaffold for api routes --------- Co-authored-by: Santi-GT <scriado01@gradienttradingllc.com>
1 parent 8dd4448 commit f3e1775

39 files changed

+1745
-432
lines changed

backend/hoagiemeal/api/dining.py

Lines changed: 92 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,18 @@
2323

2424
import msgspec.json as msj
2525

26-
from hoagiemeal.logger import logger
27-
from student_app import StudentApp
28-
from schemas import Schemas
26+
from rest_framework.decorators import api_view
27+
from rest_framework.response import Response
28+
from rest_framework.exceptions import APIException
29+
from django.views.decorators.cache import cache_page
2930

31+
from hoagiemeal.utils.logger import logger
32+
from hoagiemeal.api.student_app import StudentApp
33+
from hoagiemeal.api.schemas import Schemas
3034

31-
class Dining(StudentApp):
32-
"""Handles functionalities related to dining information, such as locations, menus, and events."""
35+
36+
class DiningAPI(StudentApp):
37+
"""Handle functionalities related to dining information, such as locations, menus, and events."""
3338

3439
def __init__(self):
3540
super().__init__()
@@ -39,168 +44,108 @@ def __init__(self):
3944
self.schemas = Schemas()
4045

4146
def get_locations(self, fmt: str = "xml") -> dict:
42-
"""Fetch a list of dining locations in XML format."""
43-
logger.info("Fetching dining locations.")
44-
response = self._make_request(
45-
self.DINING_LOCATIONS,
46-
params={"categoryId": "2"}, # how do i get 3 as well?
47-
fmt=fmt,
48-
)
49-
schema = self.schemas.get_dining_xsd()
50-
if not self.validate_xml(response, schema):
51-
logger.error("Invalid XML format for dining locations.")
52-
raise ValueError("Invalid XML format for dining locations.")
53-
return self._parse_xml(response)
54-
55-
def get_events(self, placeID: str = "1007") -> dict:
56-
"""Fetch dining venue open hours as an iCal stream for a given placeID.
57-
58-
Note: "1007" seems to correspond to the Princeton Dining Calendar.
47+
"""Fetch a list of dining locations in XML format.
48+
49+
NOTE: The API expects the parameters to be in camelCase.
50+
51+
Args:
52+
fmt (str): The format of the response. Defaults to "xml".
53+
54+
Returns:
55+
dict: A dictionary containing the dining locations.
56+
57+
"""
58+
try:
59+
response = self._make_request(
60+
self.DINING_LOCATIONS,
61+
params={"categoryId": "2"},
62+
fmt=fmt,
63+
)
64+
schema = self.schemas.get_dining_xsd()
65+
if not self.validate_xml(response, schema):
66+
raise APIException("Invalid XML format for dining locations")
67+
return self._parse_xml(response)
68+
except Exception as e:
69+
logger.error(f"Error fetching dining locations: {e}")
70+
raise APIException(str(e)) from e
71+
72+
def get_events(self, place_id: str = "1007") -> dict:
73+
"""Fetch dining venue open hours as an iCal stream for a given place_id.
74+
75+
NOTE: "1007" seems to correspond to the Princeton Dining Calendar.
76+
NOTE: The API expects the parameters to be in camelCase.
5977
6078
Remark: The response uses LF line-endings with a Content-Type of text/plain
6179
but is actually in iCal (text/calendar) format.
6280
6381
Args:
64-
placeID (str): The ID of the dining venue.
82+
place_id (str): The ID of the dining venue.
6583
6684
Returns:
6785
dict: Parsed iCal data with event details (summary, start time, end time, etc.).
6886
6987
"""
70-
logger.info(f"Fetching dining events for placeID: {placeID}.")
71-
params = {"placeID": placeID}
72-
response = self._make_request(self.DINING_EVENTS, params=params, fmt="ical")
73-
return self._parse_ical(response)
88+
try:
89+
response = self._make_request(self.DINING_EVENTS, params={"placeId": place_id}, fmt="ical")
90+
return self._parse_ical(response)
91+
except Exception as e:
92+
logger.error(f"Error fetching dining events: {e}")
93+
raise APIException(str(e)) from e
7494

75-
def get_menu(self, locationID: str, menuID: str) -> dict:
95+
def get_menu(self, location_id: str, menu_id: str) -> dict:
7696
"""Fetch the menu for a specific dining location.
7797
98+
NOTE: The API expects the parameters to be in camelCase.
99+
78100
Args:
79-
locationID (str): The ID of the dining location.
80-
menuID (str): The ID of the dining menu.
101+
location_id (str): The ID of the dining location.
102+
menu_id (str): The ID of the dining menu.
81103
82104
Returns:
83105
dict: A JSON object containing the menu details.
84106
85107
"""
86-
logger.info(f"Fetching dining menu for locationID: {locationID}, menuID: {menuID}.")
87-
params = {"locationID": locationID, "menuID": menuID}
88-
response = self._make_request(self.DINING_MENU, params=params)
89-
response = msj.decode(response)
90-
return response
91-
92-
93-
def _test_dining_locations():
94-
# Create an instance of the Dining class
95-
dining = Dining()
96-
97-
# Test: Get Dining Locations
98-
logger.info("Testing: Get Dining Locations...")
99-
try:
100-
# Fetch dining locations in XML format and log the raw XML response
101-
locations_xml = dining.get_locations(fmt="xml")
102-
logger.info(f"Dining Locations (Raw XML): {locations_xml}")
103-
104-
# Extract location IDs with corresponding names
105-
locations = locations_xml.get("locations", {}).get("location", [])
106-
location_id_name_pairs = []
107-
108-
# Loop over each location in the response
109-
for loc in locations:
110-
building = loc.get("building", {})
111-
location_id = building.get("location_id", "Unknown ID")
112-
location_name = loc.get("name", "Unknown Name")
113-
location_id_name_pairs.append((location_id, location_name))
114-
115-
# Log and return the location ID and name pairs
116-
logger.info(f"Location ID and Name Pairs: {location_id_name_pairs}")
117-
logger.info(f"Location ID length: {len(location_id_name_pairs)}")
118-
return location_id_name_pairs
119-
120-
except Exception as e:
121-
logger.error(f"Error fetching dining locations: {e}")
122-
123-
124-
def _test_get_events():
125-
dining = Dining()
126-
logger.info("Testing: Get Dining Events...")
127-
try:
128-
events = dining.get_events(placeID="1007") # 1007 ~ Princeton Dining Calendar?
129-
logger.info(f"Dining Events fetched: {events}")
130-
131-
# Iterate and log each event detail
132-
event_list = events.get("events", [])
133-
for event in event_list:
134-
summary = event.get("summary", "No Summary")
135-
start = event.get("start", "No Start Time")
136-
end = event.get("end", "No End Time")
137-
uid = event.get("uid", "No UID")
138-
description = event.get("description", "No Description")
139-
logger.info(
140-
f"Event Summary: {summary}, Start: {start}, End: {end}, UID: {uid}, Description: {description}"
141-
)
142-
143-
except Exception as e:
144-
logger.error(f"Error fetching dining events: {e}")
145-
146-
147-
def _test_dining_apis():
148-
dining = Dining()
149-
response = dining.get_locations()
150-
151-
for location in response["locations"]["location"]:
152-
name = location.get("name")
153-
map_name = location.get("mapName")
154-
dbid = location.get("dbid")
155-
latitude = location["geoloc"].get("lat")
156-
longitude = location["geoloc"].get("long")
157-
building_name = location["building"].get("name")
158-
159-
# Handle both single and multiple amenities
160-
amenities_data = location["amenities"]["amenity"]
161-
if isinstance(amenities_data, list):
162-
amenities = [amenity["name"] for amenity in amenities_data]
163-
else:
164-
amenities = [amenities_data["name"]]
165-
166-
logger.info(f"Location Name: {name}")
167-
logger.info(f"Map Name: {map_name}")
168-
logger.info(f"Database ID: {dbid}")
169-
logger.info(f"Latitude: {latitude}")
170-
logger.info(f"Longitude: {longitude}")
171-
logger.info(f"Building Name: {building_name}")
172-
logger.info(f"Amenities: {', '.join(amenities)}")
173-
logger.info("---" * 40)
174-
175108
try:
176-
# Fetch the menu data
177-
menu_data = dining.get_menu(locationID=dbid, menuID="2024-10-24-Dinner")
178-
logger.debug(f"Raw menu data fetched: {menu_data}")
179-
180-
# Now check if menu_data is a dictionary and has the "menus" key
181-
if isinstance(menu_data, dict) and "menus" in menu_data:
182-
logger.info(f"Dining Menu for {name}:")
183-
for item in menu_data["menus"]:
184-
menu_id = item.get("id")
185-
menu_name = item.get("name")
186-
description = item.get("description")
187-
link = item.get("link")
188-
189-
logger.info(f" - Menu Item ID: {menu_id}")
190-
logger.info(f" Name: {menu_name}")
191-
logger.info(f" Description: {description}")
192-
logger.info(f" Link: {link}") # Scrape data from this link
193-
logger.info("---" * 40)
194-
else:
195-
logger.warning(f"No valid menu data available for {name} (location ID: {dbid}).")
109+
response = self._make_request(self.DINING_MENU, params={"locationId": location_id, "menuId": menu_id})
110+
return msj.decode(response)
196111
except Exception as e:
197-
logger.error(f"Exception occurred: {e}")
198-
logger.error(
199-
f"Location Details - Name: {name}, DBID: {dbid}, Latitude: {menu_data.get('geoloc', {}).get('lat', 'N/A')}, Longitude: {menu_data.get('geoloc', {}).get('long', 'N/A')}"
200-
)
112+
logger.error(f"Error fetching menu: {e}")
113+
raise APIException(str(e)) from e
114+
115+
116+
#################### Exposed endpoints #########################
117+
118+
dining_api = DiningAPI()
119+
120+
121+
@api_view(["GET"])
122+
@cache_page(60 * 15)
123+
def get_locations(request):
124+
"""Get all dining locations."""
125+
locations = dining_api.get_locations()
126+
location_list = locations.get("locations", {}).get("location", [])
127+
return Response({"data": location_list, "message": "Successfully fetched dining locations"})
128+
129+
130+
@api_view(["GET"])
131+
@cache_page(60 * 5)
132+
def get_events(request):
133+
"""Get dining events/hours."""
134+
place_id = request.query_params.get("place_id", "1007")
135+
events = dining_api.get_events(place_id)
136+
return Response({"data": events.get("events", []), "message": "Successfully fetched dining events"})
137+
138+
139+
@api_view(["GET"])
140+
@cache_page(60 * 5)
141+
def get_menu(request):
142+
"""Get menu for a specific location."""
143+
location_id = request.query_params.get("location_id")
144+
menu_id = request.query_params.get("menu_id")
145+
146+
if not location_id or not menu_id:
147+
return Response({"error": "location_id and menu_id are required"}, status=400)
201148

149+
menu = dining_api.get_menu(location_id, menu_id)
202150

203-
if __name__ == "__main__":
204-
_test_dining_locations()
205-
_test_get_events()
206-
_test_dining_apis()
151+
return Response({"data": menu.get("menus", []), "message": "Successfully fetched menu"})

backend/hoagiemeal/api/schemas.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@
2424

2525
import msgspec.json as msj
2626

27-
from hoagiemeal.utils import deprecated
28-
from hoagiemeal.logger import logger
29-
from student_app import StudentApp
27+
from hoagiemeal.utils.deprecated import deprecated
28+
from hoagiemeal.utils.logger import logger
29+
from hoagiemeal.api.student_app import StudentApp
3030

3131

3232
class Schemas(StudentApp):

backend/hoagiemeal/api/student_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import xmlschema
3030
from dotenv import load_dotenv
3131
from icalendar import Calendar
32-
from hoagiemeal.logger import logger
32+
from hoagiemeal.utils.logger import logger
3333
from msgspec import DecodeError
3434

3535
load_dotenv()

0 commit comments

Comments
 (0)