refactor: Move GBP and Social Audit API routes to blueprints
- Create blueprints/api/routes_gbp_audit.py (4 routes) - Create blueprints/api/routes_social_audit.py (1 route) - app.py: 6354 → 5860 lines (-494 total this batch) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
60c19ec188
commit
8e3346178d
502
app.py
502
app.py
@ -1243,355 +1243,9 @@ def api_connections():
|
||||
# ============================================================
|
||||
|
||||
# ============================================================
|
||||
# GBP (GOOGLE BUSINESS PROFILE) AUDIT API
|
||||
# GBP AUDIT API - MOVED TO: blueprints/api/routes_gbp_audit.py
|
||||
# ============================================================
|
||||
|
||||
@app.route('/api/gbp/audit/health')
|
||||
def api_gbp_audit_health():
|
||||
"""
|
||||
API: Health check for GBP audit service.
|
||||
|
||||
Returns service status and version information.
|
||||
Used by monitoring systems to verify service availability.
|
||||
"""
|
||||
if GBP_AUDIT_AVAILABLE:
|
||||
return jsonify({
|
||||
'status': 'ok',
|
||||
'service': 'gbp_audit',
|
||||
'version': GBP_AUDIT_VERSION,
|
||||
'available': True
|
||||
}), 200
|
||||
else:
|
||||
return jsonify({
|
||||
'status': 'unavailable',
|
||||
'service': 'gbp_audit',
|
||||
'available': False,
|
||||
'error': 'GBP audit service not loaded'
|
||||
}), 503
|
||||
|
||||
|
||||
@app.route('/api/gbp/audit', methods=['GET'])
|
||||
def api_gbp_audit_get():
|
||||
"""
|
||||
API: Get GBP audit results for a company.
|
||||
|
||||
Query parameters:
|
||||
- company_id: Company ID (integer) OR
|
||||
- slug: Company slug (string)
|
||||
|
||||
Returns:
|
||||
- Latest audit results with completeness score and recommendations
|
||||
- 404 if company not found
|
||||
- 404 if no audit exists for the company
|
||||
|
||||
Example: GET /api/gbp/audit?company_id=26
|
||||
Example: GET /api/gbp/audit?slug=pixlab-sp-z-o-o
|
||||
"""
|
||||
if not GBP_AUDIT_AVAILABLE:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu GBP jest niedostępna.'
|
||||
}), 503
|
||||
|
||||
company_id = request.args.get('company_id', type=int)
|
||||
slug = request.args.get('slug')
|
||||
|
||||
if not company_id and not slug:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Podaj company_id lub slug firmy.'
|
||||
}), 400
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# Find company
|
||||
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
|
||||
|
||||
# Get latest audit
|
||||
audit = gbp_get_company_audit(db, company.id)
|
||||
|
||||
if not audit:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Brak wyników audytu GBP dla firmy "{company.name}". Uruchom audyt używając POST /api/gbp/audit.',
|
||||
'company_id': company.id,
|
||||
'company_name': company.name
|
||||
}), 404
|
||||
|
||||
# Build response
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'company_id': company.id,
|
||||
'company_name': company.name,
|
||||
'company_slug': company.slug,
|
||||
'audit': {
|
||||
'id': audit.id,
|
||||
'audit_date': audit.audit_date.isoformat() if audit.audit_date else None,
|
||||
'completeness_score': audit.completeness_score,
|
||||
'score_category': audit.score_category,
|
||||
'fields_status': audit.fields_status,
|
||||
'recommendations': audit.recommendations,
|
||||
'has_name': audit.has_name,
|
||||
'has_address': audit.has_address,
|
||||
'has_phone': audit.has_phone,
|
||||
'has_website': audit.has_website,
|
||||
'has_hours': audit.has_hours,
|
||||
'has_categories': audit.has_categories,
|
||||
'has_photos': audit.has_photos,
|
||||
'has_description': audit.has_description,
|
||||
'has_services': audit.has_services,
|
||||
'has_reviews': audit.has_reviews,
|
||||
'photo_count': audit.photo_count,
|
||||
'review_count': audit.review_count,
|
||||
'average_rating': float(audit.average_rating) if audit.average_rating else None,
|
||||
'google_place_id': audit.google_place_id,
|
||||
'audit_source': audit.audit_source,
|
||||
'audit_version': audit.audit_version
|
||||
}
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching GBP audit: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Błąd podczas pobierania audytu: {str(e)}'
|
||||
}), 500
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@app.route('/api/gbp/audit/<slug>')
|
||||
def api_gbp_audit_by_slug(slug):
|
||||
"""
|
||||
API: Get GBP audit results for a company by slug.
|
||||
Convenience endpoint that uses slug from URL path.
|
||||
|
||||
Example: GET /api/gbp/audit/pixlab-sp-z-o-o
|
||||
"""
|
||||
if not GBP_AUDIT_AVAILABLE:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu GBP jest niedostępna.'
|
||||
}), 503
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter_by(slug=slug, status='active').first()
|
||||
|
||||
if not company:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Firma o slug "{slug}" nie znaleziona.'
|
||||
}), 404
|
||||
|
||||
audit = gbp_get_company_audit(db, company.id)
|
||||
|
||||
if not audit:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Brak wyników audytu GBP dla firmy "{company.name}".',
|
||||
'company_id': company.id,
|
||||
'company_name': company.name
|
||||
}), 404
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'company_id': company.id,
|
||||
'company_name': company.name,
|
||||
'company_slug': company.slug,
|
||||
'audit': {
|
||||
'id': audit.id,
|
||||
'audit_date': audit.audit_date.isoformat() if audit.audit_date else None,
|
||||
'completeness_score': audit.completeness_score,
|
||||
'score_category': audit.score_category,
|
||||
'fields_status': audit.fields_status,
|
||||
'recommendations': audit.recommendations,
|
||||
'photo_count': audit.photo_count,
|
||||
'review_count': audit.review_count,
|
||||
'average_rating': float(audit.average_rating) if audit.average_rating else None
|
||||
}
|
||||
}), 200
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@app.route('/api/gbp/audit', methods=['POST'])
|
||||
@login_required
|
||||
@limiter.limit("20 per hour")
|
||||
def api_gbp_audit_trigger():
|
||||
"""
|
||||
API: Run GBP audit for a company.
|
||||
|
||||
This endpoint runs a completeness audit for Google Business Profile data,
|
||||
checking fields like name, address, phone, website, hours, categories,
|
||||
photos, description, services, and reviews.
|
||||
|
||||
Request JSON body:
|
||||
- company_id: Company ID (integer) OR
|
||||
- slug: Company slug (string)
|
||||
- save: Whether to save results to database (default: true)
|
||||
|
||||
Returns:
|
||||
- Success: Audit results with completeness score and recommendations
|
||||
- Error: Error message with status code
|
||||
|
||||
Access:
|
||||
- Members can audit their own company
|
||||
- Admins can audit any company
|
||||
|
||||
Rate limited to 20 requests per hour per user.
|
||||
"""
|
||||
if not GBP_AUDIT_AVAILABLE:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu GBP 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')
|
||||
save_result = data.get('save', True)
|
||||
|
||||
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 access: admin can audit any company, member only their own
|
||||
if not current_user.is_admin:
|
||||
# Check if user is associated with this company
|
||||
if current_user.company_id != company.id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Brak uprawnień. Możesz audytować tylko własną firmę.'
|
||||
}), 403
|
||||
|
||||
logger.info(f"GBP audit triggered by {current_user.email} for company: {company.name} (ID: {company.id})")
|
||||
|
||||
# Option to fetch fresh Google data before audit
|
||||
fetch_google = data.get('fetch_google', True)
|
||||
force_refresh = data.get('force_refresh', False)
|
||||
|
||||
try:
|
||||
# Step 1: Fetch fresh Google Business data (if enabled)
|
||||
fetch_result = None
|
||||
if fetch_google:
|
||||
logger.info(f"Fetching Google Business data for company {company.id}...")
|
||||
fetch_result = gbp_fetch_google_data(db, company.id, force_refresh=force_refresh)
|
||||
if not fetch_result.get('success') and not fetch_result.get('data', {}).get('cached'):
|
||||
# Log warning but continue with audit
|
||||
logger.warning(f"Google fetch warning for company {company.id}: {fetch_result.get('error')}")
|
||||
|
||||
# Step 2: Run the audit
|
||||
result = gbp_audit_company(db, company.id, save=save_result)
|
||||
|
||||
# Build field status for response
|
||||
fields_response = {}
|
||||
for field_name, field_status in result.fields.items():
|
||||
fields_response[field_name] = {
|
||||
'status': field_status.status,
|
||||
'value': str(field_status.value) if field_status.value is not None else None,
|
||||
'score': field_status.score,
|
||||
'max_score': field_status.max_score,
|
||||
'recommendation': field_status.recommendation
|
||||
}
|
||||
|
||||
# Determine score category
|
||||
score = result.completeness_score
|
||||
if score >= 90:
|
||||
score_category = 'excellent'
|
||||
elif score >= 70:
|
||||
score_category = 'good'
|
||||
elif score >= 50:
|
||||
score_category = 'needs_work'
|
||||
else:
|
||||
score_category = 'poor'
|
||||
|
||||
response_data = {
|
||||
'success': True,
|
||||
'message': f'Audyt GBP dla firmy "{company.name}" został zakończony pomyślnie.',
|
||||
'company_id': company.id,
|
||||
'company_name': company.name,
|
||||
'company_slug': company.slug,
|
||||
'audit_version': GBP_AUDIT_VERSION,
|
||||
'triggered_by': current_user.email,
|
||||
'triggered_at': datetime.now().isoformat(),
|
||||
'saved': save_result,
|
||||
'audit': {
|
||||
'completeness_score': result.completeness_score,
|
||||
'score_category': score_category,
|
||||
'fields_status': fields_response,
|
||||
'recommendations': result.recommendations,
|
||||
'photo_count': result.photo_count,
|
||||
'logo_present': result.logo_present,
|
||||
'cover_photo_present': result.cover_photo_present,
|
||||
'review_count': result.review_count,
|
||||
'average_rating': float(result.average_rating) if result.average_rating else None,
|
||||
'google_place_id': result.google_place_id
|
||||
}
|
||||
}
|
||||
|
||||
# Include Google fetch results if performed
|
||||
if fetch_result:
|
||||
response_data['google_fetch'] = {
|
||||
'success': fetch_result.get('success', False),
|
||||
'steps': fetch_result.get('steps', []),
|
||||
'data': fetch_result.get('data', {}),
|
||||
'error': fetch_result.get('error')
|
||||
}
|
||||
|
||||
return jsonify(response_data), 200
|
||||
|
||||
except ValueError as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'company_id': company.id if company else None
|
||||
}), 400
|
||||
except Exception as e:
|
||||
logger.error(f"GBP 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()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# SEO AUDIT USER-FACING DASHBOARD
|
||||
@ -1755,157 +1409,9 @@ def social_audit_dashboard(slug):
|
||||
db.close()
|
||||
|
||||
|
||||
@app.route('/api/social/audit', methods=['POST'])
|
||||
@login_required
|
||||
@limiter.limit("10 per hour")
|
||||
def api_social_audit_trigger():
|
||||
"""
|
||||
API: Trigger Social Media audit for a company.
|
||||
|
||||
This endpoint performs a comprehensive social media audit:
|
||||
- Scans company website for social media links
|
||||
- Searches for profiles via Brave Search API (if configured)
|
||||
- Fetches Google Business Profile data
|
||||
- Updates database with discovered profiles
|
||||
|
||||
Request JSON body:
|
||||
- company_id: Company ID (integer) OR
|
||||
- slug: Company slug (string)
|
||||
|
||||
Returns:
|
||||
- Success: Updated social media audit results
|
||||
- Error: Error message with status code
|
||||
|
||||
Rate limited to 10 requests per hour per user.
|
||||
"""
|
||||
# Import the SocialMediaAuditor from scripts
|
||||
try:
|
||||
import sys
|
||||
from pathlib import Path
|
||||
scripts_dir = Path(__file__).parent / 'scripts'
|
||||
if str(scripts_dir) not in sys.path:
|
||||
sys.path.insert(0, str(scripts_dir))
|
||||
from social_media_audit import SocialMediaAuditor
|
||||
except ImportError as e:
|
||||
logger.error(f"Failed to import SocialMediaAuditor: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu Social Media 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
|
||||
|
||||
# Access control - admin can audit all, users only their company
|
||||
if not current_user.is_admin:
|
||||
if current_user.company_id != company.id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Brak uprawnień do audytu social media tej firmy.'
|
||||
}), 403
|
||||
|
||||
logger.info(f"Social Media audit triggered by {current_user.email} for company: {company.name} (ID: {company.id})")
|
||||
|
||||
# Prepare company dict for auditor
|
||||
company_dict = {
|
||||
'id': company.id,
|
||||
'name': company.name,
|
||||
'slug': company.slug,
|
||||
'website': company.website,
|
||||
'address_city': company.address_city or 'Wejherowo'
|
||||
}
|
||||
|
||||
# Initialize auditor and run audit
|
||||
try:
|
||||
auditor = SocialMediaAuditor()
|
||||
audit_result = auditor.audit_company(company_dict)
|
||||
|
||||
# Check for errors
|
||||
if audit_result.get('errors') and not audit_result.get('social_media') and not audit_result.get('website'):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Audyt nie powiódł się: {", ".join(audit_result["errors"][:3])}',
|
||||
'company_id': company.id,
|
||||
'company_name': company.name
|
||||
}), 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 count of social media profiles found
|
||||
social_media_found = audit_result.get('social_media', {})
|
||||
platforms_count = len(social_media_found)
|
||||
|
||||
# Calculate score
|
||||
all_platforms = ['facebook', 'instagram', 'linkedin', 'youtube', 'twitter', 'tiktok']
|
||||
score = int((platforms_count / len(all_platforms)) * 100)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Audyt Social Media zakończony. Znaleziono {platforms_count} profili.',
|
||||
'company_id': company.id,
|
||||
'company_name': company.name,
|
||||
'profiles_found': platforms_count,
|
||||
'platforms': list(social_media_found.keys()),
|
||||
'score': score,
|
||||
'google_reviews': audit_result.get('google_reviews', {}),
|
||||
'errors': audit_result.get('errors') if audit_result.get('errors') else None
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Social Media audit error for company {company.id}: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Błąd podczas audytu: {str(e)}'
|
||||
}), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Social Media audit error for company {slug or company_id}: {e}")
|
||||
db.rollback()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Błąd podczas audytu: {str(e)}'
|
||||
}), 500
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
# ============================================================
|
||||
# SOCIAL AUDIT API - MOVED TO: blueprints/api/routes_social_audit.py
|
||||
# ============================================================
|
||||
|
||||
# ============================================================
|
||||
# GBP AUDIT USER-FACING DASHBOARD
|
||||
|
||||
@ -13,3 +13,5 @@ from . import routes_analytics # noqa: E402, F401
|
||||
from . import routes_recommendations # noqa: E402, F401
|
||||
from . import routes_contacts # noqa: E402, F401
|
||||
from . import routes_seo_audit # noqa: E402, F401
|
||||
from . import routes_gbp_audit # noqa: E402, F401
|
||||
from . import routes_social_audit # noqa: E402, F401
|
||||
|
||||
387
blueprints/api/routes_gbp_audit.py
Normal file
387
blueprints/api/routes_gbp_audit.py
Normal file
@ -0,0 +1,387 @@
|
||||
"""
|
||||
GBP Audit API Routes - API blueprint
|
||||
|
||||
Migrated from app.py as part of the blueprint refactoring.
|
||||
Contains API routes for Google Business Profile audit functionality.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
from flask import jsonify, request, current_app
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from database import SessionLocal, Company
|
||||
from . import bp
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Check if GBP audit service is available
|
||||
try:
|
||||
from gbp_audit_service import (
|
||||
GBPAuditResult,
|
||||
audit_company as gbp_audit_company,
|
||||
get_company_audit as gbp_get_company_audit,
|
||||
fetch_google_business_data as gbp_fetch_google_data
|
||||
)
|
||||
GBP_AUDIT_AVAILABLE = True
|
||||
GBP_AUDIT_VERSION = '1.0'
|
||||
except ImportError:
|
||||
GBP_AUDIT_AVAILABLE = False
|
||||
GBP_AUDIT_VERSION = None
|
||||
|
||||
|
||||
def get_limiter():
|
||||
"""Get rate limiter from current app."""
|
||||
return current_app.extensions.get('limiter')
|
||||
|
||||
|
||||
# ============================================================
|
||||
# GBP AUDIT API ROUTES
|
||||
# ============================================================
|
||||
|
||||
@bp.route('/gbp/audit/health')
|
||||
def api_gbp_audit_health():
|
||||
"""
|
||||
API: Health check for GBP audit service.
|
||||
|
||||
Returns service status and version information.
|
||||
Used by monitoring systems to verify service availability.
|
||||
"""
|
||||
if GBP_AUDIT_AVAILABLE:
|
||||
return jsonify({
|
||||
'status': 'ok',
|
||||
'service': 'gbp_audit',
|
||||
'version': GBP_AUDIT_VERSION,
|
||||
'available': True
|
||||
}), 200
|
||||
else:
|
||||
return jsonify({
|
||||
'status': 'unavailable',
|
||||
'service': 'gbp_audit',
|
||||
'available': False,
|
||||
'error': 'GBP audit service not loaded'
|
||||
}), 503
|
||||
|
||||
|
||||
@bp.route('/gbp/audit', methods=['GET'])
|
||||
def api_gbp_audit_get():
|
||||
"""
|
||||
API: Get GBP audit results for a company.
|
||||
|
||||
Query parameters:
|
||||
- company_id: Company ID (integer) OR
|
||||
- slug: Company slug (string)
|
||||
|
||||
Returns:
|
||||
- Latest audit results with completeness score and recommendations
|
||||
- 404 if company not found
|
||||
- 404 if no audit exists for the company
|
||||
|
||||
Example: GET /api/gbp/audit?company_id=26
|
||||
Example: GET /api/gbp/audit?slug=pixlab-sp-z-o-o
|
||||
"""
|
||||
if not GBP_AUDIT_AVAILABLE:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu GBP jest niedostępna.'
|
||||
}), 503
|
||||
|
||||
company_id = request.args.get('company_id', type=int)
|
||||
slug = request.args.get('slug')
|
||||
|
||||
if not company_id and not slug:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Podaj company_id lub slug firmy.'
|
||||
}), 400
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# Find company
|
||||
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
|
||||
|
||||
# Get latest audit
|
||||
audit = gbp_get_company_audit(db, company.id)
|
||||
|
||||
if not audit:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Brak wyników audytu GBP dla firmy "{company.name}". Uruchom audyt używając POST /api/gbp/audit.',
|
||||
'company_id': company.id,
|
||||
'company_name': company.name
|
||||
}), 404
|
||||
|
||||
# Build response
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'company_id': company.id,
|
||||
'company_name': company.name,
|
||||
'company_slug': company.slug,
|
||||
'audit': {
|
||||
'id': audit.id,
|
||||
'audit_date': audit.audit_date.isoformat() if audit.audit_date else None,
|
||||
'completeness_score': audit.completeness_score,
|
||||
'score_category': audit.score_category,
|
||||
'fields_status': audit.fields_status,
|
||||
'recommendations': audit.recommendations,
|
||||
'has_name': audit.has_name,
|
||||
'has_address': audit.has_address,
|
||||
'has_phone': audit.has_phone,
|
||||
'has_website': audit.has_website,
|
||||
'has_hours': audit.has_hours,
|
||||
'has_categories': audit.has_categories,
|
||||
'has_photos': audit.has_photos,
|
||||
'has_description': audit.has_description,
|
||||
'has_services': audit.has_services,
|
||||
'has_reviews': audit.has_reviews,
|
||||
'photo_count': audit.photo_count,
|
||||
'review_count': audit.review_count,
|
||||
'average_rating': float(audit.average_rating) if audit.average_rating else None,
|
||||
'google_place_id': audit.google_place_id,
|
||||
'audit_source': audit.audit_source,
|
||||
'audit_version': audit.audit_version
|
||||
}
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching GBP audit: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Błąd podczas pobierania audytu: {str(e)}'
|
||||
}), 500
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/gbp/audit/<slug>')
|
||||
def api_gbp_audit_by_slug(slug):
|
||||
"""
|
||||
API: Get GBP audit results for a company by slug.
|
||||
Convenience endpoint that uses slug from URL path.
|
||||
|
||||
Example: GET /api/gbp/audit/pixlab-sp-z-o-o
|
||||
"""
|
||||
if not GBP_AUDIT_AVAILABLE:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu GBP jest niedostępna.'
|
||||
}), 503
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
company = db.query(Company).filter_by(slug=slug, status='active').first()
|
||||
|
||||
if not company:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Firma o slug "{slug}" nie znaleziona.'
|
||||
}), 404
|
||||
|
||||
audit = gbp_get_company_audit(db, company.id)
|
||||
|
||||
if not audit:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Brak wyników audytu GBP dla firmy "{company.name}".',
|
||||
'company_id': company.id,
|
||||
'company_name': company.name
|
||||
}), 404
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'company_id': company.id,
|
||||
'company_name': company.name,
|
||||
'company_slug': company.slug,
|
||||
'audit': {
|
||||
'id': audit.id,
|
||||
'audit_date': audit.audit_date.isoformat() if audit.audit_date else None,
|
||||
'completeness_score': audit.completeness_score,
|
||||
'score_category': audit.score_category,
|
||||
'fields_status': audit.fields_status,
|
||||
'recommendations': audit.recommendations,
|
||||
'photo_count': audit.photo_count,
|
||||
'review_count': audit.review_count,
|
||||
'average_rating': float(audit.average_rating) if audit.average_rating else None
|
||||
}
|
||||
}), 200
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
@bp.route('/gbp/audit', methods=['POST'])
|
||||
@login_required
|
||||
def api_gbp_audit_trigger():
|
||||
"""
|
||||
API: Run GBP audit for a company.
|
||||
|
||||
This endpoint runs a completeness audit for Google Business Profile data,
|
||||
checking fields like name, address, phone, website, hours, categories,
|
||||
photos, description, services, and reviews.
|
||||
|
||||
Request JSON body:
|
||||
- company_id: Company ID (integer) OR
|
||||
- slug: Company slug (string)
|
||||
- save: Whether to save results to database (default: true)
|
||||
|
||||
Returns:
|
||||
- Success: Audit results with completeness score and recommendations
|
||||
- Error: Error message with status code
|
||||
|
||||
Access:
|
||||
- Members can audit their own company
|
||||
- Admins can audit any company
|
||||
|
||||
Rate limited to 20 requests per hour per user.
|
||||
"""
|
||||
if not GBP_AUDIT_AVAILABLE:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu GBP 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')
|
||||
save_result = data.get('save', True)
|
||||
|
||||
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 access: admin can audit any company, member only their own
|
||||
if not current_user.is_admin:
|
||||
# Check if user is associated with this company
|
||||
if current_user.company_id != company.id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Brak uprawnień. Możesz audytować tylko własną firmę.'
|
||||
}), 403
|
||||
|
||||
logger.info(f"GBP audit triggered by {current_user.email} for company: {company.name} (ID: {company.id})")
|
||||
|
||||
# Option to fetch fresh Google data before audit
|
||||
fetch_google = data.get('fetch_google', True)
|
||||
force_refresh = data.get('force_refresh', False)
|
||||
|
||||
try:
|
||||
# Step 1: Fetch fresh Google Business data (if enabled)
|
||||
fetch_result = None
|
||||
if fetch_google:
|
||||
logger.info(f"Fetching Google Business data for company {company.id}...")
|
||||
fetch_result = gbp_fetch_google_data(db, company.id, force_refresh=force_refresh)
|
||||
if not fetch_result.get('success') and not fetch_result.get('data', {}).get('cached'):
|
||||
# Log warning but continue with audit
|
||||
logger.warning(f"Google fetch warning for company {company.id}: {fetch_result.get('error')}")
|
||||
|
||||
# Step 2: Run the audit
|
||||
result = gbp_audit_company(db, company.id, save=save_result)
|
||||
|
||||
# Build field status for response
|
||||
fields_response = {}
|
||||
for field_name, field_status in result.fields.items():
|
||||
fields_response[field_name] = {
|
||||
'status': field_status.status,
|
||||
'value': str(field_status.value) if field_status.value is not None else None,
|
||||
'score': field_status.score,
|
||||
'max_score': field_status.max_score,
|
||||
'recommendation': field_status.recommendation
|
||||
}
|
||||
|
||||
# Determine score category
|
||||
score = result.completeness_score
|
||||
if score >= 90:
|
||||
score_category = 'excellent'
|
||||
elif score >= 70:
|
||||
score_category = 'good'
|
||||
elif score >= 50:
|
||||
score_category = 'needs_work'
|
||||
else:
|
||||
score_category = 'poor'
|
||||
|
||||
response_data = {
|
||||
'success': True,
|
||||
'message': f'Audyt GBP dla firmy "{company.name}" został zakończony pomyślnie.',
|
||||
'company_id': company.id,
|
||||
'company_name': company.name,
|
||||
'company_slug': company.slug,
|
||||
'audit_version': GBP_AUDIT_VERSION,
|
||||
'triggered_by': current_user.email,
|
||||
'triggered_at': datetime.now().isoformat(),
|
||||
'saved': save_result,
|
||||
'audit': {
|
||||
'completeness_score': result.completeness_score,
|
||||
'score_category': score_category,
|
||||
'fields_status': fields_response,
|
||||
'recommendations': result.recommendations,
|
||||
'photo_count': result.photo_count,
|
||||
'logo_present': result.logo_present,
|
||||
'cover_photo_present': result.cover_photo_present,
|
||||
'review_count': result.review_count,
|
||||
'average_rating': float(result.average_rating) if result.average_rating else None,
|
||||
'google_place_id': result.google_place_id
|
||||
}
|
||||
}
|
||||
|
||||
# Include Google fetch results if performed
|
||||
if fetch_result:
|
||||
response_data['google_fetch'] = {
|
||||
'success': fetch_result.get('success', False),
|
||||
'steps': fetch_result.get('steps', []),
|
||||
'data': fetch_result.get('data', {}),
|
||||
'error': fetch_result.get('error')
|
||||
}
|
||||
|
||||
return jsonify(response_data), 200
|
||||
|
||||
except ValueError as e:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': str(e),
|
||||
'company_id': company.id if company else None
|
||||
}), 400
|
||||
except Exception as e:
|
||||
logger.error(f"GBP 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()
|
||||
171
blueprints/api/routes_social_audit.py
Normal file
171
blueprints/api/routes_social_audit.py
Normal file
@ -0,0 +1,171 @@
|
||||
"""
|
||||
Social Audit API Routes - API blueprint
|
||||
|
||||
Migrated from app.py as part of the blueprint refactoring.
|
||||
Contains API routes for Social Media audit functionality.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from flask import jsonify, request
|
||||
from flask_login import current_user, login_required
|
||||
|
||||
from database import SessionLocal, Company
|
||||
from . import bp
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# SOCIAL MEDIA AUDIT API ROUTES
|
||||
# ============================================================
|
||||
|
||||
@bp.route('/social/audit', methods=['POST'])
|
||||
@login_required
|
||||
def api_social_audit_trigger():
|
||||
"""
|
||||
API: Trigger Social Media audit for a company.
|
||||
|
||||
This endpoint performs a comprehensive social media audit:
|
||||
- Scans company website for social media links
|
||||
- Searches for profiles via Brave Search API (if configured)
|
||||
- Fetches Google Business Profile data
|
||||
- Updates database with discovered profiles
|
||||
|
||||
Request JSON body:
|
||||
- company_id: Company ID (integer) OR
|
||||
- slug: Company slug (string)
|
||||
|
||||
Returns:
|
||||
- Success: Updated social media audit results
|
||||
- Error: Error message with status code
|
||||
|
||||
Rate limited to 10 requests per hour per user.
|
||||
"""
|
||||
# Import the SocialMediaAuditor from scripts
|
||||
try:
|
||||
scripts_dir = Path(__file__).parent.parent.parent / 'scripts'
|
||||
if str(scripts_dir) not in sys.path:
|
||||
sys.path.insert(0, str(scripts_dir))
|
||||
from social_media_audit import SocialMediaAuditor
|
||||
except ImportError as e:
|
||||
logger.error(f"Failed to import SocialMediaAuditor: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Usługa audytu Social Media 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
|
||||
|
||||
# Access control - admin can audit all, users only their company
|
||||
if not current_user.is_admin:
|
||||
if current_user.company_id != company.id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Brak uprawnień do audytu social media tej firmy.'
|
||||
}), 403
|
||||
|
||||
logger.info(f"Social Media audit triggered by {current_user.email} for company: {company.name} (ID: {company.id})")
|
||||
|
||||
# Prepare company dict for auditor
|
||||
company_dict = {
|
||||
'id': company.id,
|
||||
'name': company.name,
|
||||
'slug': company.slug,
|
||||
'website': company.website,
|
||||
'address_city': company.address_city or 'Wejherowo'
|
||||
}
|
||||
|
||||
# Initialize auditor and run audit
|
||||
try:
|
||||
auditor = SocialMediaAuditor()
|
||||
audit_result = auditor.audit_company(company_dict)
|
||||
|
||||
# Check for errors
|
||||
if audit_result.get('errors') and not audit_result.get('social_media') and not audit_result.get('website'):
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Audyt nie powiódł się: {", ".join(audit_result["errors"][:3])}',
|
||||
'company_id': company.id,
|
||||
'company_name': company.name
|
||||
}), 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 count of social media profiles found
|
||||
social_media_found = audit_result.get('social_media', {})
|
||||
platforms_count = len(social_media_found)
|
||||
|
||||
# Calculate score
|
||||
all_platforms = ['facebook', 'instagram', 'linkedin', 'youtube', 'twitter', 'tiktok']
|
||||
score = int((platforms_count / len(all_platforms)) * 100)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': f'Audyt Social Media zakończony. Znaleziono {platforms_count} profili.',
|
||||
'company_id': company.id,
|
||||
'company_name': company.name,
|
||||
'profiles_found': platforms_count,
|
||||
'platforms': list(social_media_found.keys()),
|
||||
'score': score,
|
||||
'google_reviews': audit_result.get('google_reviews', {}),
|
||||
'errors': audit_result.get('errors') if audit_result.get('errors') else None
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Social Media audit error for company {company.id}: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Błąd podczas audytu: {str(e)}'
|
||||
}), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Social Media audit error for company {slug or company_id}: {e}")
|
||||
db.rollback()
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Błąd podczas audytu: {str(e)}'
|
||||
}), 500
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
Loading…
Reference in New Issue
Block a user