fix(api): category-tree endpoint'lerini public yap

Ne yaptık:
- PUBLIC_PATHS setine /api/category-tree, /api/category-tree/roots,
  /api/category-tree/search eklendi

Neden yaptık:
- API_KEY env var set olduğunda tüm endpoint'ler auth gerektiriyor
- Frontend X-API-Key header göndermiyordu → 401 → 'Kategoriler yükleniyor...'
- Category tree read-only, herkese açık veri — auth gerektirmesi hata
This commit is contained in:
2026-05-04 11:35:05 +03:00
parent fd992fa5d2
commit a2317bdec0

View File

@@ -52,7 +52,7 @@ if not API_KEY:
API_KEY_HEADER = APIKeyHeader(name="X-API-Key", auto_error=False)
# Paths that do not require API key authentication
PUBLIC_PATHS = {"/health"}
PUBLIC_PATHS = {"/health", "/api/category-tree", "/api/category-tree/roots", "/api/category-tree/search"}
async def verify_api_key(request: Request, api_key: Optional[str] = Security(API_KEY_HEADER)):
"""
@@ -1667,13 +1667,19 @@ async def create_report(
log_sse.info(f"SSE stream started: task={task_id}, category={main_cat_name}")
try:
# Send initial info
yield f"data: {json_module.dumps({'type': 'info', 'message': f'📂 {main_cat_name} kategorisi seçildi', 'progress': 0})}\n\n"
yield f"data: {json_module.dumps({'type': 'info', 'message': f'📂 {main_cat_name} kategorisi seçildi', 'progress': 0})}
"
await asyncio.sleep(0.1)
yield f"data: {json_module.dumps({'type': 'info', 'message': f'📊 {len(categories_to_scrape)} alt kategori bulundu', 'progress': 0})}\n\n"
yield f"data: {json_module.dumps({'type': 'info', 'message': f'📊 {len(categories_to_scrape)} alt kategori bulundu', 'progress': 0})}
"
await asyncio.sleep(0.1)
yield f"data: {json_module.dumps({'type': 'info', 'message': '🚀 Veri çekimi başlatılıyor...', 'progress': 5})}\n\n"
yield f"data: {json_module.dumps({'type': 'info', 'message': '🚀 Veri çekimi başlatılıyor...', 'progress': 5})}
"
await asyncio.sleep(0.5)
# Start synchronous scraping with progress updates
@@ -1694,13 +1700,17 @@ async def create_report(
for idx, (path_model, cat_name, cat_id) in enumerate(categories_to_scrape, 1):
progress = int((idx / len(categories_to_scrape)) * 80) + 10
yield f"data: {json_module.dumps({'type': 'processing', 'message': f'🔍 [{idx}/{len(categories_to_scrape)}] {cat_name} çekiliyor...', 'progress': progress, 'current': idx, 'total': len(categories_to_scrape)})}\n\n"
yield f"data: {json_module.dumps({'type': 'processing', 'message': f'🔍 [{idx}/{len(categories_to_scrape)}] {cat_name} çekiliyor...', 'progress': progress, 'current': idx, 'total': len(categories_to_scrape)})}
"
await asyncio.sleep(0.1)
try:
if path_model:
# New Search API — works for both -c and -s categories
yield f"data: {json_module.dumps({'type': 'api', 'message': f'🌐 API: Trendyol Search - {path_model}', 'progress': progress})}\n\n"
yield f"data: {json_module.dumps({'type': 'api', 'message': f'🌐 API: Trendyol Search - {path_model}', 'progress': progress})}
"
await asyncio.sleep(0.1)
scraper = TrendyolSearchScraper(path_model)
@@ -1732,7 +1742,9 @@ async def create_report(
elif cat_id:
# Legacy fallback — old top-rankings API
yield f"data: {json_module.dumps({'type': 'api', 'message': f'🌐 API: Trendyol Best Seller - Kategori ID: {cat_id}', 'progress': progress})}\n\n"
yield f"data: {json_module.dumps({'type': 'api', 'message': f'🌐 API: Trendyol Best Seller - Kategori ID: {cat_id}', 'progress': progress})}
"
await asyncio.sleep(0.1)
scraper = TrendyolScraper(cat_id)
@@ -1768,7 +1780,9 @@ async def create_report(
"file_path": filename
})
yield f"data: {json_module.dumps({'type': 'success', 'message': f'{cat_name} tamamlandı - {len(products)} ürün bulundu', 'progress': progress})}\n\n"
yield f"data: {json_module.dumps({'type': 'success', 'message': f'{cat_name} tamamlandı - {len(products)} ürün bulundu', 'progress': progress})}
"
await asyncio.sleep(0.1)
else:
results["failed"] += 1
@@ -1780,7 +1794,9 @@ async def create_report(
"total_products": 0,
"file_path": None
})
yield f"data: {json_module.dumps({'type': 'warning', 'message': f'⚠️ {cat_name} - Ürün bulunamadı', 'progress': progress})}\n\n"
yield f"data: {json_module.dumps({'type': 'warning', 'message': f'⚠️ {cat_name} - Ürün bulunamadı', 'progress': progress})}
"
await asyncio.sleep(0.1)
except Exception as e:
@@ -1793,14 +1809,18 @@ async def create_report(
"total_products": 0,
"file_path": None
})
yield f"data: {json_module.dumps({'type': 'error', 'message': f'{cat_name} - Hata: {str(e)}', 'progress': progress})}\n\n"
yield f"data: {json_module.dumps({'type': 'error', 'message': f'{cat_name} - Hata: {str(e)}', 'progress': progress})}
"
await asyncio.sleep(0.1)
# Rate limiting (non-blocking)
await asyncio.sleep(1.5)
# Generate report file
yield f"data: {json_module.dumps({'type': 'info', 'message': '📝 Rapor dosyası oluşturuluyor...', 'progress': 88})}\n\n"
yield f"data: {json_module.dumps({'type': 'info', 'message': '📝 Rapor dosyası oluşturuluyor...', 'progress': 88})}
"
await asyncio.sleep(0.5)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
@@ -1822,7 +1842,9 @@ async def create_report(
await asyncio.to_thread(_write_json, json_filename, combined_data)
# Save to database
yield f"data: {json_module.dumps({'type': 'info', 'message': '💾 Veritabanına kaydediliyor...', 'progress': 93})}\n\n"
yield f"data: {json_module.dumps({'type': 'info', 'message': '💾 Veritabanına kaydediliyor...', 'progress': 93})}
"
await asyncio.sleep(0.5)
new_report = Report(
@@ -1856,7 +1878,9 @@ async def create_report(
log_sse.info(f"Background enrichment started for report {report_id_for_enrich}")
# Wait for enrichment to complete, sending progress updates via SSE
yield f"data: {json_module.dumps({'type': 'info', 'message': '📊 Sosyal kanıt verileri toplanıyor...', 'progress': 90})}\n\n"
yield f"data: {json_module.dumps({'type': 'info', 'message': '📊 Sosyal kanıt verileri toplanıyor...', 'progress': 90})}
"
await asyncio.sleep(0.5)
progress_key = f"social_{report_id_for_enrich}"
@@ -1874,26 +1898,38 @@ async def create_report(
if sp_total > 0:
overall_pct = 90 + int(sp_pct * 0.09)
yield f"data: {json_module.dumps({'type': 'info', 'message': f'📊 Sosyal kanıt: {sp_processed}/{sp_total} ürün (%{sp_pct})', 'progress': overall_pct})}\n\n"
yield f"data: {json_module.dumps({'type': 'info', 'message': f'📊 Sosyal kanıt: {sp_processed}/{sp_total} ürün (%{sp_pct})', 'progress': overall_pct})}
"
await asyncio.sleep(3)
waited += 3
enrich_status = enrichment_progress.get(report_id_for_enrich) or {}
if enrich_status.get("status") == "completed":
yield f"data: {json_module.dumps({'type': 'info', 'message': '✅ Sosyal kanıt tamamlandı!', 'progress': 99})}\n\n"
yield f"data: {json_module.dumps({'type': 'info', 'message': '✅ Sosyal kanıt tamamlandı!', 'progress': 99})}
"
elif enrich_status.get("status") == "error":
err_msg = str(enrich_status.get("error", ""))[:100]
yield f"data: {json_module.dumps({'type': 'warning', 'message': f'⚠️ Sosyal kanıt hatası: {err_msg}', 'progress': 99})}\n\n"
yield f"data: {json_module.dumps({'type': 'warning', 'message': f'⚠️ Sosyal kanıt hatası: {err_msg}', 'progress': 99})}
"
else:
yield f"data: {json_module.dumps({'type': 'warning', 'message': '⚠️ Sosyal kanıt zaman aşımı, arka planda devam ediyor...', 'progress': 99})}\n\n"
yield f"data: {json_module.dumps({'type': 'warning', 'message': '⚠️ Sosyal kanıt zaman aşımı, arka planda devam ediyor...', 'progress': 99})}
"
else:
yield f"data: {json_module.dumps({'type': 'info', 'message': '📊 Rapor JSON olarak kaydedildi (DB atlandı)', 'progress': 99})}\n\n"
yield f"data: {json_module.dumps({'type': 'info', 'message': '📊 Rapor JSON olarak kaydedildi (DB atlandı)', 'progress': 99})}
"
await asyncio.sleep(0.1)
# Final success message
yield f"data: {json_module.dumps({'type': 'complete', 'message': '✅ Rapor başarıyla oluşturuldu!', 'progress': 100, 'report_id': report_id_for_enrich, 'total_products': results['total_products'], 'successful': results['successful'], 'enrichment_status': enrich_status.get('status', 'unknown')})}\n\n"
yield f"data: {json_module.dumps({'type': 'complete', 'message': '✅ Rapor başarıyla oluşturuldu!', 'progress': 100, 'report_id': report_id_for_enrich, 'total_products': results['total_products'], 'successful': results['successful'], 'enrichment_status': enrich_status.get('status', 'unknown')})}
"
await asyncio.sleep(0.1)
except asyncio.CancelledError:
@@ -1901,7 +1937,9 @@ async def create_report(
return
except Exception as e:
log_sse.error(f"SSE stream error: task={task_id}, error={e}", exc_info=True)
yield f"data: {json_module.dumps({'type': 'error', 'message': f'❌ Kritik hata: {str(e)}', 'progress': -1})}\n\n"
yield f"data: {json_module.dumps({'type': 'error', 'message': f'❌ Kritik hata: {str(e)}', 'progress': -1})}
"
return StreamingResponse(progress_stream(), media_type="text/event-stream")