Skip to content

Commit cd6501b

Browse files
committed
fixed meal syncing
1 parent 0f98107 commit cd6501b

File tree

1 file changed

+146
-102
lines changed

1 file changed

+146
-102
lines changed

whisk/skylight_client.py

Lines changed: 146 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,14 @@ def _make_request(
265265

266266
try:
267267
response = self._session.request(method, url, json=data, headers=headers)
268+
268269
response.raise_for_status()
269270

271+
# Handle empty responses
272+
if not response.text.strip():
273+
logger.debug("Empty response body")
274+
return {}
275+
270276
return response.json()
271277

272278
except requests.exceptions.HTTPError as e:
@@ -628,6 +634,11 @@ def get_meal_sittings(self, start_date, end_date):
628634

629635
result = self._make_request("GET", endpoint)
630636

637+
# Handle case where result might be None
638+
if result is None:
639+
logger.warning("Meal sittings API returned None response")
640+
return []
641+
631642
# Handle JSON:API format response
632643
data = result.get("data", [])
633644
included = result.get("included", [])
@@ -643,15 +654,17 @@ def get_meal_sittings(self, start_date, end_date):
643654
relationships = item.get("relationships", {})
644655

645656
# Get meal category info
646-
meal_category_data = relationships.get("meal_category", {}).get("data", {})
647-
meal_category_id = meal_category_data.get("id")
657+
meal_category_rel = relationships.get("meal_category", {})
658+
meal_category_data = meal_category_rel.get("data") if meal_category_rel else None
659+
meal_category_id = meal_category_data.get("id") if meal_category_data else None
648660
meal_category_label = None
649661
if meal_category_id and meal_category_id in meal_categories:
650662
meal_category_label = meal_categories[meal_category_id]["attributes"]["label"]
651663

652664
# Get meal recipe info
653-
meal_recipe_data = relationships.get("meal_recipe", {}).get("data", {})
654-
meal_recipe_id = meal_recipe_data.get("id")
665+
meal_recipe_rel = relationships.get("meal_recipe", {})
666+
meal_recipe_data = meal_recipe_rel.get("data") if meal_recipe_rel else None
667+
meal_recipe_id = meal_recipe_data.get("id") if meal_recipe_data else None
655668
meal_recipe_summary = None
656669
if meal_recipe_id and meal_recipe_id in meal_recipes:
657670
meal_recipe_summary = meal_recipes[meal_recipe_id]["attributes"]["summary"]
@@ -702,72 +715,59 @@ def create_meal_sitting(self, name: str, date, meal_type: str):
702715
try:
703716
logger.debug(f"Creating meal sitting in Skylight: {name} on {date} ({meal_type})")
704717

705-
# First, we need to get available meal categories to find the right ID
706-
# For now, let's create a placeholder implementation
707-
# TODO: Add meal category lookup
718+
# Get correct meal category ID from API
708719
meal_category_id = self._get_meal_category_id(meal_type)
709-
710-
# Create meal recipe first
711-
recipe_data = {
712-
"data": {
713-
"type": "meal_recipe",
714-
"attributes": {
715-
"summary": name,
716-
"description": None
717-
},
718-
"relationships": {
719-
"meal_category": {
720-
"data": {
721-
"id": meal_category_id,
722-
"type": "meal_category"
723-
}
724-
}
725-
}
726-
}
720+
if not meal_category_id:
721+
raise Exception(f"Could not find meal category for type: {meal_type}")
722+
723+
# Use the actual payload format discovered from browser inspection
724+
sitting_data = {
725+
"meal_recipe_id": None,
726+
"meal_category_id": meal_category_id,
727+
"add_to_grocery_list": False,
728+
"date": date.isoformat(),
729+
"note": "",
730+
"rrule": None,
731+
"summary": name,
732+
"description": "",
733+
"saveToRecipeBox": False
727734
}
728735

729-
# Create the meal sitting with proper JSON:API format
730-
date_range_params = f"?date_min={date.isoformat()}&date_max={date.isoformat()}&include=meal_category%2Cmeal_recipe"
731-
endpoint = f"/frames/{self.frame_id}/meals/sittings{date_range_params}"
736+
# Use the correct endpoint with date range parameters (as seen in browser)
737+
params_str = f"?date_min={date.isoformat()}&date_max={date.isoformat()}&include=meal_category%2Cmeal_recipe"
738+
endpoint = f"/frames/{self.frame_id}/meals/sittings{params_str}"
732739

733-
# Use the structure from the API request you provided
734-
data = {
735-
"data": [
736-
{
737-
"type": "meal_sitting",
738-
"attributes": {
739-
"summary": name,
740-
"description": None,
741-
"note": None,
742-
"rrule": None,
743-
"recurring": False,
744-
"instances": [date.isoformat()]
745-
},
746-
"relationships": {
747-
"meal_category": {
748-
"data": {
749-
"id": meal_category_id,
750-
"type": "meal_category"
751-
}
752-
}
753-
}
754-
}
755-
],
756-
"meta": {
757-
"date_min": date.isoformat(),
758-
"date_max": date.isoformat()
759-
}
760-
}
740+
result = self._make_request("POST", endpoint, sitting_data)
761741

762-
result = self._make_request("POST", endpoint, data)
742+
# Log the response to understand the format
743+
logger.debug(f"Meal creation response: {result} (type: {type(result)})")
763744

764745
# Extract created meal ID from response
765-
created_meals = result.get("data", [])
766-
if created_meals:
767-
meal_id = created_meals[0].get("id")
768-
if meal_id:
746+
if isinstance(result, dict):
747+
# JSON:API format response
748+
created_meal = result.get("data", {})
749+
if created_meal:
750+
meal_id = created_meal.get("id")
751+
if meal_id:
752+
logger.info(f"Created meal sitting in Skylight: {name} (id={meal_id})")
753+
return str(meal_id)
754+
755+
# If no data field, try direct response
756+
if "id" in result:
757+
meal_id = result["id"]
769758
logger.info(f"Created meal sitting in Skylight: {name} (id={meal_id})")
770759
return str(meal_id)
760+
elif isinstance(result, list):
761+
# Response is a list - take the first item
762+
if result and len(result) > 0:
763+
first_item = result[0]
764+
if isinstance(first_item, dict) and "id" in first_item:
765+
meal_id = first_item["id"]
766+
logger.info(f"Created meal sitting in Skylight: {name} (id={meal_id})")
767+
return str(meal_id)
768+
769+
# Log the full response to understand the format
770+
logger.warning(f"Unexpected response format from meal creation: {result}")
771771

772772
raise Exception("No meal ID returned from create request")
773773

@@ -776,16 +776,62 @@ def create_meal_sitting(self, name: str, date, meal_type: str):
776776
raise
777777

778778
def _get_meal_category_id(self, meal_type: str):
779-
"""Get meal category ID for the given meal type (placeholder implementation)"""
780-
# TODO: Implement proper meal category lookup
781-
# For now, return a default ID based on meal type
782-
category_map = {
783-
"breakfast": "1",
784-
"lunch": "2",
785-
"dinner": "3",
786-
"snack": "4"
787-
}
788-
return category_map.get(meal_type.lower(), "1")
779+
"""Get meal category ID for the given meal type using dedicated categories API"""
780+
try:
781+
# Use the dedicated meal categories endpoint
782+
result = self._make_request("GET", f"/frames/{self.frame_id}/meals/categories")
783+
784+
logger.debug(f"Categories API response: {result} (type: {type(result)})")
785+
786+
# Handle both dict and list response formats
787+
categories_data = []
788+
if isinstance(result, dict):
789+
categories_data = result.get("data", [])
790+
elif isinstance(result, list):
791+
categories_data = result
792+
else:
793+
logger.error(f"Unexpected categories response type: {type(result)}")
794+
return None
795+
796+
# Extract meal categories from response data
797+
meal_categories = {}
798+
for item in categories_data:
799+
if item.get("type") == "meal_category":
800+
label = item["attributes"]["label"].lower()
801+
meal_categories[label] = item["id"]
802+
803+
logger.debug(f"Available meal categories: {meal_categories}")
804+
805+
# If no categories found, return None to trigger error
806+
if not meal_categories:
807+
logger.error("No meal categories found in Skylight frame")
808+
return None
809+
810+
# Map meal type to category
811+
meal_type_lower = meal_type.lower()
812+
if meal_type_lower in meal_categories:
813+
logger.debug(f"Found exact match for meal type '{meal_type}': {meal_categories[meal_type_lower]}")
814+
return meal_categories[meal_type_lower]
815+
816+
# Try some common mappings
817+
type_mapping = {
818+
"snack": "snacks", # Sometimes it's plural
819+
}
820+
821+
mapped_type = type_mapping.get(meal_type_lower, meal_type_lower)
822+
if mapped_type in meal_categories:
823+
logger.warning(f"Meal type '{meal_type}' not found, using '{mapped_type}' category")
824+
return meal_categories[mapped_type]
825+
826+
# Use any available category as last resort
827+
default_id = list(meal_categories.values())[0]
828+
default_label = list(meal_categories.keys())[0]
829+
logger.warning(f"Meal type '{meal_type}' not found, using available category '{default_label}' (ID: {default_id})")
830+
return default_id
831+
832+
except Exception as e:
833+
logger.error(f"Failed to get meal category ID: {e}")
834+
return None
789835

790836
def update_meal_sitting(self, sitting_id: str, name: str, date, meal_type: str):
791837
"""
@@ -801,35 +847,35 @@ def update_meal_sitting(self, sitting_id: str, name: str, date, meal_type: str):
801847
logger.debug(f"Updating meal sitting in Skylight: {sitting_id}")
802848

803849
meal_category_id = self._get_meal_category_id(meal_type)
804-
805-
# Update using the meal sittings endpoint
806-
date_range_params = f"?date_min={date.isoformat()}&date_max={date.isoformat()}&include=meal_category%2Cmeal_recipe"
807-
endpoint = f"/frames/{self.frame_id}/meals/sittings/{sitting_id}{date_range_params}"
808-
809-
data = {
810-
"data": {
811-
"type": "meal_sitting",
812-
"id": sitting_id,
813-
"attributes": {
814-
"summary": name,
815-
"description": None,
816-
"note": None,
817-
"rrule": None,
818-
"recurring": False,
819-
"instances": [date.isoformat()]
820-
},
821-
"relationships": {
822-
"meal_category": {
823-
"data": {
824-
"id": meal_category_id,
825-
"type": "meal_category"
826-
}
827-
}
828-
}
829-
}
850+
if not meal_category_id:
851+
raise Exception(f"Could not find meal category for type: {meal_type}")
852+
853+
# Use the same payload format as creation (discovered from browser)
854+
sitting_data = {
855+
"meal_recipe_id": None,
856+
"meal_category_id": meal_category_id,
857+
"add_to_grocery_list": False,
858+
"date": date.isoformat(),
859+
"note": "",
860+
"rrule": None,
861+
"summary": name,
862+
"description": "",
863+
"saveToRecipeBox": False
830864
}
831865

832-
result = self._make_request("PATCH", endpoint, data)
866+
# Try instance-based update first (similar to delete endpoint)
867+
try:
868+
date_params = f"?date_min={date.isoformat()}&date_max={date.isoformat()}&include=meal_category%2Cmeal_recipe"
869+
endpoint = f"/frames/{self.frame_id}/meals/sittings/{sitting_id}/instances/{date.isoformat()}{date_params}"
870+
result = self._make_request("PATCH", endpoint, sitting_data)
871+
logger.info(f"Updated meal sitting instance in Skylight: {sitting_id}")
872+
return
873+
except Exception as e:
874+
logger.warning(f"Instance-based update failed: {e}, trying direct update")
875+
876+
# Fallback to simple endpoint for updates
877+
endpoint = f"/frames/{self.frame_id}/meals/sittings/{sitting_id}"
878+
result = self._make_request("PATCH", endpoint, sitting_data)
833879
logger.info(f"Updated meal sitting in Skylight: {sitting_id}")
834880

835881
except Exception as e:
@@ -847,12 +893,10 @@ def delete_meal_sitting(self, sitting_id: str, date=None):
847893
try:
848894
logger.debug(f"Deleting meal sitting from Skylight: {sitting_id}")
849895

850-
# Use the correct endpoint format based on the API request you provided
851896
if date:
852-
# Delete specific instance (date-specific meal)
853-
endpoint = f"/frames/{self.frame_id}/meals/sittings/{sitting_id}/instances/{date.isoformat()}"
897+
# Delete specific instance (date-specific meal) - using discovered format
854898
date_params = f"?date_min={date.isoformat()}&date_max={date.isoformat()}&include=meal_category%2Cmeal_recipe"
855-
endpoint += date_params
899+
endpoint = f"/frames/{self.frame_id}/meals/sittings/{sitting_id}/instances/{date.isoformat()}{date_params}"
856900
else:
857901
# Delete entire meal sitting
858902
endpoint = f"/frames/{self.frame_id}/meals/sittings/{sitting_id}"

0 commit comments

Comments
 (0)