SEO Dashboard Fixes and content planning refactoring
This commit is contained in:
@@ -50,7 +50,9 @@ async def health() -> Dict[str, Any]:
|
||||
async def start_research(request: BlogResearchRequest) -> Dict[str, Any]:
|
||||
"""Start a research operation and return a task ID for polling."""
|
||||
try:
|
||||
task_id = task_manager.start_research_task(request)
|
||||
# TODO: Get user_id from authentication context
|
||||
user_id = "anonymous" # This should come from auth middleware
|
||||
task_id = await task_manager.start_research_task(request, user_id)
|
||||
return {"task_id": task_id, "status": "started"}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to start research: {e}")
|
||||
@@ -61,7 +63,7 @@ async def start_research(request: BlogResearchRequest) -> Dict[str, Any]:
|
||||
async def get_research_status(task_id: str) -> Dict[str, Any]:
|
||||
"""Get the status of a research operation."""
|
||||
try:
|
||||
status = task_manager.get_task_status(task_id)
|
||||
status = await task_manager.get_task_status(task_id)
|
||||
if status is None:
|
||||
raise HTTPException(status_code=404, detail="Task not found")
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ Task Management System for Blog Writer API
|
||||
|
||||
Handles background task execution, status tracking, and progress updates
|
||||
for research and outline generation operations.
|
||||
Now uses database-backed persistence for reliability and recovery.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
@@ -18,14 +19,22 @@ from models.blog_models import (
|
||||
MediumBlogGenerateResult,
|
||||
)
|
||||
from services.blog_writer.blog_service import BlogWriterService
|
||||
from services.blog_writer.database_task_manager import DatabaseTaskManager
|
||||
|
||||
|
||||
class TaskManager:
|
||||
"""Manages background tasks for research and outline generation."""
|
||||
|
||||
def __init__(self):
|
||||
self.task_storage: Dict[str, Dict[str, Any]] = {}
|
||||
self.service = BlogWriterService()
|
||||
def __init__(self, db_connection=None):
|
||||
# Fallback to in-memory storage if no database connection
|
||||
if db_connection:
|
||||
self.db_manager = DatabaseTaskManager(db_connection)
|
||||
self.use_database = True
|
||||
else:
|
||||
self.task_storage: Dict[str, Dict[str, Any]] = {}
|
||||
self.service = BlogWriterService()
|
||||
self.use_database = False
|
||||
logger.warning("No database connection provided, using in-memory task storage")
|
||||
|
||||
def cleanup_old_tasks(self):
|
||||
"""Remove tasks older than 1 hour to prevent memory leaks."""
|
||||
@@ -54,54 +63,61 @@ class TaskManager:
|
||||
|
||||
return task_id
|
||||
|
||||
def get_task_status(self, task_id: str) -> Dict[str, Any]:
|
||||
async def get_task_status(self, task_id: str) -> Dict[str, Any]:
|
||||
"""Get the status of a task."""
|
||||
self.cleanup_old_tasks()
|
||||
|
||||
if task_id not in self.task_storage:
|
||||
return None
|
||||
|
||||
task = self.task_storage[task_id]
|
||||
response = {
|
||||
"task_id": task_id,
|
||||
"status": task["status"],
|
||||
"created_at": task["created_at"].isoformat(),
|
||||
"progress_messages": task.get("progress_messages", [])
|
||||
}
|
||||
|
||||
if task["status"] == "completed":
|
||||
response["result"] = task["result"]
|
||||
elif task["status"] == "failed":
|
||||
response["error"] = task["error"]
|
||||
|
||||
return response
|
||||
|
||||
async def update_progress(self, task_id: str, message: str):
|
||||
"""Update progress message for a task."""
|
||||
if task_id in self.task_storage:
|
||||
if "progress_messages" not in self.task_storage[task_id]:
|
||||
self.task_storage[task_id]["progress_messages"] = []
|
||||
if self.use_database:
|
||||
return await self.db_manager.get_task_status(task_id)
|
||||
else:
|
||||
self.cleanup_old_tasks()
|
||||
|
||||
progress_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"message": message
|
||||
if task_id not in self.task_storage:
|
||||
return None
|
||||
|
||||
task = self.task_storage[task_id]
|
||||
response = {
|
||||
"task_id": task_id,
|
||||
"status": task["status"],
|
||||
"created_at": task["created_at"].isoformat(),
|
||||
"progress_messages": task.get("progress_messages", [])
|
||||
}
|
||||
self.task_storage[task_id]["progress_messages"].append(progress_entry)
|
||||
|
||||
# Keep only last 10 progress messages to prevent memory bloat
|
||||
if len(self.task_storage[task_id]["progress_messages"]) > 10:
|
||||
self.task_storage[task_id]["progress_messages"] = self.task_storage[task_id]["progress_messages"][-10:]
|
||||
if task["status"] == "completed":
|
||||
response["result"] = task["result"]
|
||||
elif task["status"] == "failed":
|
||||
response["error"] = task["error"]
|
||||
|
||||
logger.info(f"Progress update for task {task_id}: {message}")
|
||||
return response
|
||||
|
||||
def start_research_task(self, request: BlogResearchRequest) -> str:
|
||||
async def update_progress(self, task_id: str, message: str, percentage: float = None):
|
||||
"""Update progress message for a task."""
|
||||
if self.use_database:
|
||||
await self.db_manager.update_progress(task_id, message, percentage)
|
||||
else:
|
||||
if task_id in self.task_storage:
|
||||
if "progress_messages" not in self.task_storage[task_id]:
|
||||
self.task_storage[task_id]["progress_messages"] = []
|
||||
|
||||
progress_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"message": message
|
||||
}
|
||||
self.task_storage[task_id]["progress_messages"].append(progress_entry)
|
||||
|
||||
# Keep only last 10 progress messages to prevent memory bloat
|
||||
if len(self.task_storage[task_id]["progress_messages"]) > 10:
|
||||
self.task_storage[task_id]["progress_messages"] = self.task_storage[task_id]["progress_messages"][-10:]
|
||||
|
||||
logger.info(f"Progress update for task {task_id}: {message}")
|
||||
|
||||
async def start_research_task(self, request: BlogResearchRequest, user_id: str = "anonymous") -> str:
|
||||
"""Start a research operation and return a task ID."""
|
||||
task_id = self.create_task("research")
|
||||
|
||||
# Start the research operation in the background
|
||||
asyncio.create_task(self._run_research_task(task_id, request))
|
||||
|
||||
return task_id
|
||||
if self.use_database:
|
||||
return await self.db_manager.start_research_task(request, user_id)
|
||||
else:
|
||||
task_id = self.create_task("research")
|
||||
# Start the research operation in the background
|
||||
asyncio.create_task(self._run_research_task(task_id, request))
|
||||
return task_id
|
||||
|
||||
def start_outline_task(self, request: BlogOutlineRequest) -> str:
|
||||
"""Start an outline generation operation and return a task ID."""
|
||||
|
||||
@@ -7,7 +7,7 @@ from fastapi import APIRouter, HTTPException
|
||||
from typing import Dict, Any
|
||||
from loguru import logger
|
||||
|
||||
from middleware.monitoring_middleware import get_monitoring_stats, get_lightweight_stats
|
||||
from services.subscription import get_monitoring_stats, get_lightweight_stats
|
||||
from services.comprehensive_user_data_cache_service import ComprehensiveUserDataCacheService
|
||||
from services.database import get_db
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from sqlalchemy.orm import Session
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
from services.ai_analysis_db_service import AIAnalysisDBService
|
||||
from services.ai_analytics_service import AIAnalyticsService
|
||||
from services.onboarding_data_service import OnboardingDataService
|
||||
from services.onboarding.data_service import OnboardingDataService
|
||||
|
||||
# Import utilities
|
||||
from ..utils.error_handlers import ContentPlanningErrorHandler
|
||||
|
||||
@@ -13,7 +13,7 @@ import time
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
from services.ai_analysis_db_service import AIAnalysisDBService
|
||||
from services.ai_analytics_service import AIAnalyticsService
|
||||
from services.onboarding_data_service import OnboardingDataService
|
||||
from services.onboarding.data_service import OnboardingDataService
|
||||
|
||||
# Import utilities
|
||||
from ..utils.error_handlers import ContentPlanningErrorHandler
|
||||
|
||||
@@ -307,7 +307,7 @@ class CalendarGenerationService:
|
||||
logger.info("🏥 Performing calendar generation health check")
|
||||
|
||||
# Check AI services
|
||||
from services.api_key_manager import APIKeyManager
|
||||
from services.onboarding.api_key_manager import APIKeyManager
|
||||
api_manager = APIKeyManager()
|
||||
api_key_status = check_all_api_keys(api_manager)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from sqlalchemy.orm import Session
|
||||
# Import database services
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
from services.ai_analysis_db_service import AIAnalysisDBService
|
||||
from services.onboarding_data_service import OnboardingDataService
|
||||
from services.onboarding.data_service import OnboardingDataService
|
||||
|
||||
# Import migrated content gap analysis services
|
||||
from services.content_gap_analyzer.content_gap_analyzer import ContentGapAnalyzer
|
||||
|
||||
@@ -7,7 +7,7 @@ import logging
|
||||
# Import our LinkedIn image generation services
|
||||
from services.linkedin.image_generation import LinkedInImageGenerator, LinkedInImageStorage
|
||||
from services.linkedin.image_prompts import LinkedInPromptGenerator
|
||||
from services.api_key_manager import APIKeyManager
|
||||
from services.onboarding.api_key_manager import APIKeyManager
|
||||
|
||||
# Set up logging
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import Dict, Any
|
||||
from fastapi import HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from services.api_key_manager import APIKeyManager
|
||||
from services.onboarding.api_key_manager import APIKeyManager
|
||||
from services.validation import check_all_api_keys
|
||||
|
||||
class APIKeyManagementService:
|
||||
@@ -21,7 +21,7 @@ class APIKeyManagementService:
|
||||
if not hasattr(self.api_key_manager, 'use_database'):
|
||||
self.api_key_manager.use_database = True
|
||||
try:
|
||||
from services.onboarding_database_service import OnboardingDatabaseService
|
||||
from services.onboarding.database_service import OnboardingDatabaseService
|
||||
self.api_key_manager.db_service = OnboardingDatabaseService()
|
||||
logger.info("Database service initialized for APIKeyManager")
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Dict, Any, List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
from services.api_key_manager import (
|
||||
from services.onboarding.api_key_manager import (
|
||||
OnboardingProgress,
|
||||
get_onboarding_progress,
|
||||
get_onboarding_progress_for_user,
|
||||
|
||||
@@ -5,7 +5,7 @@ from fastapi import HTTPException, Depends
|
||||
|
||||
from middleware.auth_middleware import get_current_user
|
||||
|
||||
from services.onboarding_progress_service import get_onboarding_progress_service
|
||||
from services.onboarding.progress_service import get_onboarding_progress_service
|
||||
|
||||
|
||||
def health_check():
|
||||
|
||||
@@ -8,8 +8,8 @@ from datetime import datetime
|
||||
from fastapi import HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from services.onboarding_progress_service import get_onboarding_progress_service
|
||||
from services.onboarding_database_service import OnboardingDatabaseService
|
||||
from services.onboarding.progress_service import get_onboarding_progress_service
|
||||
from services.onboarding.database_service import OnboardingDatabaseService
|
||||
from services.database import get_db
|
||||
from services.persona_analysis_service import PersonaAnalysisService
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Dict, Any
|
||||
from fastapi import HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from services.api_key_manager import get_api_key_manager
|
||||
from services.onboarding.api_key_manager import get_api_key_manager
|
||||
from services.validation import check_all_api_keys
|
||||
|
||||
class OnboardingConfigService:
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import Dict, Any
|
||||
from fastapi import HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from services.api_key_manager import get_onboarding_progress, get_onboarding_progress_for_user
|
||||
from services.onboarding.api_key_manager import get_onboarding_progress, get_onboarding_progress_for_user
|
||||
|
||||
class OnboardingControlService:
|
||||
"""Service for handling onboarding control operations."""
|
||||
|
||||
@@ -7,9 +7,9 @@ from typing import Dict, Any, Optional
|
||||
from fastapi import HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from services.api_key_manager import get_api_key_manager
|
||||
from services.onboarding.api_key_manager import get_api_key_manager
|
||||
from services.database import get_db
|
||||
from services.onboarding_database_service import OnboardingDatabaseService
|
||||
from services.onboarding.database_service import OnboardingDatabaseService
|
||||
from services.website_analysis_service import WebsiteAnalysisService
|
||||
from services.research_preferences_service import ResearchPreferencesService
|
||||
from services.persona_analysis_service import PersonaAnalysisService
|
||||
|
||||
@@ -7,8 +7,8 @@ from typing import Dict, Any, List, Optional
|
||||
from fastapi import HTTPException
|
||||
from loguru import logger
|
||||
|
||||
from services.onboarding_progress_service import get_onboarding_progress_service
|
||||
from services.onboarding_database_service import OnboardingDatabaseService
|
||||
from services.onboarding.progress_service import get_onboarding_progress_service
|
||||
from services.onboarding.database_service import OnboardingDatabaseService
|
||||
from services.database import get_db
|
||||
|
||||
class StepManagementService:
|
||||
|
||||
@@ -302,7 +302,7 @@ async def generate_platform_persona(user_id: str, platform: str, db_session):
|
||||
|
||||
# Import services
|
||||
from services.persona_data_service import PersonaDataService
|
||||
from services.onboarding_database_service import OnboardingDatabaseService
|
||||
from services.onboarding.database_service import OnboardingDatabaseService
|
||||
|
||||
persona_data_service = PersonaDataService(db_session=db_session)
|
||||
onboarding_service = OnboardingDatabaseService(db=db_session)
|
||||
|
||||
@@ -10,11 +10,13 @@ from loguru import logger
|
||||
import time
|
||||
|
||||
# Import existing services
|
||||
from services.api_key_manager import APIKeyManager
|
||||
from services.onboarding.api_key_manager import APIKeyManager
|
||||
from services.validation import check_all_api_keys
|
||||
from services.seo_analyzer import ComprehensiveSEOAnalyzer, SEOAnalysisResult, SEOAnalysisService
|
||||
from services.user_data_service import UserDataService
|
||||
from services.database import get_db_session
|
||||
from services.seo import SEODashboardService
|
||||
from middleware.auth_middleware import get_current_user
|
||||
|
||||
# Initialize the SEO analyzer
|
||||
seo_analyzer = ComprehensiveSEOAnalyzer()
|
||||
@@ -238,48 +240,126 @@ def generate_ai_insights(metrics: Dict[str, Any], platforms: Dict[str, Any]) ->
|
||||
return insights
|
||||
|
||||
# API Endpoints
|
||||
async def get_seo_dashboard_data() -> SEODashboardData:
|
||||
async def get_seo_dashboard_data(current_user: dict = Depends(get_current_user)) -> SEODashboardData:
|
||||
"""Get comprehensive SEO dashboard data."""
|
||||
try:
|
||||
# For now, return mock data
|
||||
# In production, this would fetch real data from database
|
||||
return get_mock_seo_data()
|
||||
user_id = str(current_user.get('id'))
|
||||
db_session = get_db_session()
|
||||
|
||||
if not db_session:
|
||||
logger.error("No database session available")
|
||||
return get_mock_seo_data()
|
||||
|
||||
try:
|
||||
# Use new SEO dashboard service
|
||||
dashboard_service = SEODashboardService(db_session)
|
||||
overview_data = await dashboard_service.get_dashboard_overview(user_id)
|
||||
|
||||
# Convert to SEODashboardData format
|
||||
return SEODashboardData(
|
||||
health_score=SEOHealthScore(**overview_data.get("health_score", {})),
|
||||
key_insight=overview_data.get("key_insight", "Connect your analytics accounts for personalized insights"),
|
||||
priority_alert=overview_data.get("priority_alert", "No alerts at this time"),
|
||||
metrics=_convert_metrics(overview_data.get("summary", {})),
|
||||
platforms=_convert_platforms(overview_data.get("platforms", {})),
|
||||
ai_insights=[AIInsight(**insight) for insight in overview_data.get("ai_insights", [])],
|
||||
last_updated=overview_data.get("last_updated", datetime.now().isoformat()),
|
||||
website_url=overview_data.get("website_url")
|
||||
)
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting SEO dashboard data: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get SEO dashboard data")
|
||||
# Fallback to mock data
|
||||
return get_mock_seo_data()
|
||||
|
||||
async def get_seo_health_score() -> SEOHealthScore:
|
||||
async def get_seo_health_score(current_user: dict = Depends(get_current_user)) -> SEOHealthScore:
|
||||
"""Get current SEO health score."""
|
||||
try:
|
||||
mock_data = get_mock_seo_data()
|
||||
return mock_data.health_score
|
||||
user_id = str(current_user.get('id'))
|
||||
db_session = get_db_session()
|
||||
|
||||
if not db_session:
|
||||
raise HTTPException(status_code=500, detail="Database connection unavailable")
|
||||
|
||||
try:
|
||||
dashboard_service = SEODashboardService(db_session)
|
||||
overview_data = await dashboard_service.get_dashboard_overview(user_id)
|
||||
health_score_data = overview_data.get("health_score", {})
|
||||
return SEOHealthScore(**health_score_data)
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting SEO health score: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get SEO health score")
|
||||
|
||||
async def get_seo_metrics() -> Dict[str, SEOMetric]:
|
||||
async def get_seo_metrics(current_user: dict = Depends(get_current_user)) -> Dict[str, SEOMetric]:
|
||||
"""Get SEO metrics."""
|
||||
try:
|
||||
mock_data = get_mock_seo_data()
|
||||
return mock_data.metrics
|
||||
user_id = str(current_user.get('id'))
|
||||
db_session = get_db_session()
|
||||
|
||||
if not db_session:
|
||||
raise HTTPException(status_code=500, detail="Database connection unavailable")
|
||||
|
||||
try:
|
||||
dashboard_service = SEODashboardService(db_session)
|
||||
overview_data = await dashboard_service.get_dashboard_overview(user_id)
|
||||
summary_data = overview_data.get("summary", {})
|
||||
return _convert_metrics(summary_data)
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting SEO metrics: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get SEO metrics")
|
||||
|
||||
async def get_platform_status() -> Dict[str, PlatformStatus]:
|
||||
async def get_platform_status(
|
||||
current_user: dict = Depends(get_current_user)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get platform connection status."""
|
||||
try:
|
||||
mock_data = get_mock_seo_data()
|
||||
return mock_data.platforms
|
||||
user_id = str(current_user.get('id'))
|
||||
db_session = get_db_session()
|
||||
|
||||
if not db_session:
|
||||
logger.error("No database session available")
|
||||
raise HTTPException(status_code=500, detail="Database connection failed")
|
||||
|
||||
try:
|
||||
# Use SEO dashboard service to get platform status
|
||||
dashboard_service = SEODashboardService(db_session)
|
||||
platform_status = await dashboard_service.get_platform_status(user_id)
|
||||
|
||||
logger.info(f"Retrieved platform status for user {user_id}")
|
||||
return platform_status
|
||||
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting platform status: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get platform status")
|
||||
|
||||
async def get_ai_insights() -> List[AIInsight]:
|
||||
async def get_ai_insights(current_user: dict = Depends(get_current_user)) -> List[AIInsight]:
|
||||
"""Get AI-generated insights."""
|
||||
try:
|
||||
mock_data = get_mock_seo_data()
|
||||
return mock_data.ai_insights
|
||||
user_id = str(current_user.get('id'))
|
||||
db_session = get_db_session()
|
||||
|
||||
if not db_session:
|
||||
raise HTTPException(status_code=500, detail="Database connection unavailable")
|
||||
|
||||
try:
|
||||
dashboard_service = SEODashboardService(db_session)
|
||||
overview_data = await dashboard_service.get_dashboard_overview(user_id)
|
||||
ai_insights_data = overview_data.get("ai_insights", [])
|
||||
return [AIInsight(**insight) for insight in ai_insights_data]
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting AI insights: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get AI insights")
|
||||
@@ -568,4 +648,205 @@ async def batch_analyze_urls(urls: List[str]) -> Dict[str, Any]:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error in batch analysis: {str(e)}"
|
||||
)
|
||||
)
|
||||
|
||||
# New SEO Dashboard Endpoints with Real Data
|
||||
|
||||
async def get_seo_dashboard_overview(
|
||||
current_user: dict = Depends(get_current_user),
|
||||
site_url: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Get comprehensive SEO dashboard overview with real GSC/Bing data."""
|
||||
try:
|
||||
user_id = str(current_user.get('id'))
|
||||
db_session = get_db_session()
|
||||
|
||||
if not db_session:
|
||||
logger.error("No database session available")
|
||||
raise HTTPException(status_code=500, detail="Database connection failed")
|
||||
|
||||
try:
|
||||
# Use SEO dashboard service to get real data
|
||||
dashboard_service = SEODashboardService(db_session)
|
||||
overview_data = await dashboard_service.get_dashboard_overview(user_id, site_url)
|
||||
|
||||
logger.info(f"Retrieved SEO dashboard overview for user {user_id}")
|
||||
return overview_data
|
||||
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting SEO dashboard overview: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get dashboard overview")
|
||||
|
||||
async def get_gsc_raw_data(
|
||||
current_user: dict = Depends(get_current_user),
|
||||
site_url: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Get raw GSC data for the specified site."""
|
||||
try:
|
||||
user_id = str(current_user.get('id'))
|
||||
db_session = get_db_session()
|
||||
|
||||
if not db_session:
|
||||
logger.error("No database session available")
|
||||
raise HTTPException(status_code=500, detail="Database connection failed")
|
||||
|
||||
try:
|
||||
# Use SEO dashboard service to get GSC data
|
||||
dashboard_service = SEODashboardService(db_session)
|
||||
gsc_data = await dashboard_service.get_gsc_data(user_id, site_url)
|
||||
|
||||
logger.info(f"Retrieved GSC raw data for user {user_id}")
|
||||
return gsc_data
|
||||
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting GSC raw data: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get GSC data")
|
||||
|
||||
async def get_bing_raw_data(
|
||||
current_user: dict = Depends(get_current_user),
|
||||
site_url: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Get raw Bing data for the specified site."""
|
||||
try:
|
||||
user_id = str(current_user.get('id'))
|
||||
db_session = get_db_session()
|
||||
|
||||
if not db_session:
|
||||
logger.error("No database session available")
|
||||
raise HTTPException(status_code=500, detail="Database connection failed")
|
||||
|
||||
try:
|
||||
# Use SEO dashboard service to get Bing data
|
||||
dashboard_service = SEODashboardService(db_session)
|
||||
bing_data = await dashboard_service.get_bing_data(user_id, site_url)
|
||||
|
||||
logger.info(f"Retrieved Bing raw data for user {user_id}")
|
||||
return bing_data
|
||||
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting Bing raw data: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get Bing data")
|
||||
|
||||
async def get_competitive_insights(
|
||||
current_user: dict = Depends(get_current_user),
|
||||
site_url: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Get competitive insights from onboarding step 3 data."""
|
||||
try:
|
||||
user_id = str(current_user.get('id'))
|
||||
db_session = get_db_session()
|
||||
|
||||
if not db_session:
|
||||
logger.error("No database session available")
|
||||
raise HTTPException(status_code=500, detail="Database connection failed")
|
||||
|
||||
try:
|
||||
# Use SEO dashboard service to get competitive insights
|
||||
dashboard_service = SEODashboardService(db_session)
|
||||
insights_data = await dashboard_service.get_competitive_insights(user_id)
|
||||
|
||||
logger.info(f"Retrieved competitive insights for user {user_id}")
|
||||
return insights_data
|
||||
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting competitive insights: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to get competitive insights")
|
||||
|
||||
async def refresh_analytics_data(
|
||||
current_user: dict = Depends(get_current_user),
|
||||
site_url: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Refresh analytics data by invalidating cache and fetching fresh data."""
|
||||
try:
|
||||
user_id = str(current_user.get('id'))
|
||||
db_session = get_db_session()
|
||||
|
||||
if not db_session:
|
||||
logger.error("No database session available")
|
||||
raise HTTPException(status_code=500, detail="Database connection failed")
|
||||
|
||||
try:
|
||||
# Use SEO dashboard service to refresh data
|
||||
dashboard_service = SEODashboardService(db_session)
|
||||
refresh_result = await dashboard_service.refresh_analytics_data(user_id, site_url)
|
||||
|
||||
logger.info(f"Refreshed analytics data for user {user_id}")
|
||||
return refresh_result
|
||||
|
||||
finally:
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error refreshing analytics data: {e}")
|
||||
raise HTTPException(status_code=500, detail="Failed to refresh analytics data")
|
||||
|
||||
# Helper methods for data conversion
|
||||
def _convert_metrics(summary_data: Dict[str, Any]) -> Dict[str, SEOMetric]:
|
||||
"""Convert summary data to SEOMetric format."""
|
||||
try:
|
||||
return {
|
||||
"traffic": SEOMetric(
|
||||
value=summary_data.get("clicks", 0),
|
||||
change=0, # Would calculate from historical data
|
||||
trend="up",
|
||||
description="Organic traffic",
|
||||
color="#4CAF50"
|
||||
),
|
||||
"rankings": SEOMetric(
|
||||
value=summary_data.get("position", 0),
|
||||
change=0, # Would calculate from historical data
|
||||
trend="up",
|
||||
description="Average ranking",
|
||||
color="#2196F3"
|
||||
),
|
||||
"mobile": SEOMetric(
|
||||
value=0, # Would get from performance data
|
||||
change=0,
|
||||
trend="stable",
|
||||
description="Mobile speed",
|
||||
color="#FF9800"
|
||||
),
|
||||
"keywords": SEOMetric(
|
||||
value=0, # Would count from query data
|
||||
change=0,
|
||||
trend="up",
|
||||
description="Keywords tracked",
|
||||
color="#9C27B0"
|
||||
)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error converting metrics: {e}")
|
||||
return {}
|
||||
|
||||
def _convert_platforms(platform_data: Dict[str, Any]) -> Dict[str, PlatformStatus]:
|
||||
"""Convert platform data to PlatformStatus format."""
|
||||
try:
|
||||
return {
|
||||
"google_search_console": PlatformStatus(
|
||||
status="connected" if platform_data.get("gsc", {}).get("connected", False) else "disconnected",
|
||||
connected=platform_data.get("gsc", {}).get("connected", False),
|
||||
last_sync=platform_data.get("gsc", {}).get("last_sync"),
|
||||
data_points=len(platform_data.get("gsc", {}).get("sites", []))
|
||||
),
|
||||
"bing_webmaster": PlatformStatus(
|
||||
status="connected" if platform_data.get("bing", {}).get("connected", False) else "disconnected",
|
||||
connected=platform_data.get("bing", {}).get("connected", False),
|
||||
last_sync=platform_data.get("bing", {}).get("last_sync"),
|
||||
data_points=len(platform_data.get("bing", {}).get("sites", []))
|
||||
)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error converting platforms: {e}")
|
||||
return {}
|
||||
@@ -11,8 +11,7 @@ from loguru import logger
|
||||
from functools import lru_cache
|
||||
|
||||
from services.database import get_db
|
||||
from services.usage_tracking_service import UsageTrackingService
|
||||
from services.pricing_service import PricingService
|
||||
from services.subscription import UsageTrackingService, PricingService
|
||||
from middleware.auth_middleware import get_current_user
|
||||
from models.subscription_models import (
|
||||
APIProvider, SubscriptionPlan, UserSubscription, UsageSummary,
|
||||
@@ -25,7 +24,7 @@ router = APIRouter(prefix="/api/subscription", tags=["subscription"])
|
||||
# Cache key: (user_id). TTL-like behavior implemented via timestamp check
|
||||
_dashboard_cache: Dict[str, Dict[str, Any]] = {}
|
||||
_dashboard_cache_ts: Dict[str, float] = {}
|
||||
_DASHBOARD_CACHE_TTL_SEC = 2.0
|
||||
_DASHBOARD_CACHE_TTL_SEC = 600.0
|
||||
|
||||
@router.get("/usage/{user_id}")
|
||||
async def get_user_usage(
|
||||
@@ -48,10 +47,9 @@ async def get_user_usage(
|
||||
"success": True,
|
||||
"data": stats
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user usage: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
raise HTTPException(status_code=500, detail="Failed to get user usage")
|
||||
|
||||
@router.get("/usage/{user_id}/trends")
|
||||
async def get_usage_trends(
|
||||
@@ -279,19 +277,29 @@ async def get_subscription_status(
|
||||
}
|
||||
}
|
||||
|
||||
# Check if subscription is within valid period
|
||||
# Check if subscription is within valid period; auto-advance if expired and auto_renew
|
||||
now = datetime.utcnow()
|
||||
if subscription.current_period_end < now:
|
||||
return {
|
||||
"success": True,
|
||||
"data": {
|
||||
"active": False,
|
||||
"plan": subscription.plan.tier.value,
|
||||
"tier": subscription.plan.tier.value,
|
||||
"can_use_api": False,
|
||||
"reason": "Subscription expired"
|
||||
if getattr(subscription, 'auto_renew', False):
|
||||
# advance period
|
||||
try:
|
||||
from services.pricing_service import PricingService
|
||||
pricing = PricingService(db)
|
||||
# reuse helper to ensure current
|
||||
pricing._ensure_subscription_current(subscription)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to auto-advance subscription: {e}")
|
||||
else:
|
||||
return {
|
||||
"success": True,
|
||||
"data": {
|
||||
"active": False,
|
||||
"plan": subscription.plan.tier.value,
|
||||
"tier": subscription.plan.tier.value,
|
||||
"can_use_api": False,
|
||||
"reason": "Subscription expired"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
@@ -544,7 +552,14 @@ async def get_dashboard_data(
|
||||
# Serve from short TTL cache to avoid hammering DB on bursts
|
||||
import time
|
||||
now = time.time()
|
||||
if user_id in _dashboard_cache and (now - _dashboard_cache_ts.get(user_id, 0)) < _DASHBOARD_CACHE_TTL_SEC:
|
||||
import os
|
||||
nocache = False
|
||||
try:
|
||||
# Not having direct access to request here; provide env flag override as simple control
|
||||
nocache = os.getenv('SUBSCRIPTION_DASHBOARD_NOCACHE', 'false').lower() in {'1','true','yes','on'}
|
||||
except Exception:
|
||||
nocache = False
|
||||
if not nocache and user_id in _dashboard_cache and (now - _dashboard_cache_ts.get(user_id, 0)) < _DASHBOARD_CACHE_TTL_SEC:
|
||||
return _dashboard_cache[user_id]
|
||||
|
||||
usage_service = UsageTrackingService(db)
|
||||
|
||||
Reference in New Issue
Block a user