mirror of
https://github.com/nethunterzist/trendyol-analiz
synced 2026-07-01 09:27:03 +00:00
feat: /api/product/lookup endpoint eklendi
Ne yaptık: - product_lookup_cache (BoundedCache 500 item, 1h TTL) eklendi - IP rate limiter: 10 istek/IP/dakika - _TRENDYOL_URL_RE regex ile URL'den ürün ID'si çekme - GET /api/product/lookup endpoint: orderCount/favoriteCount/pageViewCount/basketCount döner - Circuit breaker ve cache entegrasyonu Neden yaptık: - "Ne Kadar Sattı?" sayfası için gerekli Python servisi altyapısı. Kullanıcı Trendyol URL'si yapıştırıyor, sosyal kanıt verisi alıyor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user