diff --git a/backend/main.py b/backend/main.py index b14e504..d5d0d75 100644 --- a/backend/main.py +++ b/backend/main.py @@ -502,6 +502,21 @@ social_proof_cache = BoundedCache(maxsize=100, ttl=3600) scraping_progress = BoundedCache(maxsize=50, ttl=7200) dashboard_cache = BoundedCache(maxsize=50, ttl=3600) enrichment_progress = BoundedCache(maxsize=50, ttl=7200) +product_lookup_cache = BoundedCache(maxsize=500, ttl=3600) + +# IP rate limiter for public product lookup +_product_lookup_ip_cache: dict = {} +_product_lookup_ip_lock = Lock() +_PRODUCT_LOOKUP_MAX = 10 # per IP per minute +_TRENDYOL_URL_RE = re.compile(r'trendyol\.com/.*?-p-(\d+)', re.IGNORECASE) + +def _check_ip_rate_limit(ip: str) -> bool: + now = time.time(); cutoff = now - 60.0 + with _product_lookup_ip_lock: + ts = [t for t in _product_lookup_ip_cache.get(ip, []) if t > cutoff] + if len(ts) >= _PRODUCT_LOOKUP_MAX: + _product_lookup_ip_cache[ip] = ts; return False + ts.append(now); _product_lookup_ip_cache[ip] = ts; return True # DISABLED: Questions, similar products, and followers features removed per user request # questions_cache = {} @@ -4128,6 +4143,39 @@ async def _stop_queue_worker(): _queue_worker = None +# --------------------------------------------------------------------------- +# Public product lookup endpoint (Ne Kadar Sattı? feature) +# --------------------------------------------------------------------------- +@app.get("/api/product/lookup", dependencies=[Depends(verify_api_key)]) +def product_lookup(url: str, request: Request): + ip = request.headers.get("X-Forwarded-For", request.client.host or "unknown").split(",")[0].strip() + if not _check_ip_rate_limit(ip): + raise HTTPException(status_code=429, detail="Çok fazla istek. Bir dakika sonra tekrar deneyin.") + if not url or "trendyol.com" not in url.lower(): + raise HTTPException(status_code=400, detail="Geçerli bir Trendyol URL'si girin.") + m = _TRENDYOL_URL_RE.search(url) + if not m: + raise HTTPException(status_code=400, detail="URL'den ürün ID'si alınamadı.") + product_id = int(m.group(1)) + cached = product_lookup_cache.get(str(product_id)) + if cached: + return {"source": "cache", "productId": product_id, **cached} + if _social_proof_breaker.is_open(): + raise HTTPException(status_code=503, detail="Servis geçici olarak kullanılamıyor.") + data = fetch_social_proof([product_id]) + if not data: + _social_proof_breaker.record_failure() + raise HTTPException(status_code=404, detail="Bu ürün için veri bulunamadı.") + items = data.get("result") or [] + pd_item = next((it for it in items if it.get("contentId") == product_id), None) + if not pd_item: + raise HTTPException(status_code=404, detail="Sosyal kanıt verisi bulunamadı.") + result = {k: pd_item.get(k, 0) for k in ["orderCount", "favoriteCount", "pageViewCount", "basketCount"]} + product_lookup_cache.set(str(product_id), result) + _social_proof_breaker.record_success() + return {"source": "live", "productId": product_id, **result} + + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8001)