Skip to content

Commit ce05836

Browse files
committed
items deleted in paprika will now delete in skylight
1 parent 17550a0 commit ce05836

File tree

3 files changed

+161
-29
lines changed

3 files changed

+161
-29
lines changed

whisk/multi_sync_engine.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -564,13 +564,55 @@ def _handle_deleted_items(self, pair: ListPairConfig,
564564
skylight_items: List[ListItem],
565565
changes: Dict[str, List[str]]) -> None:
566566
"""Handle items that were deleted from one of the services"""
567-
# For now, we'll implement a simplified version that focuses on core functionality
568-
# This can be enhanced later to handle deletion detection more robustly
569567
try:
570-
logger.debug("Deletion handling is simplified in this version")
571-
# TODO: Implement robust deletion detection
572-
# This would require tracking which items existed in previous sync
573-
# and comparing with current state
568+
logger.debug("Handling deleted items...")
569+
570+
# Get list UIDs for database queries
571+
pair_id = f"{pair.paprika_list}___{pair.skylight_list}"
572+
paprika_list_uid = pair_id.split('___')[0] # Use paprika list name as UID
573+
skylight_list_id = pair_id.split('___')[1] # Use skylight list name as list_id
574+
575+
# Get all linked items for this pair from database
576+
linked_items = self.state_manager.get_linked_items_for_pair(paprika_list_uid, skylight_list_id)
577+
578+
# Create sets of current item IDs for fast lookup
579+
current_paprika_ids = {item.paprika_id for item in paprika_items if item.paprika_id}
580+
current_skylight_ids = {item.skylight_id for item in skylight_items if item.skylight_id}
581+
582+
# Find items that were in database but missing from current API responses
583+
deleted_skylight_ids = []
584+
deleted_item_names = []
585+
586+
for link in linked_items:
587+
paprika_item = link.paprika_item
588+
skylight_item = link.skylight_item
589+
590+
# Check for Paprika → Skylight deletions
591+
# If Paprika item is missing from current response, delete from Skylight
592+
if paprika_item and paprika_item.paprika_id not in current_paprika_ids:
593+
if skylight_item and skylight_item.skylight_id:
594+
deleted_skylight_ids.append(skylight_item.skylight_id)
595+
deleted_item_names.append(paprika_item.name)
596+
logger.debug(f"Item '{paprika_item.name}' deleted from Paprika, will remove from Skylight")
597+
598+
# Bulk delete items from Skylight if any found
599+
if deleted_skylight_ids:
600+
logger.info(f"Found {len(deleted_skylight_ids)} items deleted from Paprika, removing from Skylight")
601+
try:
602+
self.skylight_client.bulk_delete_items(deleted_skylight_ids, pair.skylight_list)
603+
changes['skylight_deleted'].extend(deleted_item_names)
604+
logger.info(f"Successfully deleted {len(deleted_skylight_ids)} items from Skylight")
605+
except Exception as e:
606+
logger.error(f"Failed to bulk delete items from Skylight: {e}")
607+
# Continue processing - don't fail entire sync for deletion failures
608+
609+
# Note: We only handle Paprika → Skylight deletions because:
610+
# 1. Paprika doesn't support true deletion (only marking as purchased)
611+
# 2. The focus is on handling items deleted from Paprika appearing in Skylight
612+
# 3. This matches the plan requirements
613+
614+
logger.debug(f"Deletion handling completed: {len(deleted_skylight_ids)} items removed from Skylight")
615+
574616
except Exception as e:
575617
logger.error(f"Failed to handle deleted items: {e}")
576618
raise

