auto-claude: subtask-3-1 - Create API endpoint for running GBP audit on a company
Added GBP (Google Business Profile) audit API endpoints: - GET /api/gbp/audit/health - Health check for GBP audit service - GET /api/gbp/audit - Fetch latest audit results by company_id or slug - GET /api/gbp/audit/<slug> - Fetch audit results by slug path - POST /api/gbp/audit - Run GBP audit for a company Features: - Health endpoint returns service status and version - GET endpoints return completeness score, field status, and recommendations - POST endpoint runs audit and saves results (configurable) - Access control: members can audit own company, admins can audit any - Rate limited to 20 requests per hour Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ecf811f168
commit
a892626ebc
337
app.py
337
app.py
@ -134,6 +134,16 @@ except ImportError as e:
|
||||
SEO_AUDIT_AVAILABLE = False
|
||||
logger.warning(f"SEO audit service not available: {e}")
|
||||
|
||||
# GBP (Google Business Profile) audit service
|
||||
try:
|
||||
from gbp_audit_service import GBPAuditService, audit_company as gbp_audit_company, get_company_audit as gbp_get_company_audit
|
||||
GBP_AUDIT_AVAILABLE = True
|
||||
GBP_AUDIT_VERSION = '1.0'
|
||||
except ImportError as e:
|
||||
GBP_AUDIT_AVAILABLE = False
|
||||
GBP_AUDIT_VERSION = None
|
||||
logger.warning(f"GBP 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')
|
||||
@ -3624,6 +3634,333 @@ def admin_seo():
|
||||
db.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# GBP (GOOGLE BUSINESS PROFILE) AUDIT API
|
||||
# ============================================================
|
||||
|
||||
@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})")
|
||||
|
||||
try:
|
||||
# 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'
|
||||
|
||||
return jsonify({
|
||||
'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
|
||||
}
|
||||
}), 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()
|
||||
|
||||
|
||||
@app.route('/api/check-email', methods=['POST'])
|
||||
def api_check_email():
|
||||
"""API: Check if email is available"""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user