- 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>
388 lines
13 KiB
Python
388 lines
13 KiB
Python
"""
|
|
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()
|