diff --git a/app.py b/app.py index 12dfd5d..b9c5a78 100644 --- a/app.py +++ b/app.py @@ -121,6 +121,19 @@ except ImportError: NEWS_SERVICE_AVAILABLE = False logger.warning("News service not available") +# SEO audit components for triggering audits via API +import sys +_scripts_path = os.path.join(os.path.dirname(__file__), 'scripts') +if _scripts_path not in sys.path: + sys.path.insert(0, _scripts_path) + +try: + from seo_audit import SEOAuditor, SEO_AUDIT_VERSION + SEO_AUDIT_AVAILABLE = True +except ImportError as e: + SEO_AUDIT_AVAILABLE = False + logger.warning(f"SEO audit service not available: {e}") + # Initialize Flask app app = Flask(__name__) app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-secret-key-change-in-production') @@ -2480,6 +2493,152 @@ def api_seo_audit_by_slug(slug): db.close() +@app.route('/api/seo/audit', methods=['POST']) +@login_required +@limiter.limit("10 per hour") +def api_seo_audit_trigger(): + """ + API: Trigger SEO audit for a company (admin-only). + + This endpoint runs a full SEO audit including: + - Google PageSpeed Insights analysis + - On-page SEO analysis (meta tags, headings, images, links) + - Technical SEO checks (robots.txt, sitemap, canonical URLs) + + Request JSON body: + - company_id: Company ID (integer) OR + - slug: Company slug (string) + + Returns: + - Success: Full SEO audit results saved to database + - Error: Error message with status code + + Rate limited to 10 requests per hour per user to prevent API abuse. + """ + # Admin-only check + if not current_user.is_admin: + return jsonify({ + 'success': False, + 'error': 'Brak uprawnień. Tylko administrator może uruchamiać audyty SEO.' + }), 403 + + # Check if SEO audit service is available + if not SEO_AUDIT_AVAILABLE: + return jsonify({ + 'success': False, + 'error': 'Usługa audytu SEO jest niedostępna. Sprawdź konfigurację serwera.' + }), 503 + + # Parse request data + data = request.get_json() + if not data: + return jsonify({ + 'success': False, + 'error': 'Brak danych w żądaniu. Podaj company_id lub slug.' + }), 400 + + company_id = data.get('company_id') + slug = data.get('slug') + + if not company_id and not slug: + return jsonify({ + 'success': False, + 'error': 'Podaj company_id lub slug firmy do audytu.' + }), 400 + + db = SessionLocal() + try: + # Find company by ID or slug + if company_id: + company = db.query(Company).filter_by(id=company_id, status='active').first() + else: + company = db.query(Company).filter_by(slug=slug, status='active').first() + + if not company: + return jsonify({ + 'success': False, + 'error': 'Firma nie znaleziona lub nieaktywna.' + }), 404 + + # Check if company has a website + if not company.website: + return jsonify({ + 'success': False, + 'error': f'Firma "{company.name}" nie ma zdefiniowanej strony internetowej.', + 'company_id': company.id, + 'company_name': company.name + }), 400 + + logger.info(f"SEO audit triggered by admin {current_user.email} for company: {company.name} (ID: {company.id})") + + # Initialize SEO auditor and run audit + try: + auditor = SEOAuditor() + + # Prepare company dict for auditor + company_dict = { + 'id': company.id, + 'name': company.name, + 'slug': company.slug, + 'website': company.website, + 'address_city': company.address_city + } + + # Run the audit + audit_result = auditor.audit_company(company_dict) + + # Check for errors + if audit_result.get('errors') and not audit_result.get('onpage') and not audit_result.get('pagespeed'): + return jsonify({ + 'success': False, + 'error': f'Audyt nie powiódł się: {", ".join(audit_result["errors"])}', + 'company_id': company.id, + 'company_name': company.name, + 'website': company.website + }), 422 + + # Save result to database + saved = auditor.save_audit_result(audit_result) + + if not saved: + return jsonify({ + 'success': False, + 'error': 'Audyt został wykonany, ale nie udało się zapisać wyników do bazy danych.', + 'company_id': company.id, + 'company_name': company.name + }), 500 + + # Get the updated analysis record to return + db.expire_all() # Refresh the session to get updated data + analysis = db.query(CompanyWebsiteAnalysis).filter_by( + company_id=company.id + ).order_by(CompanyWebsiteAnalysis.analyzed_at.desc()).first() + + # Build response using the existing helper function + response = _build_seo_audit_response(company, analysis) + + return jsonify({ + 'success': True, + 'message': f'Audyt SEO dla firmy "{company.name}" został zakończony pomyślnie.', + 'audit_version': SEO_AUDIT_VERSION, + 'triggered_by': current_user.email, + 'triggered_at': datetime.now().isoformat(), + **response + }), 200 + + except Exception as e: + logger.error(f"SEO audit error for company {company.id}: {e}") + return jsonify({ + 'success': False, + 'error': f'Błąd podczas wykonywania audytu: {str(e)}', + 'company_id': company.id, + 'company_name': company.name + }), 500 + + finally: + db.close() + + @app.route('/api/check-email', methods=['POST']) def api_check_email(): """API: Check if email is available"""