from sqlalchemy.orm import Session import logging from fastapi import BackgroundTasks from db.models import TCGPlayerGroups, SetCodeGroupIdMapping, ManaboxExportData, TCGPlayerProduct, ManaboxTCGPlayerMapping, UnmatchedManaboxData, TCGPlayerInventory from db.utils import db_transaction import uuid from services.tcgplayer import TCGPlayerService from sqlalchemy.sql import exists logger = logging.getLogger(__name__) class DataService: def __init__(self, db: Session, tcgplayer_service: TCGPlayerService): self.db = db self.tcgplayer_service = tcgplayer_service def _normalize_rarity(self, rarity: str) -> str: if rarity.lower() == "rare": return "R" elif rarity.lower() == "mythic": return "M" elif rarity.lower() == "uncommon": return "U" elif rarity.lower() == "common": return "C" elif rarity.lower() in ["R", "M", "U", "C"]: return rarity.upper() else: raise ValueError(f"Invalid rarity: {rarity}") def _normalize_condition(self, condition: str, foil: str) -> str: if condition.lower() == "near_mint": condition1 = "Near Mint" else: raise ValueError(f"Invalid condition: {condition}") if foil.lower() == "foil": condition2 = " Foil" elif foil.lower() == "normal": condition2 = "" else: raise ValueError(f"Invalid foil: {foil}") return condition1 + condition2 def _normalize_number(self, number: str) -> str: return str(number.split(".")[0]) def _convert_set_code_to_group_id(self, set_code: str) -> str: group = self.db.query(TCGPlayerGroups).filter(TCGPlayerGroups.abbreviation == set_code).first() return group.group_id def _add_set_group_mapping(self, set_code: str, group_id: str) -> None: with db_transaction(self.db): self.db.add(SetCodeGroupIdMapping(id=str(uuid.uuid4()), set_code=set_code, group_id=group_id)) def _get_set_codes(self, **filters) -> list: query = self.db.query(ManaboxExportData.set_code).distinct() for field, value in filters.items(): if value is not None: query = query.filter(getattr(ManaboxExportData, field) == value) return [code[0] for code in query.all()] async def bg_set_manabox_tcg_relationship(self, box_id: str = None, upload_id: str = None) -> None: if not bool(box_id) ^ bool(upload_id): raise ValueError("Must provide exactly one of box_id or upload_id") filters = {"box_id": box_id} if box_id else {"upload_id": upload_id} set_codes = self._get_set_codes(**filters) for set_code in set_codes: try: group_id = self._convert_set_code_to_group_id(set_code) except AttributeError: logger.warning(f"No group found for set code {set_code}") continue self._add_set_group_mapping(set_code, group_id) # update pricing for groups if self.db.query(TCGPlayerProduct).filter(TCGPlayerProduct.group_id == group_id).count() == 0: self.tcgplayer_service.update_pricing(set_name_ids={"set_name_ids":[group_id]}) # match manabox data to tcgplayer pricing data # match on manabox - set_code (through group_id), collector_number, foil, rarity, condition # match on tcgplayer - group_id, number, rarity, condition (condition + foil) # use normalizing functions matched_records = self.db.query(ManaboxExportData).filter(ManaboxExportData.set_code.in_(set_codes)).all() for record in matched_records: rarity = self._normalize_rarity(record.rarity) condition = self._normalize_condition(record.condition, record.foil) number = self._normalize_number(record.collector_number) group_id = self._convert_set_code_to_group_id(record.set_code) tcg_record = self.db.query(TCGPlayerProduct).filter( TCGPlayerProduct.group_id == group_id, TCGPlayerProduct.number == number, TCGPlayerProduct.rarity == rarity, TCGPlayerProduct.condition == condition ).all() if len(tcg_record) == 0: logger.warning(f"No match found for {record.name}") if self.db.query(UnmatchedManaboxData).filter(UnmatchedManaboxData.manabox_id == record.id).count() == 0: with db_transaction(self.db): self.db.add(UnmatchedManaboxData(id=str(uuid.uuid4()), manabox_id=record.id, reason="No match found")) elif len(tcg_record) > 1: logger.warning(f"Multiple matches found for {record.name}") if self.db.query(UnmatchedManaboxData).filter(UnmatchedManaboxData.manabox_id == record.id).count() == 0: with db_transaction(self.db): self.db.add(UnmatchedManaboxData(id=str(uuid.uuid4()), manabox_id=record.id, reason="Multiple matches found")) else: with db_transaction(self.db): self.db.add(ManaboxTCGPlayerMapping(id=str(uuid.uuid4()), manabox_id=record.id, tcgplayer_id=tcg_record[0].id)) async def bg_set_tcg_inventory_product_relationship(self, export_id: str) -> None: inventory_without_product = ( self.db.query(TCGPlayerInventory.tcgplayer_id, TCGPlayerInventory.set_name) .filter(TCGPlayerInventory.total_quantity > 0) .filter(TCGPlayerInventory.product_line == "Magic") .filter(TCGPlayerInventory.export_id == export_id) .filter(TCGPlayerInventory.tcgplayer_product_id.is_(None)) .filter(~exists().where( TCGPlayerProduct.id == TCGPlayerInventory.tcgplayer_product_id )) .all() ) set_names = list(set(inv.set_name for inv in inventory_without_product if inv.set_name is not None and isinstance(inv.set_name, str))) group_ids = self.db.query(TCGPlayerGroups.group_id).filter( TCGPlayerGroups.name.in_(set_names) ).all() group_ids = [str(group_id[0]) for group_id in group_ids] self.tcgplayer_service.update_pricing(set_name_ids={"set_name_ids": group_ids}) for inventory in inventory_without_product: product = self.db.query(TCGPlayerProduct).filter( TCGPlayerProduct.tcgplayer_id == inventory.tcgplayer_id ).first() if product: with db_transaction(self.db): inventory_record = self.db.query(TCGPlayerInventory).filter( TCGPlayerInventory.tcgplayer_id == inventory.tcgplayer_id, TCGPlayerInventory.export_id == export_id ).first() if inventory_record: inventory_record.tcgplayer_product_id = product.id self.db.add(inventory_record)