diff --git a/backend/main.py b/backend/main.py index d5d0d75..b96c2fe 100644 --- a/backend/main.py +++ b/backend/main.py @@ -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")