Base code
This commit is contained in:
0
backend/api/content_planning/utils/__init__.py
Normal file
0
backend/api/content_planning/utils/__init__.py
Normal file
220
backend/api/content_planning/utils/constants.py
Normal file
220
backend/api/content_planning/utils/constants.py
Normal file
@@ -0,0 +1,220 @@
|
||||
"""
|
||||
Constants for Content Planning API
|
||||
Centralized constants and business rules extracted from the main content_planning.py file.
|
||||
"""
|
||||
|
||||
from fastapi import status
|
||||
|
||||
# API Endpoints
|
||||
API_PREFIX = "/api/content-planning"
|
||||
API_TAGS = ["content-planning"]
|
||||
|
||||
# HTTP Status Codes
|
||||
HTTP_STATUS_CODES = {
|
||||
"OK": status.HTTP_200_OK,
|
||||
"CREATED": status.HTTP_201_CREATED,
|
||||
"NO_CONTENT": status.HTTP_204_NO_CONTENT,
|
||||
"BAD_REQUEST": status.HTTP_400_BAD_REQUEST,
|
||||
"UNAUTHORIZED": status.HTTP_401_UNAUTHORIZED,
|
||||
"FORBIDDEN": status.HTTP_403_FORBIDDEN,
|
||||
"NOT_FOUND": status.HTTP_404_NOT_FOUND,
|
||||
"CONFLICT": status.HTTP_409_CONFLICT,
|
||||
"UNPROCESSABLE_ENTITY": status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
"INTERNAL_SERVER_ERROR": status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
"SERVICE_UNAVAILABLE": status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
}
|
||||
|
||||
# Error Messages
|
||||
ERROR_MESSAGES = {
|
||||
"strategy_not_found": "Content strategy not found",
|
||||
"calendar_event_not_found": "Calendar event not found",
|
||||
"gap_analysis_not_found": "Content gap analysis not found",
|
||||
"user_not_found": "User not found",
|
||||
"invalid_request": "Invalid request data",
|
||||
"database_connection": "Database connection failed",
|
||||
"ai_service_unavailable": "AI service is currently unavailable",
|
||||
"validation_failed": "Request validation failed",
|
||||
"permission_denied": "Permission denied",
|
||||
"rate_limit_exceeded": "Rate limit exceeded",
|
||||
"internal_server_error": "Internal server error",
|
||||
"service_unavailable": "Service temporarily unavailable"
|
||||
}
|
||||
|
||||
# Success Messages
|
||||
SUCCESS_MESSAGES = {
|
||||
"strategy_created": "Content strategy created successfully",
|
||||
"strategy_updated": "Content strategy updated successfully",
|
||||
"strategy_deleted": "Content strategy deleted successfully",
|
||||
"calendar_event_created": "Calendar event created successfully",
|
||||
"calendar_event_updated": "Calendar event updated successfully",
|
||||
"calendar_event_deleted": "Calendar event deleted successfully",
|
||||
"gap_analysis_created": "Content gap analysis created successfully",
|
||||
"gap_analysis_completed": "Content gap analysis completed successfully",
|
||||
"ai_analytics_generated": "AI analytics generated successfully",
|
||||
"calendar_generated": "Calendar generated successfully",
|
||||
"content_optimized": "Content optimized successfully",
|
||||
"performance_predicted": "Performance prediction completed successfully"
|
||||
}
|
||||
|
||||
# Business Rules
|
||||
BUSINESS_RULES = {
|
||||
"max_strategies_per_user": 10,
|
||||
"max_calendar_events_per_strategy": 100,
|
||||
"max_gap_analyses_per_user": 5,
|
||||
"max_ai_analytics_per_user": 20,
|
||||
"default_page_size": 10,
|
||||
"max_page_size": 100,
|
||||
"cache_duration_hours": 24,
|
||||
"max_processing_time_seconds": 30,
|
||||
"min_confidence_score": 0.7,
|
||||
"max_competitor_urls": 10,
|
||||
"max_target_keywords": 50
|
||||
}
|
||||
|
||||
# Content Types
|
||||
CONTENT_TYPES = [
|
||||
"blog_post",
|
||||
"social_media_post",
|
||||
"video",
|
||||
"infographic",
|
||||
"case_study",
|
||||
"whitepaper",
|
||||
"newsletter",
|
||||
"webinar",
|
||||
"podcast",
|
||||
"live_stream"
|
||||
]
|
||||
|
||||
# Platforms
|
||||
PLATFORMS = [
|
||||
"linkedin",
|
||||
"twitter",
|
||||
"facebook",
|
||||
"instagram",
|
||||
"youtube",
|
||||
"tiktok",
|
||||
"website",
|
||||
"email",
|
||||
"medium",
|
||||
"quora"
|
||||
]
|
||||
|
||||
# Industries
|
||||
INDUSTRIES = [
|
||||
"technology",
|
||||
"healthcare",
|
||||
"finance",
|
||||
"education",
|
||||
"retail",
|
||||
"manufacturing",
|
||||
"consulting",
|
||||
"real_estate",
|
||||
"legal",
|
||||
"non_profit"
|
||||
]
|
||||
|
||||
# Business Sizes
|
||||
BUSINESS_SIZES = [
|
||||
"startup",
|
||||
"sme",
|
||||
"enterprise"
|
||||
]
|
||||
|
||||
# Calendar Types
|
||||
CALENDAR_TYPES = [
|
||||
"monthly",
|
||||
"weekly",
|
||||
"custom"
|
||||
]
|
||||
|
||||
# Time Periods
|
||||
TIME_PERIODS = [
|
||||
"7d",
|
||||
"30d",
|
||||
"90d",
|
||||
"1y"
|
||||
]
|
||||
|
||||
# AI Service Status
|
||||
AI_SERVICE_STATUS = {
|
||||
"operational": "operational",
|
||||
"degraded": "degraded",
|
||||
"unavailable": "unavailable",
|
||||
"fallback": "fallback"
|
||||
}
|
||||
|
||||
# Data Sources
|
||||
DATA_SOURCES = {
|
||||
"ai_analysis": "ai_analysis",
|
||||
"database_cache": "database_cache",
|
||||
"fallback": "fallback"
|
||||
}
|
||||
|
||||
# Priority Levels
|
||||
PRIORITY_LEVELS = [
|
||||
"high",
|
||||
"medium",
|
||||
"low"
|
||||
]
|
||||
|
||||
# Content Pillars
|
||||
DEFAULT_CONTENT_PILLARS = [
|
||||
"Educational Content",
|
||||
"Thought Leadership",
|
||||
"Product Updates",
|
||||
"Industry Insights",
|
||||
"Customer Stories",
|
||||
"Behind the Scenes"
|
||||
]
|
||||
|
||||
# Performance Metrics
|
||||
PERFORMANCE_METRICS = [
|
||||
"engagement_rate",
|
||||
"reach",
|
||||
"conversion_rate",
|
||||
"click_through_rate",
|
||||
"time_on_page",
|
||||
"bounce_rate",
|
||||
"social_shares",
|
||||
"comments",
|
||||
"likes"
|
||||
]
|
||||
|
||||
# Validation Rules
|
||||
VALIDATION_RULES = {
|
||||
"min_title_length": 3,
|
||||
"max_title_length": 100,
|
||||
"min_description_length": 10,
|
||||
"max_description_length": 1000,
|
||||
"min_url_length": 10,
|
||||
"max_url_length": 500,
|
||||
"min_keyword_length": 2,
|
||||
"max_keyword_length": 50
|
||||
}
|
||||
|
||||
# Logging Levels
|
||||
LOGGING_LEVELS = {
|
||||
"debug": "DEBUG",
|
||||
"info": "INFO",
|
||||
"warning": "WARNING",
|
||||
"error": "ERROR",
|
||||
"critical": "CRITICAL"
|
||||
}
|
||||
|
||||
# Cache Keys
|
||||
CACHE_KEYS = {
|
||||
"strategies": "content_planning:strategies",
|
||||
"calendar_events": "content_planning:calendar_events",
|
||||
"gap_analyses": "content_planning:gap_analyses",
|
||||
"ai_analytics": "content_planning:ai_analytics",
|
||||
"calendar_generation": "content_planning:calendar_generation"
|
||||
}
|
||||
|
||||
# API Rate Limits
|
||||
RATE_LIMITS = {
|
||||
"strategies_per_minute": 10,
|
||||
"calendar_events_per_minute": 20,
|
||||
"gap_analyses_per_hour": 5,
|
||||
"ai_analytics_per_hour": 10,
|
||||
"calendar_generation_per_hour": 3
|
||||
}
|
||||
152
backend/api/content_planning/utils/error_handlers.py
Normal file
152
backend/api/content_planning/utils/error_handlers.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""
|
||||
Centralized Error Handlers for Content Planning Module
|
||||
Standardized error handling patterns extracted from the main content planning file.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, Optional
|
||||
from fastapi import HTTPException, status
|
||||
from loguru import logger
|
||||
import traceback
|
||||
|
||||
class ContentPlanningErrorHandler:
|
||||
"""Centralized error handling for content planning operations."""
|
||||
|
||||
@staticmethod
|
||||
def handle_database_error(error: Exception, operation: str) -> HTTPException:
|
||||
"""Handle database-related errors."""
|
||||
logger.error(f"Database error during {operation}: {str(error)}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Database operation failed during {operation}: {str(error)}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def handle_validation_error(error: Exception, field: str) -> HTTPException:
|
||||
"""Handle validation errors."""
|
||||
logger.error(f"Validation error for field '{field}': {str(error)}")
|
||||
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
detail=f"Validation error for {field}: {str(error)}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def handle_not_found_error(resource_type: str, resource_id: Any) -> HTTPException:
|
||||
"""Handle resource not found errors."""
|
||||
logger.warning(f"{resource_type} not found: {resource_id}")
|
||||
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"{resource_type} with id {resource_id} not found"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def handle_ai_service_error(error: Exception, service: str) -> HTTPException:
|
||||
"""Handle AI service errors."""
|
||||
logger.error(f"AI service error in {service}: {str(error)}")
|
||||
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=f"AI service {service} is currently unavailable: {str(error)}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def handle_api_key_error(missing_keys: list) -> HTTPException:
|
||||
"""Handle API key configuration errors."""
|
||||
logger.error(f"Missing API keys: {missing_keys}")
|
||||
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail=f"AI services are not properly configured. Missing keys: {', '.join(missing_keys)}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def handle_general_error(error: Exception, operation: str) -> HTTPException:
|
||||
"""Handle general errors."""
|
||||
logger.error(f"General error during {operation}: {str(error)}")
|
||||
logger.error(f"Exception type: {type(error)}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Error during {operation}: {str(error)}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_error_response(
|
||||
status_code: int,
|
||||
message: str,
|
||||
error_type: str = "general",
|
||||
details: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Create standardized error response."""
|
||||
error_response = {
|
||||
"status": "error",
|
||||
"error_type": error_type,
|
||||
"message": message,
|
||||
"status_code": status_code,
|
||||
"timestamp": "2024-08-01T10:00:00Z" # This should be dynamic
|
||||
}
|
||||
|
||||
if details:
|
||||
error_response["details"] = details
|
||||
|
||||
return error_response
|
||||
|
||||
# Common error messages
|
||||
ERROR_MESSAGES = {
|
||||
"strategy_not_found": "Content strategy not found",
|
||||
"calendar_event_not_found": "Calendar event not found",
|
||||
"gap_analysis_not_found": "Content gap analysis not found",
|
||||
"user_not_found": "User not found",
|
||||
"invalid_request": "Invalid request data",
|
||||
"database_connection": "Database connection failed",
|
||||
"ai_service_unavailable": "AI service is currently unavailable",
|
||||
"validation_failed": "Request validation failed",
|
||||
"permission_denied": "Permission denied",
|
||||
"rate_limit_exceeded": "Rate limit exceeded",
|
||||
"internal_server_error": "Internal server error",
|
||||
"service_unavailable": "Service temporarily unavailable"
|
||||
}
|
||||
|
||||
# Error status codes mapping
|
||||
ERROR_STATUS_CODES = {
|
||||
"not_found": status.HTTP_404_NOT_FOUND,
|
||||
"validation_error": status.HTTP_422_UNPROCESSABLE_ENTITY,
|
||||
"bad_request": status.HTTP_400_BAD_REQUEST,
|
||||
"unauthorized": status.HTTP_401_UNAUTHORIZED,
|
||||
"forbidden": status.HTTP_403_FORBIDDEN,
|
||||
"not_found": status.HTTP_404_NOT_FOUND,
|
||||
"conflict": status.HTTP_409_CONFLICT,
|
||||
"internal_error": status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
"service_unavailable": status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
}
|
||||
|
||||
def log_error(error: Exception, context: str, user_id: Optional[int] = None):
|
||||
"""Log error with context information."""
|
||||
logger.error(f"Error in {context}: {str(error)}")
|
||||
if user_id:
|
||||
logger.error(f"User ID: {user_id}")
|
||||
logger.error(f"Exception type: {type(error)}")
|
||||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||||
|
||||
def create_http_exception(
|
||||
error_type: str,
|
||||
message: str,
|
||||
status_code: Optional[int] = None,
|
||||
details: Optional[Dict[str, Any]] = None
|
||||
) -> HTTPException:
|
||||
"""Create HTTP exception with standardized error handling."""
|
||||
if status_code is None:
|
||||
status_code = ERROR_STATUS_CODES.get(error_type, status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
logger.error(f"HTTP Exception: {error_type} - {message}")
|
||||
if details:
|
||||
logger.error(f"Error details: {details}")
|
||||
|
||||
return HTTPException(
|
||||
status_code=status_code,
|
||||
detail=message
|
||||
)
|
||||
193
backend/api/content_planning/utils/response_builders.py
Normal file
193
backend/api/content_planning/utils/response_builders.py
Normal file
@@ -0,0 +1,193 @@
|
||||
"""
|
||||
Response Builders for Content Planning API
|
||||
Standardized response formatting utilities extracted from the main content_planning.py file.
|
||||
"""
|
||||
|
||||
from typing import Dict, Any, List, Optional
|
||||
from datetime import datetime
|
||||
from fastapi import status
|
||||
import json
|
||||
|
||||
class ResponseBuilder:
|
||||
"""Standardized response building utilities."""
|
||||
|
||||
@staticmethod
|
||||
def create_success_response(
|
||||
data: Any,
|
||||
message: str = "Operation completed successfully",
|
||||
status_code: int = 200
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a standardized success response."""
|
||||
return {
|
||||
"status": "success",
|
||||
"message": message,
|
||||
"data": data,
|
||||
"status_code": status_code,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def create_error_response(
|
||||
message: str,
|
||||
error_type: str = "general",
|
||||
status_code: int = 500,
|
||||
details: Optional[Dict[str, Any]] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a standardized error response."""
|
||||
response = {
|
||||
"status": "error",
|
||||
"error_type": error_type,
|
||||
"message": message,
|
||||
"status_code": status_code,
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
if details:
|
||||
response["details"] = details
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def create_paginated_response(
|
||||
data: List[Any],
|
||||
total_count: int,
|
||||
page: int = 1,
|
||||
page_size: int = 10,
|
||||
message: str = "Data retrieved successfully"
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a standardized paginated response."""
|
||||
return {
|
||||
"status": "success",
|
||||
"message": message,
|
||||
"data": data,
|
||||
"pagination": {
|
||||
"total_count": total_count,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
"total_pages": (total_count + page_size - 1) // page_size
|
||||
},
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def create_health_response(
|
||||
service_name: str,
|
||||
status: str,
|
||||
services: Dict[str, Any],
|
||||
timestamp: Optional[datetime] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a standardized health check response."""
|
||||
return {
|
||||
"service": service_name,
|
||||
"status": status,
|
||||
"timestamp": (timestamp or datetime.utcnow()).isoformat(),
|
||||
"services": services
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def create_ai_analytics_response(
|
||||
insights: List[Dict[str, Any]],
|
||||
recommendations: List[Dict[str, Any]],
|
||||
total_insights: int,
|
||||
total_recommendations: int,
|
||||
generated_at: datetime,
|
||||
ai_service_status: str = "operational",
|
||||
processing_time: Optional[float] = None,
|
||||
personalized_data_used: bool = True,
|
||||
data_source: str = "ai_analysis"
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a standardized AI analytics response."""
|
||||
response = {
|
||||
"insights": insights,
|
||||
"recommendations": recommendations,
|
||||
"total_insights": total_insights,
|
||||
"total_recommendations": total_recommendations,
|
||||
"generated_at": generated_at.isoformat(),
|
||||
"ai_service_status": ai_service_status,
|
||||
"personalized_data_used": personalized_data_used,
|
||||
"data_source": data_source
|
||||
}
|
||||
|
||||
if processing_time is not None:
|
||||
response["processing_time"] = f"{processing_time:.2f}s"
|
||||
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def create_gap_analysis_response(
|
||||
gap_analyses: List[Dict[str, Any]],
|
||||
total_gaps: int,
|
||||
generated_at: datetime,
|
||||
ai_service_status: str = "operational",
|
||||
personalized_data_used: bool = True,
|
||||
data_source: str = "ai_analysis"
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a standardized gap analysis response."""
|
||||
return {
|
||||
"gap_analyses": gap_analyses,
|
||||
"total_gaps": total_gaps,
|
||||
"generated_at": generated_at.isoformat(),
|
||||
"ai_service_status": ai_service_status,
|
||||
"personalized_data_used": personalized_data_used,
|
||||
"data_source": data_source
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def create_strategy_response(
|
||||
strategies: List[Dict[str, Any]],
|
||||
total_count: int,
|
||||
user_id: Optional[int] = None,
|
||||
analysis_date: Optional[datetime] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a standardized strategy response."""
|
||||
response = {
|
||||
"status": "success",
|
||||
"message": "Content strategy retrieved successfully",
|
||||
"data": {
|
||||
"strategies": strategies,
|
||||
"total_count": total_count
|
||||
}
|
||||
}
|
||||
|
||||
if user_id is not None:
|
||||
response["data"]["user_id"] = user_id
|
||||
|
||||
if analysis_date is not None:
|
||||
response["data"]["analysis_date"] = analysis_date.isoformat()
|
||||
|
||||
return response
|
||||
|
||||
# Common response patterns
|
||||
RESPONSE_PATTERNS = {
|
||||
"success": {
|
||||
"status": "success",
|
||||
"message": "Operation completed successfully"
|
||||
},
|
||||
"error": {
|
||||
"status": "error",
|
||||
"message": "Operation failed"
|
||||
},
|
||||
"not_found": {
|
||||
"status": "error",
|
||||
"message": "Resource not found"
|
||||
},
|
||||
"validation_error": {
|
||||
"status": "error",
|
||||
"message": "Validation failed"
|
||||
}
|
||||
}
|
||||
|
||||
# Response status codes
|
||||
RESPONSE_STATUS_CODES = {
|
||||
"success": 200,
|
||||
"created": 201,
|
||||
"no_content": 204,
|
||||
"bad_request": 400,
|
||||
"unauthorized": 401,
|
||||
"forbidden": 403,
|
||||
"not_found": 404,
|
||||
"conflict": 409,
|
||||
"unprocessable_entity": 422,
|
||||
"internal_error": 500,
|
||||
"service_unavailable": 503
|
||||
}
|
||||
Reference in New Issue
Block a user