whisk/skylight_client.py

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -573,42 +573,69 @@ def update_item(self, skylight_id: str, checked: bool, name: Optional[str] = Non
573573
logger.error(f"Failed to update item in Skylight: {e}")
574574
raise
575575

576-
def remove_item(self, skylight_id: str, list_name: str = None) -> None:
576+
def bulk_delete_items(self, skylight_ids: List[str], list_name: str) -> None:
577577
"""
578-
Remove item from list
578+
Remove multiple items from list using bulk destroy endpoint
579579
580580
Args:
581-
skylight_id: Skylight ID of the item to remove
582-
list_name: Optional list name to search in (REQUIRED for security)
581+
skylight_ids: List of Skylight IDs of items to remove
582+
list_name: Name of the list to remove items from (REQUIRED for security)
583583
"""
584584
try:
585-
logger.debug(f"Removing item from Skylight: {skylight_id}")
585+
if not skylight_ids:
586+
logger.debug("No items to delete")
587+
return
588+
589+
logger.debug(f"Bulk removing {len(skylight_ids)} items from Skylight list '{list_name}': {skylight_ids}")
586590

587591
if not list_name:
588592
raise ValueError("list_name is required - will not search all lists for security")
589593

590-
list_id = None
594+
# Get the list ID
595+
list_id = self.get_list_id_by_name(list_name)
596+
if not list_id:
597+
raise Exception(f"List '{list_name}' not found")
591598

592-
# Only search in the specified list (NEVER search other lists)
593-
try:
594-
target_list_id = self.get_list_id_by_name(list_name)
595-
if not target_list_id:
596-
raise Exception(f"List '{list_name}' not found")
597-
598-
# Check if item exists in this specific list
599-
items = self.get_list_items(list_name)
600-
if any(item.skylight_id == skylight_id for item in items):
601-
list_id = target_list_id
602-
logger.debug(f"Found item {skylight_id} in target list '{list_name}'")
599+
# Verify all items exist in this specific list (optional validation)
600+
existing_items = self.get_list_items(list_name)
601+
existing_ids = {item.skylight_id for item in existing_items}
602+
603+
# Filter out items that don't exist (log warning but don't fail)
604+
valid_ids = []
605+
for skylight_id in skylight_ids:
606+
if skylight_id in existing_ids:
607+
valid_ids.append(skylight_id)
603608
else:
604-
raise Exception(f"Item {skylight_id} not found in list '{list_name}'")
609+
logger.warning(f"Item {skylight_id} not found in list '{list_name}' - skipping")
605610

606-
except Exception as e:
607-
raise Exception(f"Cannot remove item {skylight_id}: {e}")
611+
if not valid_ids:
612+
logger.info("No valid items to delete after validation")
613+
return
614+
615+
# Use bulk destroy endpoint - the only working deletion method
616+
endpoint = f"/frames/{self.frame_id}/lists/{list_id}/list_items/bulk_destroy"
617+
payload = {"ids": valid_ids}
618+
619+
self._make_request("DELETE", endpoint, payload)
620+
logger.info(f"Bulk removed {len(valid_ids)} items from Skylight list '{list_name}': {valid_ids}")
621+
622+
except Exception as e:
623+
logger.error(f"Failed to bulk remove items from Skylight: {e}")
624+
raise
625+
626+
def remove_item(self, skylight_id: str, list_name: str = None) -> None:
627+
"""
628+
Remove single item from list (wrapper around bulk delete)
629+
630+
Args:
631+
skylight_id: Skylight ID of the item to remove
632+
list_name: Name of the list to search in (REQUIRED for security)
633+
"""
634+
try:
635+
logger.debug(f"Removing single item from Skylight: {skylight_id}")
608636

609-
# DELETE request
610-
self._make_request("DELETE", f"/frames/{self.frame_id}/lists/{list_id}/list_items/{skylight_id}")
611-
logger.info(f"Removed item from Skylight list '{list_name}': {skylight_id}")
637+
# Use bulk delete for single item (since individual delete doesn't work)
638+
self.bulk_delete_items([skylight_id], list_name)
612639

613640
except Exception as e:
614641
logger.error(f"Failed to remove item from Skylight: {e}")

whisk/state_manager.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,69 @@ def create_item_link(self, paprika_item_id: int, skylight_item_id: int,
578578
logger.error(f"Failed to create item link: {e}")
579579
raise
580580

581+
def get_linked_items_for_pair(self, paprika_list_uid: str, skylight_list_id: str) -> List[ItemLink]:
582+
"""
583+
Get all linked items for a specific list pair
584+
585+
Args:
586+
paprika_list_uid: Paprika list UID
587+
skylight_list_id: Skylight list ID
588+
589+
Returns:
590+
List of ItemLink objects with full item details
591+
"""
592+
try:
593+
cursor = self.conn.cursor()
594+
cursor.execute("""
595+
SELECT l.*,
596+
p.paprika_id, p.list_uid, p.name as p_name, p.checked as p_checked,
597+
p.last_modified_at as p_modified, p.is_deleted as p_deleted,
598+
s.skylight_id, s.list_id, s.name as s_name, s.checked as s_checked,
599+
s.skylight_updated_at as s_updated
600+
FROM item_links l
601+
JOIN paprika_items p ON l.paprika_item_id = p.id
602+
JOIN skylight_items s ON l.skylight_item_id = s.id
603+
WHERE p.list_uid = ? AND s.list_id = ? AND p.is_deleted = 0
604+
""", (paprika_list_uid, skylight_list_id))
605+
606+
links = []
607+
for row in cursor.fetchall():
608+
link = ItemLink(
609+
id=row['id'],
610+
paprika_item_id=row['paprika_item_id'],
611+
skylight_item_id=row['skylight_item_id'],
612+
linked_at=self._parse_datetime(row['linked_at']),
613+
confidence_score=row['confidence_score']
614+
)
615+
616+
# Attach item details
617+
link.paprika_item = PaprikaItem(
618+
id=row['paprika_item_id'],
619+
paprika_id=row['paprika_id'],
620+
list_uid=row['list_uid'],
621+
name=row['p_name'],
622+
checked=bool(row['p_checked']),
623+
last_modified_at=self._parse_datetime(row['p_modified']),
624+
is_deleted=bool(row['p_deleted'])
625+
)
626+
627+
link.skylight_item = SkylightItem(
628+
id=row['skylight_item_id'],
629+
skylight_id=row['skylight_id'],
630+
list_id=row['list_id'],
631+
name=row['s_name'],
632+
checked=bool(row['s_checked']),
633+
skylight_updated_at=self._parse_datetime(row['s_updated'])
634+
)
635+
636+
links.append(link)
637+
638+
return links
639+
640+
except Exception as e:
641+
logger.error(f"Failed to get linked items for pair: {e}")
642+
raise
643+
581644
def get_linked_items_with_conflicts(self) -> List[ItemLink]:
582645
"""Get linked items where Paprika and Skylight have different checked states"""
583646
try:

0 commit comments

Comments
 (0)