ALwrity Version 0.5.0 (Fastapi + React )
This commit is contained in:
0
backend/api/content_planning/api/__init__.py
Normal file
0
backend/api/content_planning/api/__init__.py
Normal file
901
backend/api/content_planning/api/enhanced_strategy_routes.py
Normal file
901
backend/api/content_planning/api/enhanced_strategy_routes.py
Normal file
@@ -0,0 +1,901 @@
|
||||
"""
|
||||
Enhanced Strategy API Routes
|
||||
Handles API endpoints for enhanced content strategy functionality.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from fastapi.responses import StreamingResponse
|
||||
from sqlalchemy.orm import Session
|
||||
from loguru import logger
|
||||
import json
|
||||
import asyncio
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
import time
|
||||
|
||||
# Import database
|
||||
from services.database import get_db_session
|
||||
|
||||
# Import services
|
||||
from ..services.enhanced_strategy_service import EnhancedStrategyService
|
||||
from ..services.enhanced_strategy_db_service import EnhancedStrategyDBService
|
||||
|
||||
# Import models
|
||||
from models.enhanced_strategy_models import EnhancedContentStrategy
|
||||
|
||||
# Import utilities
|
||||
from ..utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ..utils.response_builders import ResponseBuilder
|
||||
from ..utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
router = APIRouter(tags=["Enhanced Strategy"])
|
||||
|
||||
# Cache for streaming endpoints (5 minutes cache)
|
||||
streaming_cache = defaultdict(dict)
|
||||
CACHE_DURATION = 300 # 5 minutes
|
||||
|
||||
def get_cached_data(cache_key: str) -> Optional[Dict[str, Any]]:
|
||||
"""Get cached data if it exists and is not expired."""
|
||||
if cache_key in streaming_cache:
|
||||
cached_data = streaming_cache[cache_key]
|
||||
if time.time() - cached_data.get("timestamp", 0) < CACHE_DURATION:
|
||||
return cached_data.get("data")
|
||||
return None
|
||||
|
||||
def set_cached_data(cache_key: str, data: Dict[str, Any]):
|
||||
"""Set cached data with timestamp."""
|
||||
streaming_cache[cache_key] = {
|
||||
"data": data,
|
||||
"timestamp": time.time()
|
||||
}
|
||||
|
||||
# Helper function to get database session
|
||||
def get_db():
|
||||
db = get_db_session()
|
||||
try:
|
||||
yield db
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
async def stream_data(data_generator):
|
||||
"""Helper function to stream data as Server-Sent Events"""
|
||||
async for chunk in data_generator:
|
||||
if isinstance(chunk, dict):
|
||||
yield f"data: {json.dumps(chunk)}\n\n"
|
||||
else:
|
||||
yield f"data: {json.dumps({'message': str(chunk)})}\n\n"
|
||||
await asyncio.sleep(0.1) # Small delay to prevent overwhelming
|
||||
|
||||
@router.get("/stream/strategies")
|
||||
async def stream_enhanced_strategies(
|
||||
user_id: Optional[int] = Query(None, description="User ID to filter strategies"),
|
||||
strategy_id: Optional[int] = Query(None, description="Specific strategy ID"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Stream enhanced strategies with real-time updates."""
|
||||
|
||||
async def strategy_generator():
|
||||
try:
|
||||
logger.info(f"🚀 Starting strategy stream for user: {user_id}, strategy: {strategy_id}")
|
||||
|
||||
# Send initial status
|
||||
yield {"type": "status", "message": "Starting strategy retrieval...", "timestamp": datetime.utcnow().isoformat()}
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
enhanced_service = EnhancedStrategyService(db_service)
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Querying database...", "progress": 25}
|
||||
|
||||
strategies_data = await enhanced_service.get_enhanced_strategies(user_id, strategy_id, db)
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Processing strategies...", "progress": 50}
|
||||
|
||||
if strategies_data.get("status") == "not_found":
|
||||
yield {"type": "result", "status": "not_found", "data": strategies_data}
|
||||
return
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Finalizing data...", "progress": 75}
|
||||
|
||||
# Send final result
|
||||
yield {"type": "result", "status": "success", "data": strategies_data, "progress": 100}
|
||||
|
||||
logger.info(f"✅ Strategy stream completed for user: {user_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error in strategy stream: {str(e)}")
|
||||
yield {"type": "error", "message": str(e), "timestamp": datetime.utcnow().isoformat()}
|
||||
|
||||
return StreamingResponse(
|
||||
stream_data(strategy_generator()),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||||
"Access-Control-Allow-Credentials": "true"
|
||||
}
|
||||
)
|
||||
|
||||
@router.get("/stream/strategic-intelligence")
|
||||
async def stream_strategic_intelligence(
|
||||
user_id: Optional[int] = Query(None, description="User ID"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Stream strategic intelligence data with real-time updates."""
|
||||
|
||||
async def intelligence_generator():
|
||||
try:
|
||||
logger.info(f"🚀 Starting strategic intelligence stream for user: {user_id}")
|
||||
|
||||
# Check cache first
|
||||
cache_key = f"strategic_intelligence_{user_id}"
|
||||
cached_data = get_cached_data(cache_key)
|
||||
if cached_data:
|
||||
logger.info(f"✅ Returning cached strategic intelligence data for user: {user_id}")
|
||||
yield {"type": "result", "status": "success", "data": cached_data, "progress": 100}
|
||||
return
|
||||
|
||||
# Send initial status
|
||||
yield {"type": "status", "message": "Loading strategic intelligence...", "timestamp": datetime.utcnow().isoformat()}
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
enhanced_service = EnhancedStrategyService(db_service)
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Retrieving strategies...", "progress": 20}
|
||||
|
||||
strategies_data = await enhanced_service.get_enhanced_strategies(user_id, None, db)
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Analyzing market positioning...", "progress": 40}
|
||||
|
||||
if strategies_data.get("status") == "not_found":
|
||||
# Send fallback data
|
||||
fallback_data = {
|
||||
"market_positioning": {
|
||||
"score": 75,
|
||||
"strengths": ["Strong brand voice", "Consistent content quality"],
|
||||
"weaknesses": ["Limited video content", "Slow content production"]
|
||||
},
|
||||
"competitive_advantages": [
|
||||
{"advantage": "AI-powered content creation", "impact": "High", "implementation": "In Progress"},
|
||||
{"advantage": "Data-driven strategy", "impact": "Medium", "implementation": "Complete"}
|
||||
],
|
||||
"strategic_risks": [
|
||||
{"risk": "Content saturation in market", "probability": "Medium", "impact": "High"},
|
||||
{"risk": "Algorithm changes affecting reach", "probability": "High", "impact": "Medium"}
|
||||
]
|
||||
}
|
||||
# Cache the fallback data
|
||||
set_cached_data(cache_key, fallback_data)
|
||||
yield {"type": "result", "status": "success", "data": fallback_data, "progress": 100}
|
||||
return
|
||||
|
||||
# Extract strategic intelligence from first strategy
|
||||
strategy = strategies_data.get("strategies", [{}])[0]
|
||||
|
||||
# Parse ai_recommendations if it's a JSON string
|
||||
ai_recommendations = {}
|
||||
if strategy.get("ai_recommendations"):
|
||||
try:
|
||||
if isinstance(strategy["ai_recommendations"], str):
|
||||
ai_recommendations = json.loads(strategy["ai_recommendations"])
|
||||
else:
|
||||
ai_recommendations = strategy["ai_recommendations"]
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
ai_recommendations = {}
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Extracting competitive analysis...", "progress": 60}
|
||||
|
||||
strategic_data = {
|
||||
"market_positioning": {
|
||||
"score": ai_recommendations.get("market_positioning", {}).get("score", 75),
|
||||
"strengths": ai_recommendations.get("market_positioning", {}).get("strengths", ["Strong brand voice", "Consistent content quality"]),
|
||||
"weaknesses": ai_recommendations.get("market_positioning", {}).get("weaknesses", ["Limited video content", "Slow content production"])
|
||||
},
|
||||
"competitive_advantages": ai_recommendations.get("competitive_advantages", [
|
||||
{"advantage": "AI-powered content creation", "impact": "High", "implementation": "In Progress"},
|
||||
{"advantage": "Data-driven strategy", "impact": "Medium", "implementation": "Complete"}
|
||||
]),
|
||||
"strategic_risks": ai_recommendations.get("strategic_risks", [
|
||||
{"risk": "Content saturation in market", "probability": "Medium", "impact": "High"},
|
||||
{"risk": "Algorithm changes affecting reach", "probability": "High", "impact": "Medium"}
|
||||
])
|
||||
}
|
||||
|
||||
# Cache the strategic data
|
||||
set_cached_data(cache_key, strategic_data)
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Finalizing intelligence data...", "progress": 80}
|
||||
|
||||
# Send final result
|
||||
yield {"type": "result", "status": "success", "data": strategic_data, "progress": 100}
|
||||
|
||||
logger.info(f"✅ Strategic intelligence stream completed for user: {user_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error in strategic intelligence stream: {str(e)}")
|
||||
yield {"type": "error", "message": str(e), "timestamp": datetime.utcnow().isoformat()}
|
||||
|
||||
return StreamingResponse(
|
||||
stream_data(intelligence_generator()),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||||
"Access-Control-Allow-Credentials": "true"
|
||||
}
|
||||
)
|
||||
|
||||
@router.get("/stream/keyword-research")
|
||||
async def stream_keyword_research(
|
||||
user_id: Optional[int] = Query(None, description="User ID"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Stream keyword research data with real-time updates."""
|
||||
|
||||
async def keyword_generator():
|
||||
try:
|
||||
logger.info(f"🚀 Starting keyword research stream for user: {user_id}")
|
||||
|
||||
# Check cache first
|
||||
cache_key = f"keyword_research_{user_id}"
|
||||
cached_data = get_cached_data(cache_key)
|
||||
if cached_data:
|
||||
logger.info(f"✅ Returning cached keyword research data for user: {user_id}")
|
||||
yield {"type": "result", "status": "success", "data": cached_data, "progress": 100}
|
||||
return
|
||||
|
||||
# Send initial status
|
||||
yield {"type": "status", "message": "Loading keyword research...", "timestamp": datetime.utcnow().isoformat()}
|
||||
|
||||
# Import gap analysis service
|
||||
from ..services.gap_analysis_service import GapAnalysisService
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Retrieving gap analyses...", "progress": 20}
|
||||
|
||||
gap_service = GapAnalysisService()
|
||||
gap_analyses = await gap_service.get_gap_analyses(user_id)
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Analyzing keyword opportunities...", "progress": 40}
|
||||
|
||||
# Handle case where gap_analyses is 0, None, or empty
|
||||
if not gap_analyses or gap_analyses == 0 or len(gap_analyses) == 0:
|
||||
# Send fallback data
|
||||
fallback_data = {
|
||||
"trend_analysis": {
|
||||
"high_volume_keywords": [
|
||||
{"keyword": "AI marketing automation", "volume": "10K-100K", "difficulty": "Medium"},
|
||||
{"keyword": "content strategy 2024", "volume": "1K-10K", "difficulty": "Low"},
|
||||
{"keyword": "digital marketing trends", "volume": "10K-100K", "difficulty": "High"}
|
||||
],
|
||||
"trending_keywords": [
|
||||
{"keyword": "AI content generation", "growth": "+45%", "opportunity": "High"},
|
||||
{"keyword": "voice search optimization", "growth": "+32%", "opportunity": "Medium"},
|
||||
{"keyword": "video marketing strategy", "growth": "+28%", "opportunity": "High"}
|
||||
]
|
||||
},
|
||||
"intent_analysis": {
|
||||
"informational": ["how to", "what is", "guide to"],
|
||||
"navigational": ["company name", "brand name", "website"],
|
||||
"transactional": ["buy", "purchase", "download", "sign up"]
|
||||
},
|
||||
"opportunities": [
|
||||
{"keyword": "AI content tools", "search_volume": "5K-10K", "competition": "Low", "cpc": "$2.50"},
|
||||
{"keyword": "content marketing ROI", "search_volume": "1K-5K", "competition": "Medium", "cpc": "$4.20"},
|
||||
{"keyword": "social media strategy", "search_volume": "10K-50K", "competition": "High", "cpc": "$3.80"}
|
||||
]
|
||||
}
|
||||
# Cache the fallback data
|
||||
set_cached_data(cache_key, fallback_data)
|
||||
yield {"type": "result", "status": "success", "data": fallback_data, "progress": 100}
|
||||
return
|
||||
|
||||
# Extract keyword data from first gap analysis
|
||||
gap_analysis = gap_analyses[0] if isinstance(gap_analyses, list) else gap_analyses
|
||||
|
||||
# Parse analysis_results if it's a JSON string
|
||||
analysis_results = {}
|
||||
if gap_analysis.get("analysis_results"):
|
||||
try:
|
||||
if isinstance(gap_analysis["analysis_results"], str):
|
||||
analysis_results = json.loads(gap_analysis["analysis_results"])
|
||||
else:
|
||||
analysis_results = gap_analysis["analysis_results"]
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
analysis_results = {}
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Processing keyword data...", "progress": 60}
|
||||
|
||||
keyword_data = {
|
||||
"trend_analysis": {
|
||||
"high_volume_keywords": analysis_results.get("opportunities", [])[:3] or [
|
||||
{"keyword": "AI marketing automation", "volume": "10K-100K", "difficulty": "Medium"},
|
||||
{"keyword": "content strategy 2024", "volume": "1K-10K", "difficulty": "Low"},
|
||||
{"keyword": "digital marketing trends", "volume": "10K-100K", "difficulty": "High"}
|
||||
],
|
||||
"trending_keywords": [
|
||||
{"keyword": "AI content generation", "growth": "+45%", "opportunity": "High"},
|
||||
{"keyword": "voice search optimization", "growth": "+32%", "opportunity": "Medium"},
|
||||
{"keyword": "video marketing strategy", "growth": "+28%", "opportunity": "High"}
|
||||
]
|
||||
},
|
||||
"intent_analysis": {
|
||||
"informational": ["how to", "what is", "guide to"],
|
||||
"navigational": ["company name", "brand name", "website"],
|
||||
"transactional": ["buy", "purchase", "download", "sign up"]
|
||||
},
|
||||
"opportunities": analysis_results.get("opportunities", []) or [
|
||||
{"keyword": "AI content tools", "search_volume": "5K-10K", "competition": "Low", "cpc": "$2.50"},
|
||||
{"keyword": "content marketing ROI", "search_volume": "1K-5K", "competition": "Medium", "cpc": "$4.20"},
|
||||
{"keyword": "social media strategy", "search_volume": "10K-50K", "competition": "High", "cpc": "$3.80"}
|
||||
]
|
||||
}
|
||||
|
||||
# Cache the keyword data
|
||||
set_cached_data(cache_key, keyword_data)
|
||||
|
||||
# Send progress update
|
||||
yield {"type": "progress", "message": "Finalizing keyword research...", "progress": 80}
|
||||
|
||||
# Send final result
|
||||
yield {"type": "result", "status": "success", "data": keyword_data, "progress": 100}
|
||||
|
||||
logger.info(f"✅ Keyword research stream completed for user: {user_id}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error in keyword research stream: {str(e)}")
|
||||
yield {"type": "error", "message": str(e), "timestamp": datetime.utcnow().isoformat()}
|
||||
|
||||
return StreamingResponse(
|
||||
stream_data(keyword_generator()),
|
||||
media_type="text/event-stream",
|
||||
headers={
|
||||
"Cache-Control": "no-cache",
|
||||
"Connection": "keep-alive",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
||||
"Access-Control-Allow-Credentials": "true"
|
||||
}
|
||||
)
|
||||
|
||||
@router.post("/create")
|
||||
async def create_enhanced_strategy(
|
||||
strategy_data: Dict[str, Any],
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a new enhanced content strategy with 30+ strategic inputs."""
|
||||
try:
|
||||
logger.info("🚀 Creating enhanced content strategy")
|
||||
|
||||
# Validate required fields
|
||||
if not strategy_data.get('user_id'):
|
||||
raise HTTPException(status_code=400, detail="user_id is required")
|
||||
|
||||
if not strategy_data.get('name'):
|
||||
raise HTTPException(status_code=400, detail="strategy name is required")
|
||||
|
||||
# Create enhanced strategy
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
enhanced_service = EnhancedStrategyService(db_service)
|
||||
created_strategy = await enhanced_service.create_enhanced_strategy(strategy_data, db)
|
||||
|
||||
logger.info(f"✅ Enhanced strategy created successfully: {created_strategy.get('id')}")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced content strategy created successfully",
|
||||
data=created_strategy
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error creating enhanced strategy: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "create_enhanced_strategy")
|
||||
|
||||
@router.get("/")
|
||||
async def get_enhanced_strategies(
|
||||
user_id: Optional[int] = Query(None, description="User ID to filter strategies"),
|
||||
strategy_id: Optional[int] = Query(None, description="Specific strategy ID"),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get enhanced content strategies with comprehensive data and AI recommendations."""
|
||||
try:
|
||||
logger.info(f"🚀 Getting enhanced strategies for user: {user_id}, strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
enhanced_service = EnhancedStrategyService(db_service)
|
||||
strategies_data = await enhanced_service.get_enhanced_strategies(user_id, strategy_id, db)
|
||||
|
||||
if strategies_data.get("status") == "not_found":
|
||||
return ResponseBuilder.create_not_found_response(
|
||||
message="No enhanced content strategies found",
|
||||
data=strategies_data
|
||||
)
|
||||
|
||||
logger.info(f"✅ Retrieved {strategies_data.get('total_count', 0)} enhanced strategies")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced content strategies retrieved successfully",
|
||||
data=strategies_data
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting enhanced strategies: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_enhanced_strategies")
|
||||
|
||||
@router.get("/onboarding-data")
|
||||
async def get_onboarding_data(
|
||||
user_id: Optional[int] = Query(None, description="User ID to get onboarding data for"),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get onboarding data for enhanced strategy auto-population."""
|
||||
try:
|
||||
logger.info(f"🚀 Getting onboarding data for user: {user_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
enhanced_service = EnhancedStrategyService(db_service)
|
||||
|
||||
# Ensure we have a valid user_id
|
||||
actual_user_id = user_id or 1
|
||||
onboarding_data = await enhanced_service._get_onboarding_data(actual_user_id)
|
||||
|
||||
logger.info(f"✅ Onboarding data retrieved successfully for user: {actual_user_id}")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Onboarding data retrieved successfully",
|
||||
data=onboarding_data
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting onboarding data: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_onboarding_data")
|
||||
|
||||
@router.get("/tooltips")
|
||||
async def get_enhanced_strategy_tooltips() -> Dict[str, Any]:
|
||||
"""Get tooltip data for enhanced strategy fields."""
|
||||
try:
|
||||
logger.info("🚀 Getting enhanced strategy tooltips")
|
||||
|
||||
# Mock tooltip data - in real implementation, this would come from a database
|
||||
tooltip_data = {
|
||||
"business_objectives": {
|
||||
"title": "Business Objectives",
|
||||
"description": "Define your primary and secondary business goals that content will support.",
|
||||
"examples": ["Increase brand awareness by 25%", "Generate 100 qualified leads per month"],
|
||||
"best_practices": ["Be specific and measurable", "Align with overall business strategy"]
|
||||
},
|
||||
"target_metrics": {
|
||||
"title": "Target Metrics",
|
||||
"description": "Specify the KPIs that will measure content strategy success.",
|
||||
"examples": ["Traffic growth: 30%", "Engagement rate: 5%", "Conversion rate: 2%"],
|
||||
"best_practices": ["Set realistic targets", "Track both leading and lagging indicators"]
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("✅ Enhanced strategy tooltips retrieved successfully")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced strategy tooltips retrieved successfully",
|
||||
data=tooltip_data
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting enhanced strategy tooltips: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_enhanced_strategy_tooltips")
|
||||
|
||||
@router.get("/disclosure-steps")
|
||||
async def get_enhanced_strategy_disclosure_steps() -> Dict[str, Any]:
|
||||
"""Get progressive disclosure steps for enhanced strategy."""
|
||||
try:
|
||||
logger.info("🚀 Getting enhanced strategy disclosure steps")
|
||||
|
||||
# Progressive disclosure steps configuration
|
||||
disclosure_steps = [
|
||||
{
|
||||
"id": "business_context",
|
||||
"title": "Business Context",
|
||||
"description": "Define your business objectives and context",
|
||||
"fields": ["business_objectives", "target_metrics", "content_budget", "team_size", "implementation_timeline", "market_share", "competitive_position", "performance_metrics"],
|
||||
"is_complete": False,
|
||||
"is_visible": True,
|
||||
"dependencies": []
|
||||
},
|
||||
{
|
||||
"id": "audience_intelligence",
|
||||
"title": "Audience Intelligence",
|
||||
"description": "Understand your target audience",
|
||||
"fields": ["content_preferences", "consumption_patterns", "audience_pain_points", "buying_journey", "seasonal_trends", "engagement_metrics"],
|
||||
"is_complete": False,
|
||||
"is_visible": False,
|
||||
"dependencies": ["business_context"]
|
||||
},
|
||||
{
|
||||
"id": "competitive_intelligence",
|
||||
"title": "Competitive Intelligence",
|
||||
"description": "Analyze your competitive landscape",
|
||||
"fields": ["top_competitors", "competitor_content_strategies", "market_gaps", "industry_trends", "emerging_trends"],
|
||||
"is_complete": False,
|
||||
"is_visible": False,
|
||||
"dependencies": ["audience_intelligence"]
|
||||
},
|
||||
{
|
||||
"id": "content_strategy",
|
||||
"title": "Content Strategy",
|
||||
"description": "Define your content approach",
|
||||
"fields": ["preferred_formats", "content_mix", "content_frequency", "optimal_timing", "quality_metrics", "editorial_guidelines", "brand_voice"],
|
||||
"is_complete": False,
|
||||
"is_visible": False,
|
||||
"dependencies": ["competitive_intelligence"]
|
||||
},
|
||||
{
|
||||
"id": "performance_analytics",
|
||||
"title": "Performance & Analytics",
|
||||
"description": "Set up measurement and optimization",
|
||||
"fields": ["traffic_sources", "conversion_rates", "content_roi_targets", "ab_testing_capabilities"],
|
||||
"is_complete": False,
|
||||
"is_visible": False,
|
||||
"dependencies": ["content_strategy"]
|
||||
}
|
||||
]
|
||||
|
||||
logger.info("✅ Enhanced strategy disclosure steps retrieved successfully")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced strategy disclosure steps retrieved successfully",
|
||||
data=disclosure_steps
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting enhanced strategy disclosure steps: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_enhanced_strategy_disclosure_steps")
|
||||
|
||||
@router.get("/{strategy_id}")
|
||||
async def get_enhanced_strategy_by_id(
|
||||
strategy_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get a specific enhanced content strategy by ID."""
|
||||
try:
|
||||
logger.info(f"🚀 Getting enhanced strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
strategy = await db_service.get_enhanced_strategy(strategy_id)
|
||||
|
||||
if not strategy:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Enhanced strategy", strategy_id)
|
||||
|
||||
# Get comprehensive data
|
||||
enhanced_service = EnhancedStrategyService(db_service)
|
||||
comprehensive_data = await enhanced_service.get_enhanced_strategies(
|
||||
strategy_id=strategy_id
|
||||
)
|
||||
|
||||
logger.info(f"✅ Enhanced strategy retrieved successfully: {strategy_id}")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced content strategy retrieved successfully",
|
||||
data=comprehensive_data.get("strategies", [{}])[0] if comprehensive_data.get("strategies") else {}
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting enhanced strategy: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_enhanced_strategy_by_id")
|
||||
|
||||
@router.put("/{strategy_id}")
|
||||
async def update_enhanced_strategy(
|
||||
strategy_id: int,
|
||||
update_data: Dict[str, Any],
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Update an enhanced content strategy."""
|
||||
try:
|
||||
logger.info(f"🚀 Updating enhanced strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
updated_strategy = await db_service.update_enhanced_strategy(strategy_id, update_data)
|
||||
|
||||
if not updated_strategy:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Enhanced strategy", strategy_id)
|
||||
|
||||
logger.info(f"✅ Enhanced strategy updated successfully: {strategy_id}")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced content strategy updated successfully",
|
||||
data=updated_strategy.to_dict()
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error updating enhanced strategy: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "update_enhanced_strategy")
|
||||
|
||||
@router.delete("/{strategy_id}")
|
||||
async def delete_enhanced_strategy(
|
||||
strategy_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Delete an enhanced content strategy."""
|
||||
try:
|
||||
logger.info(f"🚀 Deleting enhanced strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
deleted = await db_service.delete_enhanced_strategy(strategy_id)
|
||||
|
||||
if not deleted:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Enhanced strategy", strategy_id)
|
||||
|
||||
logger.info(f"✅ Enhanced strategy deleted successfully: {strategy_id}")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced content strategy deleted successfully",
|
||||
data={"strategy_id": strategy_id}
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error deleting enhanced strategy: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "delete_enhanced_strategy")
|
||||
|
||||
@router.get("/{strategy_id}/analytics")
|
||||
async def get_enhanced_strategy_analytics(
|
||||
strategy_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get comprehensive analytics for an enhanced strategy."""
|
||||
try:
|
||||
logger.info(f"🚀 Getting analytics for enhanced strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
|
||||
# Get strategy with analytics
|
||||
strategies_with_analytics = await db_service.get_enhanced_strategies_with_analytics(
|
||||
strategy_id=strategy_id
|
||||
)
|
||||
|
||||
if not strategies_with_analytics:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Enhanced strategy", strategy_id)
|
||||
|
||||
strategy_analytics = strategies_with_analytics[0]
|
||||
|
||||
logger.info(f"✅ Enhanced strategy analytics retrieved successfully: {strategy_id}")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced strategy analytics retrieved successfully",
|
||||
data=strategy_analytics
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting enhanced strategy analytics: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_enhanced_strategy_analytics")
|
||||
|
||||
@router.get("/{strategy_id}/ai-analyses")
|
||||
async def get_enhanced_strategy_ai_analysis(
|
||||
strategy_id: int,
|
||||
limit: int = Query(10, description="Number of AI analysis results to return"),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get AI analysis history for an enhanced strategy."""
|
||||
try:
|
||||
logger.info(f"🚀 Getting AI analysis for enhanced strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
|
||||
# Verify strategy exists
|
||||
strategy = await db_service.get_enhanced_strategy(strategy_id)
|
||||
if not strategy:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Enhanced strategy", strategy_id)
|
||||
|
||||
# Get AI analysis history
|
||||
ai_analysis_history = await db_service.get_ai_analysis_history(strategy_id, limit)
|
||||
|
||||
logger.info(f"✅ AI analysis history retrieved successfully: {strategy_id}")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced strategy AI analysis retrieved successfully",
|
||||
data={
|
||||
"strategy_id": strategy_id,
|
||||
"ai_analysis_history": ai_analysis_history,
|
||||
"total_analyses": len(ai_analysis_history)
|
||||
}
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting enhanced strategy AI analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_enhanced_strategy_ai_analysis")
|
||||
|
||||
@router.get("/{strategy_id}/completion")
|
||||
async def get_enhanced_strategy_completion_stats(
|
||||
strategy_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get completion statistics for an enhanced strategy."""
|
||||
try:
|
||||
logger.info(f"🚀 Getting completion stats for enhanced strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
|
||||
# Get strategy
|
||||
strategy = await db_service.get_enhanced_strategy(strategy_id)
|
||||
if not strategy:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Enhanced strategy", strategy_id)
|
||||
|
||||
# Calculate completion stats
|
||||
completion_stats = {
|
||||
"strategy_id": strategy_id,
|
||||
"completion_percentage": strategy.completion_percentage,
|
||||
"total_fields": 30, # 30+ strategic inputs
|
||||
"filled_fields": len([f for f in strategy.__dict__.keys() if getattr(strategy, f) is not None]),
|
||||
"missing_fields": 30 - len([f for f in strategy.__dict__.keys() if getattr(strategy, f) is not None]),
|
||||
"last_updated": strategy.updated_at.isoformat() if strategy.updated_at else None
|
||||
}
|
||||
|
||||
logger.info(f"✅ Completion stats retrieved successfully: {strategy_id}")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced strategy completion stats retrieved successfully",
|
||||
data=completion_stats
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting enhanced strategy completion stats: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_enhanced_strategy_completion_stats")
|
||||
|
||||
@router.get("/{strategy_id}/onboarding-integration")
|
||||
async def get_enhanced_strategy_onboarding_integration(
|
||||
strategy_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get onboarding data integration for an enhanced strategy."""
|
||||
try:
|
||||
logger.info(f"🚀 Getting onboarding integration for enhanced strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
onboarding_integration = await db_service.get_onboarding_integration(strategy_id)
|
||||
|
||||
if not onboarding_integration:
|
||||
return ResponseBuilder.create_not_found_response(
|
||||
message="No onboarding integration found for this strategy",
|
||||
data={"strategy_id": strategy_id}
|
||||
)
|
||||
|
||||
logger.info(f"✅ Onboarding integration retrieved successfully: {strategy_id}")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced strategy onboarding integration retrieved successfully",
|
||||
data=onboarding_integration
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting onboarding integration: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_enhanced_strategy_onboarding_integration")
|
||||
|
||||
@router.post("/cache/clear")
|
||||
async def clear_streaming_cache(
|
||||
user_id: Optional[int] = Query(None, description="User ID to clear cache for")
|
||||
):
|
||||
"""Clear streaming cache for a specific user or all users."""
|
||||
try:
|
||||
logger.info(f"🚀 Clearing streaming cache for user: {user_id}")
|
||||
|
||||
if user_id:
|
||||
# Clear cache for specific user
|
||||
cache_keys_to_remove = [
|
||||
f"strategic_intelligence_{user_id}",
|
||||
f"keyword_research_{user_id}"
|
||||
]
|
||||
for key in cache_keys_to_remove:
|
||||
if key in streaming_cache:
|
||||
del streaming_cache[key]
|
||||
logger.info(f"✅ Cleared cache for key: {key}")
|
||||
else:
|
||||
# Clear all cache
|
||||
streaming_cache.clear()
|
||||
logger.info("✅ Cleared all streaming cache")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Streaming cache cleared successfully",
|
||||
data={"cleared_for_user": user_id}
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error clearing streaming cache: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "clear_streaming_cache")
|
||||
|
||||
@router.post("/{strategy_id}/ai-recommendations")
|
||||
async def generate_enhanced_ai_recommendations(
|
||||
strategy_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Generate AI recommendations for an enhanced strategy."""
|
||||
try:
|
||||
logger.info(f"🚀 Generating AI recommendations for enhanced strategy: {strategy_id}")
|
||||
|
||||
# Get strategy
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
strategy = await db_service.get_enhanced_strategy(strategy_id)
|
||||
|
||||
if not strategy:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Enhanced strategy", strategy_id)
|
||||
|
||||
# Generate AI recommendations
|
||||
enhanced_service = EnhancedStrategyService(db_service)
|
||||
await enhanced_service._generate_comprehensive_ai_recommendations(strategy, db)
|
||||
|
||||
# Get updated strategy data
|
||||
updated_strategy = await db_service.get_enhanced_strategy(strategy_id)
|
||||
|
||||
logger.info(f"✅ AI recommendations generated successfully: {strategy_id}")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced strategy AI recommendations generated successfully",
|
||||
data=updated_strategy.to_dict()
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error generating AI recommendations: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "generate_enhanced_ai_recommendations")
|
||||
|
||||
@router.post("/{strategy_id}/ai-analysis/regenerate")
|
||||
async def regenerate_enhanced_strategy_ai_analysis(
|
||||
strategy_id: int,
|
||||
analysis_type: str,
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""Regenerate AI analysis for an enhanced strategy."""
|
||||
try:
|
||||
logger.info(f"🚀 Regenerating AI analysis for enhanced strategy: {strategy_id}, type: {analysis_type}")
|
||||
|
||||
# Get strategy
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
strategy = await db_service.get_enhanced_strategy(strategy_id)
|
||||
|
||||
if not strategy:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Enhanced strategy", strategy_id)
|
||||
|
||||
# Regenerate AI analysis
|
||||
enhanced_service = EnhancedStrategyService(db_service)
|
||||
await enhanced_service._generate_specialized_recommendations(strategy, analysis_type, db)
|
||||
|
||||
# Get updated strategy data
|
||||
updated_strategy = await db_service.get_enhanced_strategy(strategy_id)
|
||||
|
||||
logger.info(f"✅ AI analysis regenerated successfully: {strategy_id}")
|
||||
|
||||
return ResponseBuilder.create_success_response(
|
||||
message="Enhanced strategy AI analysis regenerated successfully",
|
||||
data=updated_strategy.to_dict()
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error regenerating AI analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "regenerate_enhanced_strategy_ai_analysis")
|
||||
0
backend/api/content_planning/api/models/__init__.py
Normal file
0
backend/api/content_planning/api/models/__init__.py
Normal file
104
backend/api/content_planning/api/models/requests.py
Normal file
104
backend/api/content_planning/api/models/requests.py
Normal file
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
Request Models for Content Planning API
|
||||
Extracted from the main content_planning.py file for better organization.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
# Content Strategy Request Models
|
||||
class ContentStrategyRequest(BaseModel):
|
||||
industry: str
|
||||
target_audience: Dict[str, Any]
|
||||
business_goals: List[str]
|
||||
content_preferences: Dict[str, Any]
|
||||
competitor_urls: Optional[List[str]] = None
|
||||
|
||||
class ContentStrategyCreate(BaseModel):
|
||||
user_id: int
|
||||
name: str
|
||||
industry: str
|
||||
target_audience: Dict[str, Any]
|
||||
content_pillars: Optional[List[Dict[str, Any]]] = None
|
||||
ai_recommendations: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Calendar Event Request Models
|
||||
class CalendarEventCreate(BaseModel):
|
||||
strategy_id: int
|
||||
title: str
|
||||
description: str
|
||||
content_type: str
|
||||
platform: str
|
||||
scheduled_date: datetime
|
||||
ai_recommendations: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Content Gap Analysis Request Models
|
||||
class ContentGapAnalysisCreate(BaseModel):
|
||||
user_id: int
|
||||
website_url: str
|
||||
competitor_urls: List[str]
|
||||
target_keywords: Optional[List[str]] = None
|
||||
industry: Optional[str] = None
|
||||
analysis_results: Optional[Dict[str, Any]] = None
|
||||
recommendations: Optional[Dict[str, Any]] = None
|
||||
opportunities: Optional[Dict[str, Any]] = None
|
||||
|
||||
class ContentGapAnalysisRequest(BaseModel):
|
||||
website_url: str
|
||||
competitor_urls: List[str]
|
||||
target_keywords: Optional[List[str]] = None
|
||||
industry: Optional[str] = None
|
||||
|
||||
# AI Analytics Request Models
|
||||
class ContentEvolutionRequest(BaseModel):
|
||||
strategy_id: int
|
||||
time_period: str = "30d" # 7d, 30d, 90d, 1y
|
||||
|
||||
class PerformanceTrendsRequest(BaseModel):
|
||||
strategy_id: int
|
||||
metrics: Optional[List[str]] = None
|
||||
|
||||
class ContentPerformancePredictionRequest(BaseModel):
|
||||
strategy_id: int
|
||||
content_data: Dict[str, Any]
|
||||
|
||||
class StrategicIntelligenceRequest(BaseModel):
|
||||
strategy_id: int
|
||||
market_data: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Calendar Generation Request Models
|
||||
class CalendarGenerationRequest(BaseModel):
|
||||
user_id: int
|
||||
strategy_id: Optional[int] = None
|
||||
calendar_type: str = Field("monthly", description="Type of calendar: monthly, weekly, custom")
|
||||
industry: Optional[str] = None
|
||||
business_size: str = Field("sme", description="Business size: startup, sme, enterprise")
|
||||
force_refresh: bool = Field(False, description="Force refresh calendar generation")
|
||||
|
||||
class ContentOptimizationRequest(BaseModel):
|
||||
user_id: int
|
||||
event_id: Optional[int] = None
|
||||
title: str
|
||||
description: str
|
||||
content_type: str
|
||||
target_platform: str
|
||||
original_content: Optional[Dict[str, Any]] = None
|
||||
|
||||
class PerformancePredictionRequest(BaseModel):
|
||||
user_id: int
|
||||
strategy_id: Optional[int] = None
|
||||
content_type: str
|
||||
platform: str
|
||||
content_data: Dict[str, Any]
|
||||
|
||||
class ContentRepurposingRequest(BaseModel):
|
||||
user_id: int
|
||||
strategy_id: Optional[int] = None
|
||||
original_content: Dict[str, Any]
|
||||
target_platforms: List[str]
|
||||
|
||||
class TrendingTopicsRequest(BaseModel):
|
||||
user_id: int
|
||||
industry: str
|
||||
limit: int = Field(10, description="Number of trending topics to return")
|
||||
135
backend/api/content_planning/api/models/responses.py
Normal file
135
backend/api/content_planning/api/models/responses.py
Normal file
@@ -0,0 +1,135 @@
|
||||
"""
|
||||
Response Models for Content Planning API
|
||||
Extracted from the main content_planning.py file for better organization.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
# Content Strategy Response Models
|
||||
class ContentStrategyResponse(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
industry: str
|
||||
target_audience: Dict[str, Any]
|
||||
content_pillars: List[Dict[str, Any]]
|
||||
ai_recommendations: Dict[str, Any]
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# Calendar Event Response Models
|
||||
class CalendarEventResponse(BaseModel):
|
||||
id: int
|
||||
strategy_id: int
|
||||
title: str
|
||||
description: str
|
||||
content_type: str
|
||||
platform: str
|
||||
scheduled_date: datetime
|
||||
status: str
|
||||
ai_recommendations: Optional[Dict[str, Any]] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
# Content Gap Analysis Response Models
|
||||
class ContentGapAnalysisResponse(BaseModel):
|
||||
id: int
|
||||
user_id: int
|
||||
website_url: str
|
||||
competitor_urls: List[str]
|
||||
target_keywords: Optional[List[str]] = None
|
||||
industry: Optional[str] = None
|
||||
analysis_results: Optional[Dict[str, Any]] = None
|
||||
recommendations: Optional[Dict[str, Any]] = None
|
||||
opportunities: Optional[Dict[str, Any]] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class ContentGapAnalysisFullResponse(BaseModel):
|
||||
website_analysis: Dict[str, Any]
|
||||
competitor_analysis: Dict[str, Any]
|
||||
gap_analysis: Dict[str, Any]
|
||||
recommendations: List[Dict[str, Any]]
|
||||
opportunities: List[Dict[str, Any]]
|
||||
created_at: datetime
|
||||
|
||||
# AI Analytics Response Models
|
||||
class AIAnalyticsResponse(BaseModel):
|
||||
analysis_type: str
|
||||
strategy_id: int
|
||||
results: Dict[str, Any]
|
||||
recommendations: List[Dict[str, Any]]
|
||||
analysis_date: datetime
|
||||
|
||||
# Calendar Generation Response Models
|
||||
class CalendarGenerationResponse(BaseModel):
|
||||
user_id: int
|
||||
strategy_id: Optional[int]
|
||||
calendar_type: str
|
||||
industry: str
|
||||
business_size: str
|
||||
generated_at: datetime
|
||||
content_pillars: List[str]
|
||||
platform_strategies: Dict[str, Any]
|
||||
content_mix: Dict[str, float]
|
||||
daily_schedule: List[Dict[str, Any]]
|
||||
weekly_themes: List[Dict[str, Any]]
|
||||
content_recommendations: List[Dict[str, Any]]
|
||||
optimal_timing: Dict[str, Any]
|
||||
performance_predictions: Dict[str, Any]
|
||||
trending_topics: List[Dict[str, Any]]
|
||||
repurposing_opportunities: List[Dict[str, Any]]
|
||||
ai_insights: List[Dict[str, Any]]
|
||||
competitor_analysis: Dict[str, Any]
|
||||
gap_analysis_insights: Dict[str, Any]
|
||||
strategy_insights: Dict[str, Any]
|
||||
onboarding_insights: Dict[str, Any]
|
||||
processing_time: float
|
||||
ai_confidence: float
|
||||
|
||||
class ContentOptimizationResponse(BaseModel):
|
||||
user_id: int
|
||||
event_id: Optional[int]
|
||||
original_content: Dict[str, Any]
|
||||
optimized_content: Dict[str, Any]
|
||||
platform_adaptations: List[str]
|
||||
visual_recommendations: List[str]
|
||||
hashtag_suggestions: List[str]
|
||||
keyword_optimization: Dict[str, Any]
|
||||
tone_adjustments: Dict[str, Any]
|
||||
length_optimization: Dict[str, Any]
|
||||
performance_prediction: Dict[str, Any]
|
||||
optimization_score: float
|
||||
created_at: datetime
|
||||
|
||||
class PerformancePredictionResponse(BaseModel):
|
||||
user_id: int
|
||||
strategy_id: Optional[int]
|
||||
content_type: str
|
||||
platform: str
|
||||
predicted_engagement_rate: float
|
||||
predicted_reach: int
|
||||
predicted_conversions: int
|
||||
predicted_roi: float
|
||||
confidence_score: float
|
||||
recommendations: List[str]
|
||||
created_at: datetime
|
||||
|
||||
class ContentRepurposingResponse(BaseModel):
|
||||
user_id: int
|
||||
strategy_id: Optional[int]
|
||||
original_content: Dict[str, Any]
|
||||
platform_adaptations: List[Dict[str, Any]]
|
||||
transformations: List[Dict[str, Any]]
|
||||
implementation_tips: List[str]
|
||||
gap_addresses: List[str]
|
||||
created_at: datetime
|
||||
|
||||
class TrendingTopicsResponse(BaseModel):
|
||||
user_id: int
|
||||
industry: str
|
||||
trending_topics: List[Dict[str, Any]]
|
||||
gap_relevance_scores: Dict[str, float]
|
||||
audience_alignment_scores: Dict[str, float]
|
||||
created_at: datetime
|
||||
70
backend/api/content_planning/api/router.py
Normal file
70
backend/api/content_planning/api/router.py
Normal file
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
Main Router for Content Planning API
|
||||
Centralized router that includes all sub-routes for the content planning module.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends, status
|
||||
from typing import Dict, Any
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
|
||||
# Import route modules
|
||||
from .routes import strategies, calendar_events, gap_analysis, ai_analytics, calendar_generation, health_monitoring
|
||||
|
||||
# Import enhanced strategy routes
|
||||
from .enhanced_strategy_routes import router as enhanced_strategy_router
|
||||
|
||||
# Create main router
|
||||
router = APIRouter(prefix="/api/content-planning", tags=["content-planning"])
|
||||
|
||||
# Include route modules
|
||||
router.include_router(strategies.router)
|
||||
router.include_router(calendar_events.router)
|
||||
router.include_router(gap_analysis.router)
|
||||
router.include_router(ai_analytics.router)
|
||||
router.include_router(calendar_generation.router)
|
||||
router.include_router(health_monitoring.router)
|
||||
|
||||
# Include enhanced strategy routes with correct prefix
|
||||
router.include_router(enhanced_strategy_router, prefix="/enhanced-strategies")
|
||||
|
||||
# Add health check endpoint
|
||||
@router.get("/health")
|
||||
async def content_planning_health_check():
|
||||
"""
|
||||
Health check for content planning module.
|
||||
Returns operational status of all sub-modules.
|
||||
"""
|
||||
try:
|
||||
logger.info("🏥 Performing content planning health check")
|
||||
|
||||
health_status = {
|
||||
"service": "content_planning",
|
||||
"status": "healthy",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"modules": {
|
||||
"strategies": "operational",
|
||||
"calendar_events": "operational",
|
||||
"gap_analysis": "operational",
|
||||
"ai_analytics": "operational",
|
||||
"calendar_generation": "operational",
|
||||
"health_monitoring": "operational",
|
||||
"enhanced_strategies": "operational",
|
||||
"models": "operational",
|
||||
"utils": "operational"
|
||||
},
|
||||
"version": "2.0.0",
|
||||
"architecture": "modular"
|
||||
}
|
||||
|
||||
logger.info("✅ Content planning health check completed")
|
||||
return health_status
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Content planning health check failed: {str(e)}")
|
||||
return {
|
||||
"service": "content_planning",
|
||||
"status": "unhealthy",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"error": str(e)
|
||||
}
|
||||
0
backend/api/content_planning/api/routes/__init__.py
Normal file
0
backend/api/content_planning/api/routes/__init__.py
Normal file
265
backend/api/content_planning/api/routes/ai_analytics.py
Normal file
265
backend/api/content_planning/api/routes/ai_analytics.py
Normal file
@@ -0,0 +1,265 @@
|
||||
"""
|
||||
AI Analytics Routes for Content Planning API
|
||||
Extracted from the main content_planning.py file for better organization.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
import json
|
||||
import time
|
||||
|
||||
# Import database service
|
||||
from services.database import get_db_session, get_db
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
|
||||
# Import models
|
||||
from ..models.requests import (
|
||||
ContentEvolutionRequest, PerformanceTrendsRequest,
|
||||
ContentPerformancePredictionRequest, StrategicIntelligenceRequest
|
||||
)
|
||||
from ..models.responses import AIAnalyticsResponse
|
||||
|
||||
# Import utilities
|
||||
from ...utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ...utils.response_builders import ResponseBuilder
|
||||
from ...utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
# Import services
|
||||
from ...services.ai_analytics_service import ContentPlanningAIAnalyticsService
|
||||
|
||||
# Initialize services
|
||||
ai_analytics_service = ContentPlanningAIAnalyticsService()
|
||||
|
||||
# Create router
|
||||
router = APIRouter(prefix="/ai-analytics", tags=["ai-analytics"])
|
||||
|
||||
@router.post("/content-evolution", response_model=AIAnalyticsResponse)
|
||||
async def analyze_content_evolution(request: ContentEvolutionRequest):
|
||||
"""
|
||||
Analyze content evolution over time for a specific strategy.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starting content evolution analysis for strategy {request.strategy_id}")
|
||||
|
||||
result = await ai_analytics_service.analyze_content_evolution(
|
||||
strategy_id=request.strategy_id,
|
||||
time_period=request.time_period
|
||||
)
|
||||
|
||||
return AIAnalyticsResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error analyzing content evolution: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error analyzing content evolution: {str(e)}"
|
||||
)
|
||||
|
||||
@router.post("/performance-trends", response_model=AIAnalyticsResponse)
|
||||
async def analyze_performance_trends(request: PerformanceTrendsRequest):
|
||||
"""
|
||||
Analyze performance trends for content strategy.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starting performance trends analysis for strategy {request.strategy_id}")
|
||||
|
||||
result = await ai_analytics_service.analyze_performance_trends(
|
||||
strategy_id=request.strategy_id,
|
||||
metrics=request.metrics
|
||||
)
|
||||
|
||||
return AIAnalyticsResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error analyzing performance trends: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error analyzing performance trends: {str(e)}"
|
||||
)
|
||||
|
||||
@router.post("/predict-performance", response_model=AIAnalyticsResponse)
|
||||
async def predict_content_performance(request: ContentPerformancePredictionRequest):
|
||||
"""
|
||||
Predict content performance using AI models.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starting content performance prediction for strategy {request.strategy_id}")
|
||||
|
||||
result = await ai_analytics_service.predict_content_performance(
|
||||
strategy_id=request.strategy_id,
|
||||
content_data=request.content_data
|
||||
)
|
||||
|
||||
return AIAnalyticsResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error predicting content performance: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error predicting content performance: {str(e)}"
|
||||
)
|
||||
|
||||
@router.post("/strategic-intelligence", response_model=AIAnalyticsResponse)
|
||||
async def generate_strategic_intelligence(request: StrategicIntelligenceRequest):
|
||||
"""
|
||||
Generate strategic intelligence for content planning.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starting strategic intelligence generation for strategy {request.strategy_id}")
|
||||
|
||||
result = await ai_analytics_service.generate_strategic_intelligence(
|
||||
strategy_id=request.strategy_id,
|
||||
market_data=request.market_data
|
||||
)
|
||||
|
||||
return AIAnalyticsResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating strategic intelligence: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error generating strategic intelligence: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/", response_model=Dict[str, Any])
|
||||
async def get_ai_analytics(
|
||||
user_id: Optional[int] = Query(None, description="User ID"),
|
||||
strategy_id: Optional[int] = Query(None, description="Strategy ID"),
|
||||
force_refresh: bool = Query(False, description="Force refresh AI analysis")
|
||||
):
|
||||
"""Get AI analytics with real personalized insights - Database first approach."""
|
||||
try:
|
||||
logger.info(f"🚀 Starting AI analytics for user: {user_id}, strategy: {strategy_id}, force_refresh: {force_refresh}")
|
||||
|
||||
result = await ai_analytics_service.get_ai_analytics(user_id, strategy_id, force_refresh)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error generating AI analytics: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Error generating AI analytics: {str(e)}")
|
||||
|
||||
@router.get("/health")
|
||||
async def ai_analytics_health_check():
|
||||
"""
|
||||
Health check for AI analytics services.
|
||||
"""
|
||||
try:
|
||||
# Check AI analytics service
|
||||
service_status = {}
|
||||
|
||||
# Test AI analytics service
|
||||
try:
|
||||
# Test with a simple operation that doesn't require data
|
||||
# Just check if the service can be instantiated
|
||||
test_service = ContentPlanningAIAnalyticsService()
|
||||
service_status['ai_analytics_service'] = 'operational'
|
||||
except Exception as e:
|
||||
service_status['ai_analytics_service'] = f'error: {str(e)}'
|
||||
|
||||
# Determine overall status
|
||||
operational_services = sum(1 for status in service_status.values() if status == 'operational')
|
||||
total_services = len(service_status)
|
||||
|
||||
overall_status = 'healthy' if operational_services == total_services else 'degraded'
|
||||
|
||||
health_status = {
|
||||
'status': overall_status,
|
||||
'services': service_status,
|
||||
'operational_services': operational_services,
|
||||
'total_services': total_services,
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
return health_status
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"AI analytics health check failed: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"AI analytics health check failed: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/results/{user_id}")
|
||||
async def get_user_ai_analysis_results(
|
||||
user_id: int,
|
||||
analysis_type: Optional[str] = Query(None, description="Filter by analysis type"),
|
||||
limit: int = Query(10, description="Number of results to return")
|
||||
):
|
||||
"""Get AI analysis results for a specific user."""
|
||||
try:
|
||||
logger.info(f"Fetching AI analysis results for user {user_id}")
|
||||
|
||||
result = await ai_analytics_service.get_user_ai_analysis_results(
|
||||
user_id=user_id,
|
||||
analysis_type=analysis_type,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching AI analysis results: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
@router.post("/refresh/{user_id}")
|
||||
async def refresh_ai_analysis(
|
||||
user_id: int,
|
||||
analysis_type: str = Query(..., description="Type of analysis to refresh"),
|
||||
strategy_id: Optional[int] = Query(None, description="Strategy ID")
|
||||
):
|
||||
"""Force refresh of AI analysis for a user."""
|
||||
try:
|
||||
logger.info(f"Force refreshing AI analysis for user {user_id}, type: {analysis_type}")
|
||||
|
||||
result = await ai_analytics_service.refresh_ai_analysis(
|
||||
user_id=user_id,
|
||||
analysis_type=analysis_type,
|
||||
strategy_id=strategy_id
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error refreshing AI analysis: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
@router.delete("/cache/{user_id}")
|
||||
async def clear_ai_analysis_cache(
|
||||
user_id: int,
|
||||
analysis_type: Optional[str] = Query(None, description="Specific analysis type to clear")
|
||||
):
|
||||
"""Clear AI analysis cache for a user."""
|
||||
try:
|
||||
logger.info(f"Clearing AI analysis cache for user {user_id}")
|
||||
|
||||
result = await ai_analytics_service.clear_ai_analysis_cache(
|
||||
user_id=user_id,
|
||||
analysis_type=analysis_type
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error clearing AI analysis cache: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
@router.get("/statistics")
|
||||
async def get_ai_analysis_statistics(
|
||||
user_id: Optional[int] = Query(None, description="User ID for user-specific stats")
|
||||
):
|
||||
"""Get AI analysis statistics."""
|
||||
try:
|
||||
logger.info(f"📊 Getting AI analysis statistics for user: {user_id}")
|
||||
|
||||
result = await ai_analytics_service.get_ai_analysis_statistics(user_id)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting AI analysis statistics: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to get AI analysis statistics: {str(e)}"
|
||||
)
|
||||
170
backend/api/content_planning/api/routes/calendar_events.py
Normal file
170
backend/api/content_planning/api/routes/calendar_events.py
Normal file
@@ -0,0 +1,170 @@
|
||||
"""
|
||||
Calendar Events Routes for Content Planning API
|
||||
Extracted from the main content_planning.py file for better organization.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
|
||||
# Import database service
|
||||
from services.database import get_db_session, get_db
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
|
||||
# Import models
|
||||
from ..models.requests import CalendarEventCreate
|
||||
from ..models.responses import CalendarEventResponse
|
||||
|
||||
# Import utilities
|
||||
from ...utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ...utils.response_builders import ResponseBuilder
|
||||
from ...utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
# Import services
|
||||
from ...services.calendar_service import CalendarService
|
||||
|
||||
# Initialize services
|
||||
calendar_service = CalendarService()
|
||||
|
||||
# Create router
|
||||
router = APIRouter(prefix="/calendar-events", tags=["calendar-events"])
|
||||
|
||||
@router.post("/", response_model=CalendarEventResponse)
|
||||
async def create_calendar_event(
|
||||
event: CalendarEventCreate,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create a new calendar event."""
|
||||
try:
|
||||
logger.info(f"Creating calendar event: {event.title}")
|
||||
|
||||
event_data = event.dict()
|
||||
created_event = await calendar_service.create_calendar_event(event_data, db)
|
||||
|
||||
return CalendarEventResponse(**created_event)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating calendar event: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "create_calendar_event")
|
||||
|
||||
@router.get("/", response_model=List[CalendarEventResponse])
|
||||
async def get_calendar_events(
|
||||
strategy_id: Optional[int] = Query(None, description="Filter by strategy ID"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get calendar events, optionally filtered by strategy."""
|
||||
try:
|
||||
logger.info("Fetching calendar events")
|
||||
|
||||
events = await calendar_service.get_calendar_events(strategy_id, db)
|
||||
return [CalendarEventResponse(**event) for event in events]
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting calendar events: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_calendar_events")
|
||||
|
||||
@router.get("/{event_id}", response_model=CalendarEventResponse)
|
||||
async def get_calendar_event(
|
||||
event_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a specific calendar event by ID."""
|
||||
try:
|
||||
logger.info(f"Fetching calendar event: {event_id}")
|
||||
|
||||
event = await calendar_service.get_calendar_event_by_id(event_id, db)
|
||||
return CalendarEventResponse(**event)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting calendar event: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_calendar_event")
|
||||
|
||||
@router.put("/{event_id}", response_model=CalendarEventResponse)
|
||||
async def update_calendar_event(
|
||||
event_id: int,
|
||||
update_data: Dict[str, Any],
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update a calendar event."""
|
||||
try:
|
||||
logger.info(f"Updating calendar event: {event_id}")
|
||||
|
||||
updated_event = await calendar_service.update_calendar_event(event_id, update_data, db)
|
||||
return CalendarEventResponse(**updated_event)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating calendar event: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "update_calendar_event")
|
||||
|
||||
@router.delete("/{event_id}")
|
||||
async def delete_calendar_event(
|
||||
event_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Delete a calendar event."""
|
||||
try:
|
||||
logger.info(f"Deleting calendar event: {event_id}")
|
||||
|
||||
deleted = await calendar_service.delete_calendar_event(event_id, db)
|
||||
|
||||
if deleted:
|
||||
return {"message": f"Calendar event {event_id} deleted successfully"}
|
||||
else:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Calendar event", event_id)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting calendar event: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "delete_calendar_event")
|
||||
|
||||
@router.post("/schedule", response_model=Dict[str, Any])
|
||||
async def schedule_calendar_event(
|
||||
event: CalendarEventCreate,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Schedule a calendar event with conflict checking."""
|
||||
try:
|
||||
logger.info(f"Scheduling calendar event: {event.title}")
|
||||
|
||||
event_data = event.dict()
|
||||
result = await calendar_service.schedule_event(event_data, db)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error scheduling calendar event: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "schedule_calendar_event")
|
||||
|
||||
@router.get("/strategy/{strategy_id}/events")
|
||||
async def get_strategy_events(
|
||||
strategy_id: int,
|
||||
status: Optional[str] = Query(None, description="Filter by event status"),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get calendar events for a specific strategy."""
|
||||
try:
|
||||
logger.info(f"Fetching events for strategy: {strategy_id}")
|
||||
|
||||
if status:
|
||||
events = await calendar_service.get_events_by_status(strategy_id, status, db)
|
||||
return {
|
||||
'strategy_id': strategy_id,
|
||||
'status': status,
|
||||
'events_count': len(events),
|
||||
'events': events
|
||||
}
|
||||
else:
|
||||
result = await calendar_service.get_strategy_events(strategy_id, db)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting strategy events: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
247
backend/api/content_planning/api/routes/calendar_generation.py
Normal file
247
backend/api/content_planning/api/routes/calendar_generation.py
Normal file
@@ -0,0 +1,247 @@
|
||||
"""
|
||||
Calendar Generation Routes for Content Planning API
|
||||
Extracted from the main content_planning.py file for better organization.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
import time
|
||||
|
||||
# Import database service
|
||||
from services.database import get_db_session, get_db
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
|
||||
# Import models
|
||||
from ..models.requests import (
|
||||
CalendarGenerationRequest, ContentOptimizationRequest,
|
||||
PerformancePredictionRequest, ContentRepurposingRequest,
|
||||
TrendingTopicsRequest
|
||||
)
|
||||
from ..models.responses import (
|
||||
CalendarGenerationResponse, ContentOptimizationResponse,
|
||||
PerformancePredictionResponse, ContentRepurposingResponse,
|
||||
TrendingTopicsResponse
|
||||
)
|
||||
|
||||
# Import utilities
|
||||
from ...utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ...utils.response_builders import ResponseBuilder
|
||||
from ...utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
# Import services
|
||||
from ...services.calendar_generation_service import CalendarGenerationService
|
||||
|
||||
# Initialize services
|
||||
calendar_generation_service = CalendarGenerationService()
|
||||
|
||||
# Create router
|
||||
router = APIRouter(prefix="/calendar-generation", tags=["calendar-generation"])
|
||||
|
||||
@router.post("/generate-calendar", response_model=CalendarGenerationResponse)
|
||||
async def generate_comprehensive_calendar(request: CalendarGenerationRequest):
|
||||
"""
|
||||
Generate a comprehensive AI-powered content calendar using database insights.
|
||||
This endpoint uses advanced AI analysis and comprehensive user data.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"🎯 Generating comprehensive calendar for user {request.user_id}")
|
||||
|
||||
calendar_data = await calendar_generation_service.generate_comprehensive_calendar(
|
||||
user_id=request.user_id,
|
||||
strategy_id=request.strategy_id,
|
||||
calendar_type=request.calendar_type,
|
||||
industry=request.industry,
|
||||
business_size=request.business_size
|
||||
)
|
||||
|
||||
return CalendarGenerationResponse(**calendar_data)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error generating comprehensive calendar: {str(e)}")
|
||||
logger.error(f"Exception type: {type(e)}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error generating comprehensive calendar: {str(e)}"
|
||||
)
|
||||
|
||||
@router.post("/optimize-content", response_model=ContentOptimizationResponse)
|
||||
async def optimize_content_for_platform(request: ContentOptimizationRequest):
|
||||
"""
|
||||
Optimize content for specific platforms using database insights.
|
||||
|
||||
This endpoint optimizes content based on:
|
||||
- Historical performance data for the platform
|
||||
- Audience preferences from onboarding data
|
||||
- Gap analysis insights for content improvement
|
||||
- Competitor analysis for differentiation
|
||||
"""
|
||||
try:
|
||||
logger.info(f"🔧 Starting content optimization for user {request.user_id}")
|
||||
|
||||
result = await calendar_generation_service.optimize_content_for_platform(
|
||||
user_id=request.user_id,
|
||||
title=request.title,
|
||||
description=request.description,
|
||||
content_type=request.content_type,
|
||||
target_platform=request.target_platform,
|
||||
event_id=request.event_id
|
||||
)
|
||||
|
||||
return ContentOptimizationResponse(**result)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error optimizing content: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to optimize content: {str(e)}"
|
||||
)
|
||||
|
||||
@router.post("/performance-predictions", response_model=PerformancePredictionResponse)
|
||||
async def predict_content_performance(request: PerformancePredictionRequest):
|
||||
"""
|
||||
Predict content performance using database insights.
|
||||
|
||||
This endpoint predicts performance based on:
|
||||
- Historical performance data
|
||||
- Audience demographics and preferences
|
||||
- Content type and platform patterns
|
||||
- Gap analysis opportunities
|
||||
"""
|
||||
try:
|
||||
logger.info(f"📊 Starting performance prediction for user {request.user_id}")
|
||||
|
||||
result = await calendar_generation_service.predict_content_performance(
|
||||
user_id=request.user_id,
|
||||
content_type=request.content_type,
|
||||
platform=request.platform,
|
||||
content_data=request.content_data,
|
||||
strategy_id=request.strategy_id
|
||||
)
|
||||
|
||||
return PerformancePredictionResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error predicting content performance: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to predict content performance: {str(e)}"
|
||||
)
|
||||
|
||||
@router.post("/repurpose-content", response_model=ContentRepurposingResponse)
|
||||
async def repurpose_content_across_platforms(request: ContentRepurposingRequest):
|
||||
"""
|
||||
Repurpose content across different platforms using database insights.
|
||||
|
||||
This endpoint suggests content repurposing based on:
|
||||
- Existing content and strategy data
|
||||
- Gap analysis opportunities
|
||||
- Platform-specific requirements
|
||||
- Audience preferences
|
||||
"""
|
||||
try:
|
||||
logger.info(f"🔄 Starting content repurposing for user {request.user_id}")
|
||||
|
||||
result = await calendar_generation_service.repurpose_content_across_platforms(
|
||||
user_id=request.user_id,
|
||||
original_content=request.original_content,
|
||||
target_platforms=request.target_platforms,
|
||||
strategy_id=request.strategy_id
|
||||
)
|
||||
|
||||
return ContentRepurposingResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error repurposing content: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to repurpose content: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/trending-topics", response_model=TrendingTopicsResponse)
|
||||
async def get_trending_topics(
|
||||
user_id: int = Query(..., description="User ID"),
|
||||
industry: str = Query(..., description="Industry for trending topics"),
|
||||
limit: int = Query(10, description="Number of trending topics to return")
|
||||
):
|
||||
"""
|
||||
Get trending topics relevant to the user's industry and content gaps.
|
||||
|
||||
This endpoint provides trending topics based on:
|
||||
- Industry-specific trends
|
||||
- Gap analysis keyword opportunities
|
||||
- Audience alignment assessment
|
||||
- Competitor analysis insights
|
||||
"""
|
||||
try:
|
||||
logger.info(f"📈 Getting trending topics for user {user_id} in {industry}")
|
||||
|
||||
result = await calendar_generation_service.get_trending_topics(
|
||||
user_id=user_id,
|
||||
industry=industry,
|
||||
limit=limit
|
||||
)
|
||||
|
||||
return TrendingTopicsResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting trending topics: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Failed to get trending topics: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/comprehensive-user-data")
|
||||
async def get_comprehensive_user_data(
|
||||
user_id: int = Query(..., description="User ID"),
|
||||
db: Session = Depends(get_db)
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get comprehensive user data for calendar generation.
|
||||
This endpoint aggregates all data points needed for the calendar wizard.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Getting comprehensive user data for user_id: {user_id}")
|
||||
|
||||
result = await calendar_generation_service.get_comprehensive_user_data(user_id)
|
||||
|
||||
logger.info(f"Successfully retrieved comprehensive user data for user_id: {user_id}")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting comprehensive user data for user_id {user_id}: {str(e)}")
|
||||
logger.error(f"Exception type: {type(e)}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error retrieving comprehensive user data: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/health")
|
||||
async def calendar_generation_health_check():
|
||||
"""
|
||||
Health check for calendar generation services.
|
||||
"""
|
||||
try:
|
||||
logger.info("🏥 Performing calendar generation health check")
|
||||
|
||||
result = await calendar_generation_service.health_check()
|
||||
|
||||
logger.info("✅ Calendar generation health check completed")
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Calendar generation health check failed: {str(e)}")
|
||||
return {
|
||||
"service": "calendar_generation",
|
||||
"status": "unhealthy",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"error": str(e)
|
||||
}
|
||||
169
backend/api/content_planning/api/routes/gap_analysis.py
Normal file
169
backend/api/content_planning/api/routes/gap_analysis.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""
|
||||
Gap Analysis Routes for Content Planning API
|
||||
Extracted from the main content_planning.py file for better organization.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
import json
|
||||
|
||||
# Import database service
|
||||
from services.database import get_db_session, get_db
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
|
||||
# Import models
|
||||
from ..models.requests import ContentGapAnalysisCreate, ContentGapAnalysisRequest
|
||||
from ..models.responses import ContentGapAnalysisResponse, ContentGapAnalysisFullResponse
|
||||
|
||||
# Import utilities
|
||||
from ...utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ...utils.response_builders import ResponseBuilder
|
||||
from ...utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
# Import services
|
||||
from ...services.gap_analysis_service import GapAnalysisService
|
||||
|
||||
# Initialize services
|
||||
gap_analysis_service = GapAnalysisService()
|
||||
|
||||
# Create router
|
||||
router = APIRouter(prefix="/gap-analysis", tags=["gap-analysis"])
|
||||
|
||||
@router.post("/", response_model=ContentGapAnalysisResponse)
|
||||
async def create_content_gap_analysis(
|
||||
analysis: ContentGapAnalysisCreate,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create a new content gap analysis."""
|
||||
try:
|
||||
logger.info(f"Creating content gap analysis for: {analysis.website_url}")
|
||||
|
||||
analysis_data = analysis.dict()
|
||||
created_analysis = await gap_analysis_service.create_gap_analysis(analysis_data, db)
|
||||
|
||||
return ContentGapAnalysisResponse(**created_analysis)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating content gap analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "create_content_gap_analysis")
|
||||
|
||||
@router.get("/", response_model=Dict[str, Any])
|
||||
async def get_content_gap_analyses(
|
||||
user_id: Optional[int] = Query(None, description="User ID"),
|
||||
strategy_id: Optional[int] = Query(None, description="Strategy ID"),
|
||||
force_refresh: bool = Query(False, description="Force refresh gap analysis")
|
||||
):
|
||||
"""Get content gap analysis with real AI insights - Database first approach."""
|
||||
try:
|
||||
logger.info(f"🚀 Starting content gap analysis for user: {user_id}, strategy: {strategy_id}, force_refresh: {force_refresh}")
|
||||
|
||||
result = await gap_analysis_service.get_gap_analyses(user_id, strategy_id, force_refresh)
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error generating content gap analysis: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Error generating content gap analysis: {str(e)}")
|
||||
|
||||
@router.get("/{analysis_id}", response_model=ContentGapAnalysisResponse)
|
||||
async def get_content_gap_analysis(
|
||||
analysis_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a specific content gap analysis by ID."""
|
||||
try:
|
||||
logger.info(f"Fetching content gap analysis: {analysis_id}")
|
||||
|
||||
analysis = await gap_analysis_service.get_gap_analysis_by_id(analysis_id, db)
|
||||
return ContentGapAnalysisResponse(**analysis)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting content gap analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_content_gap_analysis")
|
||||
|
||||
@router.post("/analyze", response_model=ContentGapAnalysisFullResponse)
|
||||
async def analyze_content_gaps(request: ContentGapAnalysisRequest):
|
||||
"""
|
||||
Analyze content gaps between your website and competitors.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"Starting content gap analysis for: {request.website_url}")
|
||||
|
||||
request_data = request.dict()
|
||||
result = await gap_analysis_service.analyze_content_gaps(request_data)
|
||||
|
||||
return ContentGapAnalysisFullResponse(**result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error analyzing content gaps: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error analyzing content gaps: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/user/{user_id}/analyses")
|
||||
async def get_user_gap_analyses(
|
||||
user_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get all gap analyses for a specific user."""
|
||||
try:
|
||||
logger.info(f"Fetching gap analyses for user: {user_id}")
|
||||
|
||||
analyses = await gap_analysis_service.get_user_gap_analyses(user_id, db)
|
||||
return {
|
||||
"user_id": user_id,
|
||||
"analyses": analyses,
|
||||
"total_count": len(analyses)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user gap analyses: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_user_gap_analyses")
|
||||
|
||||
@router.put("/{analysis_id}", response_model=ContentGapAnalysisResponse)
|
||||
async def update_content_gap_analysis(
|
||||
analysis_id: int,
|
||||
update_data: Dict[str, Any],
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update a content gap analysis."""
|
||||
try:
|
||||
logger.info(f"Updating content gap analysis: {analysis_id}")
|
||||
|
||||
updated_analysis = await gap_analysis_service.update_gap_analysis(analysis_id, update_data, db)
|
||||
return ContentGapAnalysisResponse(**updated_analysis)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating content gap analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "update_content_gap_analysis")
|
||||
|
||||
@router.delete("/{analysis_id}")
|
||||
async def delete_content_gap_analysis(
|
||||
analysis_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Delete a content gap analysis."""
|
||||
try:
|
||||
logger.info(f"Deleting content gap analysis: {analysis_id}")
|
||||
|
||||
deleted = await gap_analysis_service.delete_gap_analysis(analysis_id, db)
|
||||
|
||||
if deleted:
|
||||
return {"message": f"Content gap analysis {analysis_id} deleted successfully"}
|
||||
else:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Content gap analysis", analysis_id)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting content gap analysis: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "delete_content_gap_analysis")
|
||||
268
backend/api/content_planning/api/routes/health_monitoring.py
Normal file
268
backend/api/content_planning/api/routes/health_monitoring.py
Normal file
@@ -0,0 +1,268 @@
|
||||
"""
|
||||
Health Monitoring Routes for Content Planning API
|
||||
Extracted from the main content_planning.py file for better organization.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
|
||||
# Import database service
|
||||
from services.database import get_db_session, get_db
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
|
||||
# Import utilities
|
||||
from ...utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ...utils.response_builders import ResponseBuilder
|
||||
from ...utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
# Import AI analysis database service
|
||||
from services.ai_analysis_db_service import AIAnalysisDBService
|
||||
|
||||
# Initialize services
|
||||
ai_analysis_db_service = AIAnalysisDBService()
|
||||
|
||||
# Create router
|
||||
router = APIRouter(prefix="/health", tags=["health-monitoring"])
|
||||
|
||||
@router.get("/backend", response_model=Dict[str, Any])
|
||||
async def check_backend_health():
|
||||
"""
|
||||
Check core backend health (independent of AI services)
|
||||
"""
|
||||
try:
|
||||
# Check basic backend functionality
|
||||
health_status = {
|
||||
"status": "healthy",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"services": {
|
||||
"api_server": True,
|
||||
"database_connection": False, # Will be updated below
|
||||
"file_system": True,
|
||||
"memory_usage": "normal"
|
||||
},
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
||||
# Test database connection
|
||||
try:
|
||||
from sqlalchemy import text
|
||||
db_session = get_db_session()
|
||||
result = db_session.execute(text("SELECT 1"))
|
||||
result.fetchone()
|
||||
health_status["services"]["database_connection"] = True
|
||||
except Exception as e:
|
||||
logger.warning(f"Database health check failed: {str(e)}")
|
||||
health_status["services"]["database_connection"] = False
|
||||
|
||||
# Determine overall status
|
||||
all_services_healthy = all(health_status["services"].values())
|
||||
health_status["status"] = "healthy" if all_services_healthy else "degraded"
|
||||
|
||||
return health_status
|
||||
except Exception as e:
|
||||
logger.error(f"Backend health check failed: {e}")
|
||||
return {
|
||||
"status": "unhealthy",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"error": str(e),
|
||||
"services": {
|
||||
"api_server": False,
|
||||
"database_connection": False,
|
||||
"file_system": False,
|
||||
"memory_usage": "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
@router.get("/ai", response_model=Dict[str, Any])
|
||||
async def check_ai_services_health():
|
||||
"""
|
||||
Check AI services health separately
|
||||
"""
|
||||
try:
|
||||
health_status = {
|
||||
"status": "healthy",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"services": {
|
||||
"gemini_provider": False,
|
||||
"ai_analytics_service": False,
|
||||
"ai_engine_service": False
|
||||
}
|
||||
}
|
||||
|
||||
# Test Gemini provider
|
||||
try:
|
||||
from llm_providers.gemini_provider import get_gemini_api_key
|
||||
api_key = get_gemini_api_key()
|
||||
if api_key:
|
||||
health_status["services"]["gemini_provider"] = True
|
||||
except Exception as e:
|
||||
logger.warning(f"Gemini provider health check failed: {e}")
|
||||
|
||||
# Test AI Analytics Service
|
||||
try:
|
||||
from services.ai_analytics_service import AIAnalyticsService
|
||||
ai_service = AIAnalyticsService()
|
||||
health_status["services"]["ai_analytics_service"] = True
|
||||
except Exception as e:
|
||||
logger.warning(f"AI Analytics Service health check failed: {e}")
|
||||
|
||||
# Test AI Engine Service
|
||||
try:
|
||||
from services.content_gap_analyzer.ai_engine_service import AIEngineService
|
||||
ai_engine = AIEngineService()
|
||||
health_status["services"]["ai_engine_service"] = True
|
||||
except Exception as e:
|
||||
logger.warning(f"AI Engine Service health check failed: {e}")
|
||||
|
||||
# Determine overall AI status
|
||||
ai_services_healthy = any(health_status["services"].values())
|
||||
health_status["status"] = "healthy" if ai_services_healthy else "unhealthy"
|
||||
|
||||
return health_status
|
||||
except Exception as e:
|
||||
logger.error(f"AI services health check failed: {e}")
|
||||
return {
|
||||
"status": "unhealthy",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"error": str(e),
|
||||
"services": {
|
||||
"gemini_provider": False,
|
||||
"ai_analytics_service": False,
|
||||
"ai_engine_service": False
|
||||
}
|
||||
}
|
||||
|
||||
@router.get("/database", response_model=Dict[str, Any])
|
||||
async def database_health_check(db: Session = Depends(get_db)):
|
||||
"""
|
||||
Health check for database operations.
|
||||
"""
|
||||
try:
|
||||
logger.info("Performing database health check")
|
||||
|
||||
db_service = ContentPlanningDBService(db)
|
||||
health_status = await db_service.health_check()
|
||||
|
||||
logger.info(f"Database health check completed: {health_status['status']}")
|
||||
return health_status
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Database health check failed: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Database health check failed: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/debug/strategies/{user_id}")
|
||||
async def debug_content_strategies(user_id: int):
|
||||
"""
|
||||
Debug endpoint to print content strategy data directly.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"🔍 DEBUG: Getting content strategy data for user {user_id}")
|
||||
|
||||
# Get latest AI analysis
|
||||
latest_analysis = await ai_analysis_db_service.get_latest_ai_analysis(
|
||||
user_id=user_id,
|
||||
analysis_type="strategic_intelligence"
|
||||
)
|
||||
|
||||
if latest_analysis:
|
||||
logger.info("📊 DEBUG: Content Strategy Data Found")
|
||||
logger.info("=" * 50)
|
||||
logger.info("FULL CONTENT STRATEGY DATA:")
|
||||
logger.info("=" * 50)
|
||||
|
||||
# Print the entire data structure
|
||||
import json
|
||||
logger.info(json.dumps(latest_analysis, indent=2, default=str))
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Content strategy data printed to logs",
|
||||
"data": latest_analysis
|
||||
}
|
||||
else:
|
||||
logger.warning("⚠️ DEBUG: No content strategy data found")
|
||||
return {
|
||||
"status": "not_found",
|
||||
"message": "No content strategy data found",
|
||||
"data": None
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ DEBUG: Error getting content strategy data: {str(e)}")
|
||||
import traceback
|
||||
logger.error(f"DEBUG Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Debug error: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/comprehensive", response_model=Dict[str, Any])
|
||||
async def comprehensive_health_check():
|
||||
"""
|
||||
Comprehensive health check for all content planning services.
|
||||
"""
|
||||
try:
|
||||
logger.info("🏥 Performing comprehensive health check")
|
||||
|
||||
# Check backend health
|
||||
backend_health = await check_backend_health()
|
||||
|
||||
# Check AI services health
|
||||
ai_health = await check_ai_services_health()
|
||||
|
||||
# Check database health
|
||||
try:
|
||||
db_session = get_db_session()
|
||||
db_service = ContentPlanningDBService(db_session)
|
||||
db_health = await db_service.health_check()
|
||||
except Exception as e:
|
||||
db_health = {
|
||||
"status": "unhealthy",
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
# Compile comprehensive health status
|
||||
all_services = {
|
||||
"backend": backend_health,
|
||||
"ai_services": ai_health,
|
||||
"database": db_health
|
||||
}
|
||||
|
||||
# Determine overall status
|
||||
healthy_services = sum(1 for service in all_services.values() if service.get("status") == "healthy")
|
||||
total_services = len(all_services)
|
||||
|
||||
overall_status = "healthy" if healthy_services == total_services else "degraded"
|
||||
|
||||
comprehensive_health = {
|
||||
"status": overall_status,
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"services": all_services,
|
||||
"summary": {
|
||||
"healthy_services": healthy_services,
|
||||
"total_services": total_services,
|
||||
"health_percentage": (healthy_services / total_services) * 100 if total_services > 0 else 0
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(f"✅ Comprehensive health check completed: {overall_status}")
|
||||
return comprehensive_health
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Comprehensive health check failed: {str(e)}")
|
||||
return {
|
||||
"status": "unhealthy",
|
||||
"timestamp": datetime.utcnow().isoformat(),
|
||||
"error": str(e),
|
||||
"services": {
|
||||
"backend": {"status": "unknown"},
|
||||
"ai_services": {"status": "unknown"},
|
||||
"database": {"status": "unknown"}
|
||||
}
|
||||
}
|
||||
212
backend/api/content_planning/api/routes/strategies.py
Normal file
212
backend/api/content_planning/api/routes/strategies.py
Normal file
@@ -0,0 +1,212 @@
|
||||
"""
|
||||
Strategy Routes for Content Planning API
|
||||
Extracted from the main content_planning.py file for better organization.
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Depends, status, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from loguru import logger
|
||||
|
||||
# Import database service
|
||||
from services.database import get_db_session, get_db
|
||||
from services.content_planning_db import ContentPlanningDBService
|
||||
|
||||
# Import models
|
||||
from ..models.requests import ContentStrategyCreate
|
||||
from ..models.responses import ContentStrategyResponse
|
||||
|
||||
# Import utilities
|
||||
from ...utils.error_handlers import ContentPlanningErrorHandler
|
||||
from ...utils.response_builders import ResponseBuilder
|
||||
from ...utils.constants import ERROR_MESSAGES, SUCCESS_MESSAGES
|
||||
|
||||
# Import services
|
||||
from ...services.enhanced_strategy_service import EnhancedStrategyService
|
||||
from ...services.enhanced_strategy_db_service import EnhancedStrategyDBService
|
||||
|
||||
# Create router
|
||||
router = APIRouter(prefix="/strategies", tags=["strategies"])
|
||||
|
||||
@router.post("/", response_model=ContentStrategyResponse)
|
||||
async def create_content_strategy(
|
||||
strategy: ContentStrategyCreate,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Create a new content strategy."""
|
||||
try:
|
||||
logger.info(f"Creating content strategy: {strategy.name}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
strategy_service = EnhancedStrategyService(db_service)
|
||||
strategy_data = strategy.dict()
|
||||
created_strategy = await strategy_service.create_enhanced_strategy(strategy_data, db)
|
||||
|
||||
return ContentStrategyResponse(**created_strategy)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating content strategy: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "create_content_strategy")
|
||||
|
||||
@router.get("/", response_model=Dict[str, Any])
|
||||
async def get_content_strategies(
|
||||
user_id: Optional[int] = Query(None, description="User ID"),
|
||||
strategy_id: Optional[int] = Query(None, description="Strategy ID")
|
||||
):
|
||||
"""
|
||||
Get content strategies with comprehensive logging for debugging.
|
||||
"""
|
||||
try:
|
||||
logger.info(f"🚀 Starting content strategy analysis for user: {user_id}, strategy: {strategy_id}")
|
||||
|
||||
# Create a temporary database session for this operation
|
||||
from services.database import get_db_session
|
||||
temp_db = get_db_session()
|
||||
try:
|
||||
db_service = EnhancedStrategyDBService(temp_db)
|
||||
strategy_service = EnhancedStrategyService(db_service)
|
||||
result = await strategy_service.get_enhanced_strategies(user_id, strategy_id, temp_db)
|
||||
return result
|
||||
finally:
|
||||
temp_db.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error retrieving content strategies: {str(e)}")
|
||||
logger.error(f"Exception type: {type(e)}")
|
||||
import traceback
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Error retrieving content strategies: {str(e)}"
|
||||
)
|
||||
|
||||
@router.get("/{strategy_id}", response_model=ContentStrategyResponse)
|
||||
async def get_content_strategy(
|
||||
strategy_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a specific content strategy by ID."""
|
||||
try:
|
||||
logger.info(f"Fetching content strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
strategy_service = EnhancedStrategyService(db_service)
|
||||
strategy_data = await strategy_service.get_enhanced_strategies(strategy_id=strategy_id, db=db)
|
||||
strategy = strategy_data.get('strategies', [{}])[0] if strategy_data.get('strategies') else {}
|
||||
return ContentStrategyResponse(**strategy)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting content strategy: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "get_content_strategy")
|
||||
|
||||
@router.put("/{strategy_id}", response_model=ContentStrategyResponse)
|
||||
async def update_content_strategy(
|
||||
strategy_id: int,
|
||||
update_data: Dict[str, Any],
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update a content strategy."""
|
||||
try:
|
||||
logger.info(f"Updating content strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
updated_strategy = await db_service.update_enhanced_strategy(strategy_id, update_data)
|
||||
|
||||
if not updated_strategy:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Content strategy", strategy_id)
|
||||
|
||||
return ContentStrategyResponse(**updated_strategy.to_dict())
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating content strategy: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "update_content_strategy")
|
||||
|
||||
@router.delete("/{strategy_id}")
|
||||
async def delete_content_strategy(
|
||||
strategy_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Delete a content strategy."""
|
||||
try:
|
||||
logger.info(f"Deleting content strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
deleted = await db_service.delete_enhanced_strategy(strategy_id)
|
||||
|
||||
if deleted:
|
||||
return {"message": f"Content strategy {strategy_id} deleted successfully"}
|
||||
else:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Content strategy", strategy_id)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error deleting content strategy: {str(e)}")
|
||||
raise ContentPlanningErrorHandler.handle_general_error(e, "delete_content_strategy")
|
||||
|
||||
@router.get("/{strategy_id}/analytics")
|
||||
async def get_strategy_analytics(
|
||||
strategy_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get analytics for a specific strategy."""
|
||||
try:
|
||||
logger.info(f"Fetching analytics for strategy: {strategy_id}")
|
||||
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
analytics = await db_service.get_enhanced_strategies_with_analytics(strategy_id)
|
||||
|
||||
if not analytics:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Content strategy", strategy_id)
|
||||
|
||||
return analytics[0] if analytics else {}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting strategy analytics: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
@router.get("/{strategy_id}/summary")
|
||||
async def get_strategy_summary(
|
||||
strategy_id: int,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get a comprehensive summary of a strategy with analytics."""
|
||||
try:
|
||||
logger.info(f"Fetching summary for strategy: {strategy_id}")
|
||||
|
||||
# Get strategy with analytics for comprehensive summary
|
||||
db_service = EnhancedStrategyDBService(db)
|
||||
strategy_with_analytics = await db_service.get_enhanced_strategies_with_analytics(strategy_id)
|
||||
|
||||
if not strategy_with_analytics:
|
||||
raise ContentPlanningErrorHandler.handle_not_found_error("Content strategy", strategy_id)
|
||||
|
||||
strategy_data = strategy_with_analytics[0]
|
||||
|
||||
# Create a comprehensive summary
|
||||
summary = {
|
||||
"strategy_id": strategy_id,
|
||||
"name": strategy_data.get("name", "Unknown Strategy"),
|
||||
"completion_percentage": strategy_data.get("completion_percentage", 0),
|
||||
"created_at": strategy_data.get("created_at"),
|
||||
"updated_at": strategy_data.get("updated_at"),
|
||||
"analytics_summary": {
|
||||
"total_analyses": len(strategy_data.get("ai_analyses", [])),
|
||||
"last_analysis": strategy_data.get("ai_analyses", [{}])[-1] if strategy_data.get("ai_analyses") else None
|
||||
}
|
||||
}
|
||||
|
||||
return summary
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting strategy summary: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
Reference in New Issue
Block a user