Base code

This commit is contained in:
Kunthawat Greethong
2026-01-08 22:39:53 +07:00
parent 697115c61a
commit c35fa52117
2169 changed files with 626670 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
"""
Analytics Handlers Package
Contains platform-specific analytics handlers.
"""
from .base_handler import BaseAnalyticsHandler
from .gsc_handler import GSCAnalyticsHandler
from .bing_handler import BingAnalyticsHandler
from .wordpress_handler import WordPressAnalyticsHandler
from .wix_handler import WixAnalyticsHandler
__all__ = [
'BaseAnalyticsHandler',
'GSCAnalyticsHandler',
'BingAnalyticsHandler',
'WordPressAnalyticsHandler',
'WixAnalyticsHandler'
]

View File

@@ -0,0 +1,88 @@
"""
Base Analytics Handler
Abstract base class for platform-specific analytics handlers.
"""
from abc import ABC, abstractmethod
from typing import Dict, Any, Optional
from datetime import datetime
from ..models.analytics_data import AnalyticsData
from ..models.platform_types import PlatformType
class BaseAnalyticsHandler(ABC):
"""Abstract base class for platform analytics handlers"""
def __init__(self, platform_type: PlatformType):
self.platform_type = platform_type
self.platform_name = platform_type.value
@abstractmethod
async def get_analytics(self, user_id: str) -> AnalyticsData:
"""
Get analytics data for the platform
Args:
user_id: User ID to get analytics for
Returns:
AnalyticsData object with platform metrics
"""
pass
@abstractmethod
def get_connection_status(self, user_id: str) -> Dict[str, Any]:
"""
Get connection status for the platform
Args:
user_id: User ID to check connection for
Returns:
Dictionary with connection status information
"""
pass
def create_error_response(self, error_message: str) -> AnalyticsData:
"""Create a standardized error response"""
return AnalyticsData(
platform=self.platform_name,
metrics={},
date_range={'start': '', 'end': ''},
last_updated=datetime.now().isoformat(),
status='error',
error_message=error_message
)
def create_partial_response(self, metrics: Dict[str, Any], error_message: str = None) -> AnalyticsData:
"""Create a standardized partial response"""
return AnalyticsData(
platform=self.platform_name,
metrics=metrics,
date_range={'start': '', 'end': ''},
last_updated=datetime.now().isoformat(),
status='partial',
error_message=error_message
)
def create_success_response(self, metrics: Dict[str, Any], date_range: Dict[str, str] = None) -> AnalyticsData:
"""Create a standardized success response"""
return AnalyticsData(
platform=self.platform_name,
metrics=metrics,
date_range=date_range or {'start': '', 'end': ''},
last_updated=datetime.now().isoformat(),
status='success'
)
def log_analytics_request(self, user_id: str, operation: str):
"""Log analytics request for monitoring"""
from loguru import logger
logger.info(f"{self.platform_name} analytics: {operation} for user {user_id}")
def log_analytics_error(self, user_id: str, operation: str, error: Exception):
"""Log analytics error for monitoring"""
from loguru import logger
logger.error(f"{self.platform_name} analytics: {operation} failed for user {user_id}: {error}")

View File

