2323
2424import 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" })
0 commit comments