""" Soldout Check Service - Checks if cars are still available on Carmodoo """ import logging from datetime import datetime from typing import List, Tuple from sqlalchemy.orm import Session import httpx from ..models.car import Car logger = logging.getLogger(__name__) class SoldoutChecker: """Carmodoo에서 차량 판매 여부를 확인하는 서비스""" def __init__(self, db: Session, carmodoo_session_cookie: str = None): self.db = db self.carmodoo_session_cookie = carmodoo_session_cookie self.carmodoo_base_url = "https://dealer.carmodoo.com" async def check_car_availability(self, source_id: str) -> bool: """ Carmodoo에서 차량이 아직 판매 중인지 확인 Returns: True if car is still available, False if sold/removed """ try: async with httpx.AsyncClient(timeout=30.0) as client: # Carmodoo 차량 상세 페이지 접근 url = f"{self.carmodoo_base_url}/car/carPopView.html" params = {"carNo": source_id} headers = {} if self.carmodoo_session_cookie: headers["Cookie"] = self.carmodoo_session_cookie response = await client.get(url, params=params, headers=headers) if response.status_code == 404: return False # 페이지 내용에서 "판매완료", "삭제", "없는 차량" 등 확인 content = response.text.lower() sold_keywords = [ "판매완료", "판매 완료", "sold", "삭제된", "없는 차량", "존재하지 않", "찾을 수 없", "not found" ] for keyword in sold_keywords: if keyword in content: return False return True except Exception as e: logger.error(f"Error checking car {source_id}: {e}") # 에러 발생 시 available로 간주 (안전하게) return True async def check_all_cars(self) -> Tuple[int, int, List[int]]: """ 모든 활성 차량의 판매 여부 확인 Returns: (checked_count, soldout_count, soldout_car_ids) """ # soldout=False인 활성 차량만 조회 cars = self.db.query(Car).filter( Car.status == "active", Car.soldout == False, Car.source == "carmodoo" ).all() logger.info(f"Checking {len(cars)} cars for soldout status...") checked = 0 soldout_count = 0 soldout_ids = [] for car in cars: is_available = await self.check_car_availability(car.source_id) checked += 1 if not is_available: car.soldout = True soldout_count += 1 soldout_ids.append(car.id) logger.info(f"Car {car.id} ({car.car_name}) marked as SOLD OUT") # Rate limiting if checked % 10 == 0: logger.info(f"Progress: {checked}/{len(cars)} checked, {soldout_count} sold out") import asyncio await asyncio.sleep(1) self.db.commit() logger.info(f"Soldout check completed: {checked} checked, {soldout_count} sold out") return checked, soldout_count, soldout_ids def mark_soldout(self, car_id: int) -> bool: """수동으로 차량을 soldout 처리""" car = self.db.query(Car).filter(Car.id == car_id).first() if car: car.soldout = True self.db.commit() return True return False def mark_available(self, car_id: int) -> bool: """수동으로 차량을 available 처리""" car = self.db.query(Car).filter(Car.id == car_id).first() if car: car.soldout = False self.db.commit() return True return False