@@ -0,0 +1,279 @@
"""
Bing Webmaster Tools Analytics Handler
Handles Bing Webmaster Tools analytics data retrieval and processing.
"""
import requests
from typing import Dict, Any
from datetime import datetime, timedelta
from loguru import logger
from services.integrations.bing_oauth import BingOAuthService
from ...analytics_cache_service import analytics_cache
from ..models.analytics_data import AnalyticsData
from ..models.platform_types import PlatformType
from .base_handler import BaseAnalyticsHandler
from ..insights.bing_insights_service import BingInsightsService
from services.bing_analytics_storage_service import BingAnalyticsStorageService
import os
class BingAnalyticsHandler(BaseAnalyticsHandler):
"""Handler for Bing Webmaster Tools analytics"""
def __init__(self):
super().__init__(PlatformType.BING)
self.bing_service = BingOAuthService()
# Initialize insights service
database_url = os.getenv('DATABASE_URL', 'sqlite:///./bing_analytics.db')
self.insights_service = BingInsightsService(database_url)
# Storage service used in onboarding step 5
self.storage_service = BingAnalyticsStorageService(os.getenv('DATABASE_URL', 'sqlite:///alwrity.db'))
async def get_analytics(self, user_id: str) -> AnalyticsData:
"""
Get Bing Webmaster analytics data using Bing Webmaster API
Note: Bing Webmaster provides SEO insights and search performance data
"""
self.log_analytics_request(user_id, "get_analytics")
# Check cache first - this is an expensive operation
cached_data = analytics_cache.get('bing_analytics', user_id)
if cached_data:
logger.info("Using cached Bing analytics for user {user_id}", user_id=user_id)
return AnalyticsData(**cached_data)
logger.info("Fetching fresh Bing analytics for user {user_id} (expensive operation)", user_id=user_id)
try:
# Get user's Bing connection status with detailed token info
token_status = self.bing_service.get_user_token_status(user_id)
if not token_status.get('has_active_tokens'):
if token_status.get('has_expired_tokens'):
return self.create_error_response('Bing Webmaster tokens expired - please reconnect')
else:
return self.create_error_response('Bing Webmaster not connected')
# Try once to fetch sites (may return empty if tokens are valid but no verified sites); do not block
sites = self.bing_service.get_user_sites(user_id)
# Get active tokens for access token
active_tokens = token_status.get('active_tokens', [])
if not active_tokens:
return self.create_error_response('No active Bing Webmaster tokens available')
# Get the first active token's access token
token_info = active_tokens[0]
access_token = token_info.get('access_token')
# Cache the sites for future use (even if empty)
analytics_cache.set('bing_sites', user_id, sites or [], ttl_override=2*60*60)
logger.info(f"Cached Bing sites for analytics for user {user_id} (TTL: 2 hours)")
if not access_token:
return self.create_error_response('Bing Webmaster access token not available')
# Do NOT call live Bing APIs here; use stored analytics like step 5
query_stats = {}
try:
# If sites available, use first; otherwise ask storage for any stored summary
site_url_for_storage = sites[0].get('Url', '') if (sites and isinstance(sites[0], dict)) else None
stored = self.storage_service.get_analytics_summary(user_id, site_url_for_storage, days=30)
if stored and isinstance(stored, dict):
query_stats = {
'total_clicks': stored.get('summary', {}).get('total_clicks', 0),
'total_impressions': stored.get('summary', {}).get('total_impressions', 0),
'total_queries': stored.get('summary', {}).get('total_queries', 0),
'avg_ctr': stored.get('summary', {}).get('total_ctr', 0),
'avg_position': stored.get('summary', {}).get('avg_position', 0),
}
except Exception as e:
logger.warning(f"Bing analytics: Failed to read stored analytics summary: {e}")
# Get enhanced insights from database
insights = self._get_enhanced_insights(user_id, sites[0].get('Url', '') if sites else '')
# Extract comprehensive site information with actual metrics
metrics = {
'connection_status': 'connected',
'connected_sites': len(sites),
'sites': sites[:5] if sites else [],
'connected_since': token_info.get('created_at', ''),
'scope': token_info.get('scope', ''),
'total_clicks': query_stats.get('total_clicks', 0),
'total_impressions': query_stats.get('total_impressions', 0),
'total_queries': query_stats.get('total_queries', 0),
'avg_ctr': query_stats.get('avg_ctr', 0),
'avg_position': query_stats.get('avg_position', 0),
'insights': insights,
'note': 'Bing Webmaster API provides SEO insights, search performance, and index status data'
}
# If no stored data or no sites, return partial like step 5, else success
if (not sites) or (metrics.get('total_impressions', 0) == 0 and metrics.get('total_clicks', 0) == 0):
result = self.create_partial_response(metrics=metrics, error_message='Connected to Bing; waiting for stored analytics or site verification')
else:
result = self.create_success_response(metrics=metrics)
# Cache the result to avoid expensive API calls
analytics_cache.set('bing_analytics', user_id, result.__dict__)
logger.info("Cached Bing analytics data for user {user_id}", user_id=user_id)
return result
except Exception as e:
self.log_analytics_error(user_id, "get_analytics", e)
error_result = self.create_error_response(str(e))
# Cache error result for shorter time to retry sooner
analytics_cache.set('bing_analytics', user_id, error_result.__dict__, ttl_override=300) # 5 minutes
return error_result
def get_connection_status(self, user_id: str) -> Dict[str, Any]:
"""Get Bing Webmaster connection status"""
self.log_analytics_request(user_id, "get_connection_status")
try:
bing_connection = self.bing_service.get_connection_status(user_id)
return {
'connected': bing_connection.get('connected', False),
'sites_count': bing_connection.get('total_sites', 0),
'sites': bing_connection.get('sites', []),
'error': None
}
except Exception as e:
self.log_analytics_error(user_id, "get_connection_status", e)
return {
'connected': False,
'sites_count': 0,
'sites': [],
'error': str(e)
}
def _extract_user_sites(self, sites_data: Any) -> list:
"""Extract user sites from Bing API response"""
if isinstance(sites_data, dict):
if 'd' in sites_data:
d_data = sites_data['d']
if isinstance(d_data, dict) and 'results' in d_data:
return d_data['results']
elif isinstance(d_data, list):
return d_data
else:
return []
else:
return []
elif isinstance(sites_data, list):
return sites_data
else:
return []
async def _get_query_stats(self, user_id: str, sites: list) -> Dict[str, Any]:
"""Get query statistics for Bing sites"""
query_stats = {}
logger.info(f"Bing sites found: {len(sites)} sites")
if sites:
first_site = sites[0]
logger.info(f"First Bing site: {first_site}")
# Bing API returns URL in 'Url' field (capital U)
site_url = first_site.get('Url', '') if isinstance(first_site, dict) else str(first_site)
logger.info(f"Extracted site URL: {site_url}")
if site_url:
try:
# Use the Bing service method to get query stats
logger.info(f"Getting Bing query stats for site: {site_url}")
query_data = self.bing_service.get_query_stats(
user_id=user_id,
site_url=site_url,
start_date=(datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d'),
end_date=datetime.now().strftime('%Y-%m-%d'),
page=0
)
if "error" not in query_data:
logger.info(f"Bing query stats response structure: {type(query_data)}, keys: {list(query_data.keys()) if isinstance(query_data, dict) else 'Not a dict'}")
logger.info(f"Bing query stats raw response: {query_data}")
# Handle different response structures from Bing API
queries = self._extract_queries(query_data)
logger.info(f"Bing queries extracted: {len(queries)} queries")
if queries and len(queries) > 0:
logger.info(f"First query sample: {queries[0] if isinstance(queries[0], dict) else queries[0]}")
# Calculate summary metrics
total_clicks = sum(query.get('Clicks', 0) for query in queries if isinstance(query, dict))
total_impressions = sum(query.get('Impressions', 0) for query in queries if isinstance(query, dict))
total_queries = len(queries)
avg_ctr = (total_clicks / total_impressions * 100) if total_impressions > 0 else 0
avg_position = sum(query.get('AvgClickPosition', 0) for query in queries if isinstance(query, dict)) / total_queries if total_queries > 0 else 0
query_stats = {
'total_clicks': total_clicks,
'total_impressions': total_impressions,
'total_queries': total_queries,
'avg_ctr': round(avg_ctr, 2),
'avg_position': round(avg_position, 2)
}
logger.info(f"Bing query stats calculated: {query_stats}")
else:
logger.warning(f"Bing query stats error: {query_data['error']}")
except Exception as e:
logger.warning(f"Error getting Bing query stats: {e}")
return query_stats
def _extract_queries(self, query_data: Any) -> list:
"""Extract queries from Bing API response"""
if isinstance(query_data, dict):
if 'd' in query_data:
d_data = query_data['d']
logger.info(f"Bing 'd' data structure: {type(d_data)}, keys: {list(d_data.keys()) if isinstance(d_data, dict) else 'Not a dict'}")
if isinstance(d_data, dict) and 'results' in d_data:
return d_data['results']
elif isinstance(d_data, list):
return d_data
else:
return []
else:
return []
elif isinstance(query_data, list):
return query_data
else:
return []
def _get_enhanced_insights(self, user_id: str, site_url: str) -> Dict[str, Any]:
"""Get enhanced insights from stored Bing analytics data"""
try:
if not site_url:
return {'status': 'no_site_url', 'message': 'No site URL available for insights'}
# Get performance insights
performance_insights = self.insights_service.get_performance_insights(user_id, site_url, days=30)
# Get SEO insights
seo_insights = self.insights_service.get_seo_insights(user_id, site_url, days=30)
# Get actionable recommendations
recommendations = self.insights_service.get_actionable_recommendations(user_id, site_url, days=30)
return {
'performance': performance_insights,
'seo': seo_insights,
'recommendations': recommendations,
'last_analyzed': datetime.now().isoformat()
}
except Exception as e:
logger.warning(f"Error getting enhanced insights: {e}")
return {
'status': 'error',
'message': f'Unable to generate insights: {str(e)}',
'fallback': True
}

View File

@@ -0,0 +1,255 @@
"""
Google Search Console Analytics Handler
Handles GSC analytics data retrieval and processing.
"""
from typing import Dict, Any
from datetime import datetime, timedelta
from loguru import logger
from services.gsc_service import GSCService
from ...analytics_cache_service import analytics_cache
from ..models.analytics_data import AnalyticsData
from ..models.platform_types import PlatformType
from .base_handler import BaseAnalyticsHandler
class GSCAnalyticsHandler(BaseAnalyticsHandler):
"""Handler for Google Search Console analytics"""
def __init__(self):
super().__init__(PlatformType.GSC)
self.gsc_service = GSCService()
async def get_analytics(self, user_id: str) -> AnalyticsData:
"""
Get Google Search Console analytics data with caching
Returns comprehensive SEO metrics including clicks, impressions, CTR, and position data.
"""
self.log_analytics_request(user_id, "get_analytics")
# Check cache first - GSC API calls can be expensive
cached_data = analytics_cache.get('gsc_analytics', user_id)
if cached_data:
logger.info("Using cached GSC analytics for user {user_id}", user_id=user_id)
return AnalyticsData(**cached_data)
logger.info("Fetching fresh GSC analytics for user {user_id}", user_id=user_id)
try:
# Get user's sites
sites = self.gsc_service.get_site_list(user_id)
logger.info(f"GSC Sites found for user {user_id}: {sites}")
if not sites:
logger.warning(f"No GSC sites found for user {user_id}")
return self.create_error_response('No GSC sites found')
# Get analytics for the first site (or combine all sites)
site_url = sites[0]['siteUrl']
logger.info(f"Using GSC site URL: {site_url}")
# Get search analytics for last 30 days
end_date = datetime.now().strftime('%Y-%m-%d')
start_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
logger.info(f"GSC Date range: {start_date} to {end_date}")
search_analytics = self.gsc_service.get_search_analytics(
user_id=user_id,
site_url=site_url,
start_date=start_date,
end_date=end_date
)
logger.info(f"GSC Search analytics retrieved for user {user_id}")
# Process GSC data into standardized format
processed_metrics = self._process_gsc_metrics(search_analytics)
result = self.create_success_response(
metrics=processed_metrics,
date_range={'start': start_date, 'end': end_date}
)
# Cache the result to avoid expensive API calls
analytics_cache.set('gsc_analytics', user_id, result.__dict__)
logger.info("Cached GSC analytics data for user {user_id}", user_id=user_id)
return result
except Exception as e:
self.log_analytics_error(user_id, "get_analytics", e)
error_result = self.create_error_response(str(e))
# Cache error result for shorter time to retry sooner
analytics_cache.set('gsc_analytics', user_id, error_result.__dict__, ttl_override=300) # 5 minutes
return error_result
def get_connection_status(self, user_id: str) -> Dict[str, Any]:
"""Get GSC connection status"""
self.log_analytics_request(user_id, "get_connection_status")
try:
sites = self.gsc_service.get_site_list(user_id)
return {
'connected': len(sites) > 0,
'sites_count': len(sites),
'sites': sites[:3] if sites else [], # Show first 3 sites
'error': None
}
except Exception as e:
self.log_analytics_error(user_id, "get_connection_status", e)
return {
'connected': False,
'sites_count': 0,
'sites': [],
'error': str(e)
}
def _process_gsc_metrics(self, search_analytics: Dict[str, Any]) -> Dict[str, Any]:
"""Process GSC raw data into standardized metrics"""
try:
# Debug: Log the raw search analytics data structure
logger.info(f"GSC Raw search analytics structure: {search_analytics}")
logger.info(f"GSC Raw search analytics keys: {list(search_analytics.keys())}")
# Handle new data structure with overall_metrics and query_data
if 'overall_metrics' in search_analytics:
# New structure from updated GSC service
overall_rows = search_analytics.get('overall_metrics', {}).get('rows', [])
query_rows = search_analytics.get('query_data', {}).get('rows', [])
verification_rows = search_analytics.get('verification_data', {}).get('rows', [])
logger.info(f"GSC Overall metrics rows: {len(overall_rows)}")
logger.info(f"GSC Query data rows: {len(query_rows)}")
logger.info(f"GSC Verification rows: {len(verification_rows)}")
if overall_rows:
logger.info(f"GSC Overall first row: {overall_rows[0]}")
if query_rows:
logger.info(f"GSC Query first row: {query_rows[0]}")
# Use query_rows for detailed insights, overall_rows for summary
rows = query_rows if query_rows else overall_rows
else:
# Legacy structure
rows = search_analytics.get('rows', [])
logger.info(f"GSC Legacy rows count: {len(rows)}")
if rows:
logger.info(f"GSC Legacy first row structure: {rows[0]}")
logger.info(f"GSC Legacy first row keys: {list(rows[0].keys()) if rows[0] else 'No rows'}")
# Calculate summary metrics - handle different response formats
total_clicks = 0
total_impressions = 0
total_position = 0
valid_rows = 0
for row in rows:
# Handle different possible response formats
clicks = row.get('clicks', 0)
impressions = row.get('impressions', 0)
position = row.get('position', 0)
# If position is 0 or None, skip it from average calculation
if position and position > 0:
total_position += position
valid_rows += 1
total_clicks += clicks
total_impressions += impressions
avg_ctr = (total_clicks / total_impressions * 100) if total_impressions > 0 else 0
avg_position = total_position / valid_rows if valid_rows > 0 else 0
logger.info(f"GSC Calculated metrics - clicks: {total_clicks}, impressions: {total_impressions}, ctr: {avg_ctr}, position: {avg_position}, valid_rows: {valid_rows}")
# Get top performing queries - handle different data structures
if rows and 'keys' in rows[0]:
# New GSC API format with keys array
top_queries = sorted(rows, key=lambda x: x.get('clicks', 0), reverse=True)[:10]
# Get top performing pages (if we have page data)
page_data = {}
for row in rows:
# Handle different key structures
keys = row.get('keys', [])
if len(keys) > 1 and keys[1]: # Page data available
page = keys[1].get('keys', ['Unknown'])[0] if isinstance(keys[1], dict) else str(keys[1])
else:
page = 'Unknown'
if page not in page_data:
page_data[page] = {'clicks': 0, 'impressions': 0, 'ctr': 0, 'position': 0}
page_data[page]['clicks'] += row.get('clicks', 0)
page_data[page]['impressions'] += row.get('impressions', 0)
else:
# Legacy format or no keys structure
top_queries = sorted(rows, key=lambda x: x.get('clicks', 0), reverse=True)[:10]
page_data = {}
# Calculate page metrics
for page in page_data:
if page_data[page]['impressions'] > 0:
page_data[page]['ctr'] = page_data[page]['clicks'] / page_data[page]['impressions'] * 100
top_pages = sorted(page_data.items(), key=lambda x: x[1]['clicks'], reverse=True)[:10]
return {
'connection_status': 'connected',
'connected_sites': 1, # GSC typically has one site per user
'total_clicks': total_clicks,
'total_impressions': total_impressions,
'avg_ctr': round(avg_ctr, 2),
'avg_position': round(avg_position, 2),
'total_queries': len(rows),
'top_queries': [
{
'query': self._extract_query_from_row(row),
'clicks': row.get('clicks', 0),
'impressions': row.get('impressions', 0),
'ctr': round(row.get('ctr', 0) * 100, 2),
'position': round(row.get('position', 0), 2)
}
for row in top_queries
],
'top_pages': [
{
'page': page,
'clicks': data['clicks'],
'impressions': data['impressions'],
'ctr': round(data['ctr'], 2)
}
for page, data in top_pages
],
'note': 'Google Search Console provides search performance data, keyword rankings, and SEO insights'
}
except Exception as e:
logger.error(f"Error processing GSC metrics: {e}")
return {
'connection_status': 'error',
'connected_sites': 0,
'total_clicks': 0,
'total_impressions': 0,
'avg_ctr': 0,
'avg_position': 0,
'total_queries': 0,
'top_queries': [],
'top_pages': [],
'error': str(e)
}
def _extract_query_from_row(self, row: Dict[str, Any]) -> str:
"""Extract query text from GSC API row data"""
try:
keys = row.get('keys', [])
if keys and len(keys) > 0:
first_key = keys[0]
if isinstance(first_key, dict):
return first_key.get('keys', ['Unknown'])[0]
else:
return str(first_key)
return 'Unknown'
except Exception as e:
logger.error(f"Error extracting query from row: {e}")
return 'Unknown'

View File

@@ -0,0 +1,71 @@
"""
Wix Analytics Handler
Handles Wix analytics data retrieval and processing.
Note: This is currently a placeholder implementation.
"""
from typing import Dict, Any
from loguru import logger
from services.wix_service import WixService
from ..models.analytics_data import AnalyticsData
from ..models.platform_types import PlatformType
from .base_handler import BaseAnalyticsHandler
class WixAnalyticsHandler(BaseAnalyticsHandler):
"""Handler for Wix analytics"""
def __init__(self):
super().__init__(PlatformType.WIX)
self.wix_service = WixService()
async def get_analytics(self, user_id: str) -> AnalyticsData:
"""
Get Wix analytics data using the Business Management API
Note: This requires the Wix Business Management API which may need additional permissions
"""
self.log_analytics_request(user_id, "get_analytics")
try:
# TODO: Implement Wix analytics retrieval
# This would require:
# 1. Storing Wix access tokens in database
# 2. Using Wix Business Management API
# 3. Requesting analytics permissions during OAuth
# For now, return a placeholder response
return self.create_partial_response(
metrics={
'connection_status': 'not_implemented',
'connected_sites': 0,
'page_views': 0,
'visitors': 0,
'bounce_rate': 0,
'avg_session_duration': 0,
'top_pages': [],
'traffic_sources': {},
'device_breakdown': {},
'geo_distribution': {},
'note': 'Wix analytics integration coming soon'
},
error_message='Wix analytics integration coming soon'
)
except Exception as e:
self.log_analytics_error(user_id, "get_analytics", e)
return self.create_error_response(str(e))
def get_connection_status(self, user_id: str) -> Dict[str, Any]:
"""Get Wix connection status"""
self.log_analytics_request(user_id, "get_connection_status")
# TODO: Implement actual Wix connection check
return {
'connected': False, # TODO: Implement actual Wix connection check
'sites_count': 0,
'sites': [],
'error': 'Wix connection check not implemented'
}

View File

@@ -0,0 +1,119 @@
"""
WordPress.com Analytics Handler
Handles WordPress.com analytics data retrieval and processing.
"""
import requests
from typing import Dict, Any
from datetime import datetime
from loguru import logger
from services.integrations.wordpress_oauth import WordPressOAuthService
from ..models.analytics_data import AnalyticsData
from ..models.platform_types import PlatformType
from .base_handler import BaseAnalyticsHandler
class WordPressAnalyticsHandler(BaseAnalyticsHandler):
"""Handler for WordPress.com analytics"""
def __init__(self):
super().__init__(PlatformType.WORDPRESS)
self.wordpress_service = WordPressOAuthService()
async def get_analytics(self, user_id: str) -> AnalyticsData:
"""
Get WordPress analytics data using WordPress.com REST API
Note: WordPress.com has limited analytics API access
We'll try to get basic site stats and post data
"""
self.log_analytics_request(user_id, "get_analytics")
try:
# Get user's WordPress tokens
connection_status = self.wordpress_service.get_connection_status(user_id)
if not connection_status.get('connected'):
return self.create_error_response('WordPress not connected')
# Get the first connected site
sites = connection_status.get('sites', [])
if not sites:
return self.create_error_response('No WordPress sites found')
site = sites[0]
access_token = site.get('access_token')
blog_id = site.get('blog_id')
if not access_token or not blog_id:
return self.create_error_response('WordPress access token not available')
# Try to get basic site stats from WordPress.com API
headers = {
'Authorization': f'Bearer {access_token}',
'User-Agent': 'ALwrity/1.0'
}
# Get site info and basic stats
site_info_url = f"https://public-api.wordpress.com/rest/v1.1/sites/{blog_id}"
response = requests.get(site_info_url, headers=headers, timeout=10)
if response.status_code != 200:
logger.warning(f"WordPress API call failed: {response.status_code}")
# Return basic connection info instead of full analytics
return self.create_partial_response(
metrics={
'site_name': site.get('blog_url', 'Unknown'),
'connection_status': 'connected',
'blog_id': blog_id,
'connected_since': site.get('created_at', ''),
'note': 'WordPress.com API has limited analytics access'
},
error_message='WordPress.com API has limited analytics access'
)
site_data = response.json()
# Extract basic site information
metrics = {
'site_name': site_data.get('name', 'Unknown'),
'site_url': site_data.get('URL', ''),
'blog_id': blog_id,
'language': site_data.get('lang', ''),
'timezone': site_data.get('timezone', ''),
'is_private': site_data.get('is_private', False),
'is_coming_soon': site_data.get('is_coming_soon', False),
'connected_since': site.get('created_at', ''),
'connection_status': 'connected',
'connected_sites': len(sites),
'note': 'WordPress.com API has limited analytics access. For detailed analytics, consider integrating with Google Analytics or Jetpack Stats.'
}
return self.create_success_response(metrics=metrics)
except Exception as e:
self.log_analytics_error(user_id, "get_analytics", e)
return self.create_error_response(str(e))
def get_connection_status(self, user_id: str) -> Dict[str, Any]:
"""Get WordPress.com connection status"""
self.log_analytics_request(user_id, "get_connection_status")
try:
wp_connection = self.wordpress_service.get_connection_status(user_id)
return {
'connected': wp_connection.get('connected', False),
'sites_count': wp_connection.get('total_sites', 0),
'sites': wp_connection.get('sites', []),
'error': None
}
except Exception as e:
self.log_analytics_error(user_id, "get_connection_status", e)
return {
'connected': False,
'sites_count': 0,
'sites': [],
'error': str(e)
}