ALwrity Backend and Frontend - Stability and Error Handling Improvements

This commit is contained in:
ajaysi
2025-10-14 10:57:16 +05:30
parent b6debd80b7
commit 40fb6ac95b
31 changed files with 1491 additions and 592 deletions

View File

@@ -1,108 +0,0 @@
# Stability AI Configuration Example
# Copy this file to .env and fill in your actual values
# Required: Your Stability AI API Key
# Get your API key from: https://platform.stability.ai/account/keys
STABILITY_API_KEY=your_stability_api_key_here
# Optional: Stability AI API Base URL (default: https://api.stability.ai)
STABILITY_BASE_URL=https://api.stability.ai
# Optional: Request timeout in seconds (default: 300)
STABILITY_TIMEOUT=300
# Optional: Maximum retries for failed requests (default: 3)
STABILITY_MAX_RETRIES=3
# Optional: Maximum file size for uploads in bytes (default: 10MB)
STABILITY_MAX_FILE_SIZE=10485760
# Optional: Enable debug mode for detailed logging (default: false)
STABILITY_DEBUG=false
# Optional: Enable caching for responses (default: true)
STABILITY_ENABLE_CACHE=true
# Optional: Cache duration in seconds (default: 3600)
STABILITY_CACHE_DURATION=3600
# Optional: Enable rate limiting (default: true)
STABILITY_ENABLE_RATE_LIMIT=true
# Optional: Rate limit - requests per window (default: 150)
STABILITY_RATE_LIMIT_REQUESTS=150
# Optional: Rate limit window in seconds (default: 10)
STABILITY_RATE_LIMIT_WINDOW=10
# Optional: Enable content moderation (default: true)
STABILITY_ENABLE_MODERATION=true
# Optional: Enable request logging (default: true)
STABILITY_ENABLE_LOGGING=true
# Optional: Maximum log entries to keep in memory (default: 1000)
STABILITY_MAX_LOG_ENTRIES=1000
# Optional: Enable experimental features (default: false)
STABILITY_ENABLE_EXPERIMENTAL=false
# Optional: Default output format for images (default: png)
STABILITY_DEFAULT_IMAGE_FORMAT=png
# Optional: Default output format for audio (default: mp3)
STABILITY_DEFAULT_AUDIO_FORMAT=mp3
# Optional: Enable webhook support (default: false)
STABILITY_ENABLE_WEBHOOKS=false
# Optional: Webhook URL for generation completion notifications
STABILITY_WEBHOOK_URL=
# Optional: Webhook secret for signature validation
STABILITY_WEBHOOK_SECRET=
# Optional: Enable batch processing (default: true)
STABILITY_ENABLE_BATCH=true
# Optional: Maximum batch size (default: 10)
STABILITY_MAX_BATCH_SIZE=10
# Optional: Enable quality analysis features (default: true)
STABILITY_ENABLE_QUALITY_ANALYSIS=true
# Optional: Enable prompt optimization features (default: true)
STABILITY_ENABLE_PROMPT_OPTIMIZATION=true
# Optional: Default creativity level for upscaling (default: 0.35)
STABILITY_DEFAULT_CREATIVITY=0.35
# Optional: Default control strength for control operations (default: 0.7)
STABILITY_DEFAULT_CONTROL_STRENGTH=0.7
# Optional: Default style fidelity for style operations (default: 0.5)
STABILITY_DEFAULT_STYLE_FIDELITY=0.5
# Optional: Enable automatic image format optimization (default: true)
STABILITY_AUTO_OPTIMIZE_FORMAT=true
# Optional: Enable automatic parameter optimization (default: true)
STABILITY_AUTO_OPTIMIZE_PARAMS=true
# Optional: Default model for generate operations (default: core)
STABILITY_DEFAULT_GENERATE_MODEL=core
# Optional: Default model for upscale operations (default: fast)
STABILITY_DEFAULT_UPSCALE_MODEL=fast
# Optional: Enable cost tracking and warnings (default: true)
STABILITY_ENABLE_COST_TRACKING=true
# Optional: Credit warning threshold (default: 10)
STABILITY_CREDIT_WARNING_THRESHOLD=10
# Optional: Enable performance monitoring (default: true)
STABILITY_ENABLE_MONITORING=true
# Optional: Performance monitoring interval in seconds (default: 60)
STABILITY_MONITORING_INTERVAL=60

View File

@@ -16,72 +16,98 @@ class DatabaseSetup:
def setup_essential_tables(self) -> bool:
"""Set up essential database tables."""
print("📊 Setting up essential database tables...")
import os
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
if verbose:
print("📊 Setting up essential database tables...")
try:
from services.database import init_database, engine
# Initialize database connection
init_database()
print(" ✅ Database connection initialized")
if verbose:
print(" ✅ Database connection initialized")
# Create essential tables
self._create_monitoring_tables()
self._create_subscription_tables()
self._create_persona_tables()
print("✅ Essential database tables created")
if verbose:
print("✅ Essential database tables created")
return True
except Exception as e:
print(f"⚠️ Warning: Database setup failed: {e}")
if self.production_mode:
print(" Continuing in production mode...")
return True
else:
print(" This may affect functionality")
return True # Don't fail startup for database issues
if verbose:
print(f"⚠️ Warning: Database setup failed: {e}")
if self.production_mode:
print(" Continuing in production mode...")
else:
print(" This may affect functionality")
return True # Don't fail startup for database issues
def _create_monitoring_tables(self) -> bool:
"""Create API monitoring tables."""
import os
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
try:
from models.api_monitoring import Base as MonitoringBase
MonitoringBase.metadata.create_all(bind=engine)
print(" ✅ Monitoring tables created")
if verbose:
print(" ✅ Monitoring tables created")
return True
except Exception as e:
print(f" ⚠️ Monitoring tables failed: {e}")
if verbose:
print(f" ⚠️ Monitoring tables failed: {e}")
return True # Non-critical
def _create_subscription_tables(self) -> bool:
"""Create subscription and billing tables."""
import os
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
try:
from models.subscription_models import Base as SubscriptionBase
SubscriptionBase.metadata.create_all(bind=engine)
print(" ✅ Subscription tables created")
if verbose:
print(" ✅ Subscription tables created")
return True
except Exception as e:
print(f" ⚠️ Subscription tables failed: {e}")
if verbose:
print(f" ⚠️ Subscription tables failed: {e}")
return True # Non-critical
def _create_persona_tables(self) -> bool:
"""Create persona analysis tables."""
import os
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
try:
from models.persona_models import Base as PersonaBase
PersonaBase.metadata.create_all(bind=engine)
print(" ✅ Persona tables created")
if verbose:
print(" ✅ Persona tables created")
return True
except Exception as e:
print(f" ⚠️ Persona tables failed: {e}")
if verbose:
print(f" ⚠️ Persona tables failed: {e}")
return True # Non-critical
def verify_tables(self) -> bool:
"""Verify that essential tables exist."""
import os
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
if self.production_mode:
print("⚠️ Skipping table verification in production mode")
if verbose:
print("⚠️ Skipping table verification in production mode")
return True
print("🔍 Verifying database tables...")
if verbose:
print("🔍 Verifying database tables...")
try:
from services.database import engine
@@ -97,11 +123,13 @@ class DatabaseSetup:
]
existing_tables = [table for table in essential_tables if table in tables]
print(f" ✅ Found tables: {existing_tables}")
if verbose:
print(f" ✅ Found tables: {existing_tables}")
if len(existing_tables) < len(essential_tables):
missing = [table for table in essential_tables if table not in existing_tables]
print(f" ⚠️ Missing tables: {missing}")
if verbose:
print(f" ⚠️ Missing tables: {missing}")
return True
@@ -124,11 +152,11 @@ class DatabaseSetup:
# Set up billing tables
self._setup_billing_tables()
print("✅ Advanced database features configured")
logger.debug("✅ Advanced database features configured")
return True
except Exception as e:
print(f"⚠️ Advanced table setup failed: {e}")
logger.warning(f"Advanced table setup failed: {e}")
return True # Non-critical
def _setup_monitoring_tables(self) -> bool:
@@ -157,16 +185,16 @@ class DatabaseSetup:
# Check if tables already exist
if check_existing_tables(engine):
print(" ✅ Billing tables already exist")
logger.debug("✅ Billing tables already exist")
return True
if create_billing_tables():
print(" ✅ Billing tables created")
logger.debug("✅ Billing tables created")
return True
else:
print(" ⚠️ Billing setup failed")
logger.warning("Billing setup failed")
return True # Non-critical
except Exception as e:
print(f" ⚠️ Billing setup failed: {e}")
logger.warning(f"Billing setup failed: {e}")
return True # Non-critical

View File

@@ -51,40 +51,54 @@ class DependencyManager:
def check_critical_dependencies(self) -> Tuple[bool, List[str]]:
"""Check if critical dependencies are available."""
print("🔍 Checking critical dependencies...")
import os
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
if verbose:
print("🔍 Checking critical dependencies...")
missing_packages = []
for package in self.critical_packages:
try:
__import__(package.replace('-', '_'))
print(f"{package}")
if verbose:
print(f"{package}")
except ImportError:
print(f"{package} - MISSING")
if verbose:
print(f"{package} - MISSING")
missing_packages.append(package)
if missing_packages:
print(f"❌ Missing critical packages: {', '.join(missing_packages)}")
if verbose:
print(f"❌ Missing critical packages: {', '.join(missing_packages)}")
return False, missing_packages
print("✅ All critical dependencies available!")
if verbose:
print("✅ All critical dependencies available!")
return True, []
def check_optional_dependencies(self) -> Tuple[bool, List[str]]:
"""Check if optional dependencies are available."""
print("🔍 Checking optional dependencies...")
import os
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
if verbose:
print("🔍 Checking optional dependencies...")
missing_packages = []
for package in self.optional_packages:
try:
__import__(package.replace('-', '_'))
print(f"{package}")
if verbose:
print(f"{package}")
except ImportError:
print(f" ⚠️ {package} - MISSING (optional)")
if verbose:
print(f" ⚠️ {package} - MISSING (optional)")
missing_packages.append(package)
if missing_packages:
if missing_packages and verbose:
print(f"⚠️ Missing optional packages: {', '.join(missing_packages)}")
print(" Some features may not be available")

View File

@@ -28,21 +28,29 @@ class EnvironmentSetup:
def setup_directories(self) -> bool:
"""Create necessary directories for ALwrity."""
print("📁 Setting up directories...")
import os
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
if verbose:
print("📁 Setting up directories...")
if not self.required_directories:
print(" ⚠️ Skipping directory creation in production mode")
if verbose:
print(" ⚠️ Skipping directory creation in production mode")
return True
for directory in self.required_directories:
try:
Path(directory).mkdir(parents=True, exist_ok=True)
print(f" ✅ Created: {directory}")
if verbose:
print(f" ✅ Created: {directory}")
except Exception as e:
print(f" ❌ Failed to create {directory}: {e}")
if verbose:
print(f" ❌ Failed to create {directory}: {e}")
return False
print("✅ All directories created successfully")
if verbose:
print("✅ All directories created successfully")
return True
def setup_environment_variables(self) -> bool:

View File

@@ -18,22 +18,31 @@ class RouterManager:
def include_router_safely(self, router, router_name: str = None) -> bool:
"""Include a router safely with error handling."""
import os
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
try:
self.app.include_router(router)
router_name = router_name or getattr(router, 'prefix', 'unknown')
self.included_routers.append(router_name)
logger.info(f"✅ Router included successfully: {router_name}")
if verbose:
logger.info(f"✅ Router included successfully: {router_name}")
return True
except Exception as e:
router_name = router_name or 'unknown'
self.failed_routers.append({"name": router_name, "error": str(e)})
logger.warning(f"❌ Router inclusion failed: {router_name} - {e}")
if verbose:
logger.warning(f"❌ Router inclusion failed: {router_name} - {e}")
return False
def include_core_routers(self) -> bool:
"""Include core application routers."""
import os
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
try:
logger.info("Including core routers...")
if verbose:
logger.info("Including core routers...")
# Component logic router
from api.component_logic import router as component_logic_router

View File

@@ -70,129 +70,103 @@ class OnboardingSummaryService:
try:
db = next(get_db())
api_keys = self.db_service.get_api_keys(self.user_id, db)
logger.info(f"Retrieved {len(api_keys)} API keys from database for user {self.user_id}")
return api_keys
db.close()
if not api_keys:
return {
"openai": {"configured": False, "value": None},
"anthropic": {"configured": False, "value": None},
"google": {"configured": False, "value": None}
}
return {
"openai": {
"configured": bool(api_keys.get('openai_api_key')),
"value": api_keys.get('openai_api_key')[:8] + "..." if api_keys.get('openai_api_key') else None
},
"anthropic": {
"configured": bool(api_keys.get('anthropic_api_key')),
"value": api_keys.get('anthropic_api_key')[:8] + "..." if api_keys.get('anthropic_api_key') else None
},
"google": {
"configured": bool(api_keys.get('google_api_key')),
"value": api_keys.get('google_api_key')[:8] + "..." if api_keys.get('google_api_key') else None
}
}
except Exception as e:
logger.error(f"Error getting API keys from database: {e}")
return {}
logger.error(f"Error getting API keys: {str(e)}")
return {
"openai": {"configured": False, "value": None},
"anthropic": {"configured": False, "value": None},
"google": {"configured": False, "value": None}
}
def _get_website_analysis(self) -> Optional[Dict[str, Any]]:
"""Get website analysis data from database (Step 2)."""
"""Get website analysis data from database."""
try:
db = next(get_db())
website_data = self.db_service.get_website_analysis(self.user_id, db)
if website_data:
logger.info(f"Retrieved website analysis from database for user {self.user_id}")
else:
logger.warning(f"No website analysis found in database for user {self.user_id}")
db.close()
return website_data
except Exception as e:
logger.error(f"Error getting website analysis from database: {e}")
logger.error(f"Error getting website analysis: {str(e)}")
return None
def _get_research_preferences(self) -> Optional[Dict[str, Any]]:
"""Get research preferences data from database (Step 3)."""
"""Get research preferences from database."""
try:
db = next(get_db())
research_data = self.db_service.get_research_preferences(self.user_id, db)
if research_data:
logger.info(f"Retrieved research preferences from database for user {self.user_id}")
else:
logger.warning(f"No research preferences found in database for user {self.user_id}")
return research_data
preferences = self.db_service.get_research_preferences(self.user_id, db)
db.close()
return preferences
except Exception as e:
logger.error(f"Error getting research preferences from database: {e}")
logger.error(f"Error getting research preferences: {str(e)}")
return None
def _get_personalization_settings(self, research_preferences: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
"""Get personalization settings from Step 4 (Persona) database."""
try:
# Try to get from Step 4 (Persona) in database
db = next(get_db())
persona_data = self.db_service.get_persona_data(self.user_id, db)
if persona_data:
logger.info(f"Retrieved persona data from database for user {self.user_id}")
# Extract personalization settings from persona data
if 'corePersona' in persona_data:
core_persona = persona_data.get('corePersona', {})
return {
'writing_style': core_persona.get('linguistic_fingerprint', {}).get('tone', 'Professional'),
'tone': core_persona.get('tonal_range', {}).get('primary_tone', 'Formal'),
'brand_voice': core_persona.get('identity', {}).get('voice', 'Trustworthy and Expert')
}
# Fallback to research preferences if persona data not available
if research_preferences:
logger.info(f"Using research preferences as fallback for personalization")
return {
'writing_style': research_preferences.get('writing_style', {}).get('tone', 'Professional'),
'tone': research_preferences.get('writing_style', {}).get('voice', 'Formal'),
'brand_voice': research_preferences.get('writing_style', {}).get('complexity', 'Trustworthy and Expert')
}
return None
except Exception as e:
logger.error(f"Error getting personalization settings from database: {e}")
return None
def _get_personalization_settings(self, research_preferences: Optional[Dict[str, Any]]) -> Dict[str, Any]:
"""Get personalization settings based on research preferences."""
if not research_preferences:
return {
"writing_style": "professional",
"target_audience": "general",
"content_focus": "informative"
}
return {
"writing_style": research_preferences.get('writing_style', 'professional'),
"target_audience": research_preferences.get('target_audience', 'general'),
"content_focus": research_preferences.get('content_focus', 'informative')
}
def _check_persona_readiness(self, website_analysis: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
"""Check if persona can be generated."""
try:
persona_service = PersonaAnalysisService()
# Check if persona can be generated
onboarding_data = persona_service._collect_onboarding_data(self.user_id)
if onboarding_data:
data_sufficiency = persona_service._calculate_data_sufficiency(onboarding_data)
return {
"ready": data_sufficiency >= 50.0,
"data_sufficiency": data_sufficiency,
"can_generate": website_analysis is not None
}
return {"ready": False, "data_sufficiency": 0.0, "can_generate": False}
except Exception as e:
logger.warning(f"Could not check persona readiness: {str(e)}")
return {"ready": False, "error": str(e)}
def _check_persona_readiness(self, website_analysis: Optional[Dict[str, Any]]) -> Dict[str, Any]:
"""Check if persona generation is ready based on available data."""
if not website_analysis:
return {
"ready": False,
"reason": "Website analysis not completed",
"missing_data": ["website_url", "style_analysis"]
}
required_fields = ['website_url', 'writing_style', 'target_audience']
missing_fields = [field for field in required_fields if not website_analysis.get(field)]
return {
"ready": len(missing_fields) == 0,
"reason": "All required data available" if len(missing_fields) == 0 else f"Missing: {', '.join(missing_fields)}",
"missing_data": missing_fields
}
def _determine_capabilities(self, api_keys: Dict[str, Any], website_analysis: Optional[Dict[str, Any]],
research_preferences: Optional[Dict[str, Any]],
personalization_settings: Optional[Dict[str, Any]],
persona_readiness: Optional[Dict[str, Any]]) -> Dict[str, bool]:
"""Determine user capabilities based on onboarding data."""
return {
"ai_content": len(api_keys) > 0,
"style_analysis": website_analysis is not None,
"research_tools": research_preferences is not None,
"personalization": personalization_settings is not None,
"persona_generation": persona_readiness.get("ready", False) if persona_readiness else False,
"integrations": False # TODO: Implement
personalization_settings: Dict[str, Any],
persona_readiness: Dict[str, Any]) -> Dict[str, Any]:
"""Determine available capabilities based on configured data."""
capabilities = {
"ai_content_generation": any(key.get("configured") for key in api_keys.values()),
"website_analysis": website_analysis is not None,
"research_capabilities": research_preferences is not None,
"persona_generation": persona_readiness.get("ready", False),
"content_optimization": website_analysis is not None and research_preferences is not None
}
async def get_website_analysis_data(self) -> Optional[Dict[str, Any]]:
"""Get website analysis data for FinalStep."""
try:
analysis = self._get_website_analysis()
if analysis:
return {
"website_url": analysis.get('website_url'),
"style_analysis": analysis.get('style_analysis'),
"style_patterns": analysis.get('style_patterns'),
"style_guidelines": analysis.get('style_guidelines'),
"status": analysis.get('status'),
"completed_at": analysis.get('created_at')
}
else:
return None
except Exception as e:
logger.error(f"Error getting website analysis data: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
async def get_research_preferences_data(self) -> Optional[Dict[str, Any]]:
"""Get research preferences data for FinalStep."""
try:
return self._get_research_preferences()
except Exception as e:
logger.error(f"Error getting research preferences data: {str(e)}")
raise HTTPException(status_code=500, detail="Internal server error")
return capabilities

View File

@@ -19,6 +19,10 @@ from alwrity_utils import HealthChecker, RateLimiter, FrontendServing, RouterMan
# Load environment variables
load_dotenv()
# Set up clean logging for end users
from logging_config import setup_clean_logging
setup_clean_logging()
# Import middleware
from middleware.auth_middleware import get_current_user

View File

@@ -1,27 +1,29 @@
# Clerk Authentication
CLERK_SECRET_KEY=your_clerk_secret_key_here
CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key_here
# ALwrity Backend Configuration
# Google Search Console
GSC_REDIRECT_URI=your-domain-name/gsc/callback
# API Keys (Configure these in the onboarding process)
# OPENAI_API_KEY=your_openai_api_key_here
GEMINI_API_KEY=your_gemini_api_key_here
# ANTHROPIC_API_KEY=your_anthropic_api_key_here
# MISTRAL_API_KEY=your_mistral_api_key_here
# Wix Integration (Headless OAuth - Client ID only, no Client Secret required)
WIX_CLIENT_ID=
WIX_REDIRECT_URI=your-domain-name/wix/callback
# Research API Keys (Optional)
# TAVILY_API_KEY=your_tavily_api_key_here
# SERPER_API_KEY=your_serper_api_key_here
EXA_API_KEY=your_exa_api_key_here
# WordPress.com OAuth2 Integration
# IMPORTANT: You need to register a WordPress.com application to get valid credentials
# 1. Go to https://developer.wordpress.com/apps/
# 2. Create a new application
# 3. Set the redirect URI to: https://your-domain.com/wp/callback
# 4. Copy the Client ID and Client Secret below
# For development, these are placeholder values that may not work
WORDPRESS_CLIENT_ID=your_wordpress_com_client_id_here
WORDPRESS_CLIENT_SECRET=your_wordpress_com_client_secret_here
WORDPRESS_REDIRECT_URI=
# Authentication
# CLERK_SECRET_KEY=your_clerk_secret_key_here
# Development Settings
DISABLE_AUTH=false
# OAuth Redirect URIs
GSC_REDIRECT_URI=https://your-frontend.vercel.app/gsc/callback
WORDPRESS_REDIRECT_URI=https://your-frontend.vercel.app/wp/callback
WIX_REDIRECT_URI=https://your-frontend.vercel.app/wix/callback
# local development
DEPLOY_ENV=local
# Server Configuration
HOST=0.0.0.0
PORT=8000
DEBUG=true
# Logging
LOG_LEVEL=INFO

110
backend/logging_config.py Normal file
View File

@@ -0,0 +1,110 @@
"""
Logging configuration for ALwrity backend.
Provides clean logging setup for end users vs developers.
"""
import logging
import os
import sys
from loguru import logger
def setup_clean_logging():
"""Set up clean logging for end users."""
verbose_mode = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
if not verbose_mode:
# Suppress verbose logging for end users - be more aggressive
logging.getLogger('sqlalchemy.engine').setLevel(logging.CRITICAL)
logging.getLogger('sqlalchemy.pool').setLevel(logging.CRITICAL)
logging.getLogger('sqlalchemy.dialects').setLevel(logging.CRITICAL)
logging.getLogger('sqlalchemy.orm').setLevel(logging.CRITICAL)
logging.getLogger('sqlalchemy').setLevel(logging.CRITICAL)
logging.getLogger('sqlalchemy.engine.Engine').setLevel(logging.CRITICAL)
# Suppress service initialization logs
logging.getLogger('services').setLevel(logging.WARNING)
logging.getLogger('api').setLevel(logging.WARNING)
logging.getLogger('models').setLevel(logging.WARNING)
# Suppress specific noisy loggers
noisy_loggers = [
'linkedin_persona_service',
'facebook_persona_service',
'core_persona_service',
'persona_analysis_service',
'ai_service_manager',
'ai_engine_service',
'website_analyzer',
'competitor_analyzer',
'keyword_researcher',
'content_gap_analyzer',
'onboarding_data_service',
'comprehensive_user_data',
'strategy_data',
'gap_analysis_data',
'phase1_steps',
'daily_schedule_generator',
'gsc_service',
'wordpress_oauth',
'data_filter',
'source_mapper',
'grounding_engine',
'blog_content_seo_analyzer',
'linkedin_service',
'citation_manager',
'content_analyzer',
'linkedin_prompt_generator',
'linkedin_image_storage',
'hallucination_detector',
'writing_assistant',
'onboarding_data_service',
'enhanced_linguistic_analyzer',
'persona_quality_improver',
'logging_middleware',
'exa_service',
'step3_research_service',
'sitemap_service',
'linkedin_image_generator',
'linkedin_prompt_generator',
'linkedin_image_storage',
'router_manager',
'frontend_serving',
'database',
'user_business_info',
'auth_middleware',
'pricing_service',
'create_billing_tables'
]
for logger_name in noisy_loggers:
logging.getLogger(logger_name).setLevel(logging.WARNING)
# Configure loguru to be less verbose (only show warnings and errors)
logger.remove() # Remove default handler
def warning_only_filter(record):
return record["level"].name in ["WARNING", "ERROR", "CRITICAL"]
logger.add(
sys.stdout.write,
level="WARNING",
format="{time:HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}\n",
filter=warning_only_filter
)
else:
# In verbose mode, show all log levels with detailed formatting
logger.remove() # Remove default handler
logger.add(
sys.stdout.write,
level="DEBUG",
format="{time:HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}\n"
)
return verbose_mode
def get_uvicorn_log_level():
"""Get appropriate uvicorn log level based on verbose mode."""
verbose_mode = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
return "debug" if verbose_mode else "warning"

View File

@@ -6,7 +6,7 @@ from datetime import datetime
Base = declarative_base()
logger.info("🔄 Loading UserBusinessInfo model...")
logger.debug("🔄 Loading UserBusinessInfo model...")
class UserBusinessInfo(Base):
__tablename__ = 'user_business_info'
@@ -35,4 +35,4 @@ class UserBusinessInfo(Base):
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
logger.info("✅ UserBusinessInfo model loaded successfully!")
logger.debug("✅ UserBusinessInfo model loaded successfully!")

View File

@@ -26,12 +26,12 @@ def create_billing_tables():
try:
# Create engine
engine = create_engine(DATABASE_URL, echo=True)
engine = create_engine(DATABASE_URL, echo=False)
# Create all tables
logger.info("Creating billing and subscription system tables...")
logger.debug("Creating billing and subscription system tables...")
SubscriptionBase.metadata.create_all(bind=engine)
logger.info("✅ Billing and subscription tables created successfully")
logger.debug("✅ Billing and subscription tables created successfully")
# Create session for data initialization
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
@@ -41,13 +41,13 @@ def create_billing_tables():
# Initialize pricing and plans
pricing_service = PricingService(db)
logger.info("Initializing default API pricing...")
logger.debug("Initializing default API pricing...")
pricing_service.initialize_default_pricing()
logger.info("✅ Default API pricing initialized")
logger.debug("✅ Default API pricing initialized")
logger.info("Initializing default subscription plans...")
logger.debug("Initializing default subscription plans...")
pricing_service.initialize_default_plans()
logger.info("✅ Default subscription plans initialized")
logger.debug("✅ Default subscription plans initialized")
except Exception as e:
logger.error(f"Error initializing default data: {e}")
@@ -57,7 +57,7 @@ def create_billing_tables():
finally:
db.close()
logger.info("🎉 Billing system setup completed successfully!")
logger.info(" Billing system setup completed successfully!")
# Display summary
display_setup_summary(engine)
@@ -94,7 +94,7 @@ def display_setup_summary(engine):
logger.info(f"\n📊 Created Tables ({len(tables)}):")
for table in tables:
logger.info(f"{table[0]}")
logger.debug(f"{table[0]}")
# Check subscription plans
try:
@@ -114,7 +114,7 @@ def display_setup_summary(engine):
for plan in plans:
name, tier, monthly, yearly = plan
logger.info(f"{name} ({tier}): ${monthly}/month, ${yearly}/year")
logger.debug(f"{name} ({tier}): ${monthly}/month, ${yearly}/year")
except Exception as e:
logger.warning(f"Could not check subscription plans: {e}")
@@ -124,22 +124,22 @@ def display_setup_summary(engine):
result = conn.execute(pricing_query)
pricing_count = result.fetchone()[0]
logger.info(f"\n💰 API Pricing Entries: {pricing_count}")
if pricing_count > 0:
pricing_detail_query = text("""
SELECT provider, model_name, cost_per_input_token, cost_per_output_token
FROM api_provider_pricing
SELECT provider, model_name, cost_per_input_token, cost_per_output_token
FROM api_provider_pricing
WHERE cost_per_input_token > 0 OR cost_per_output_token > 0
ORDER BY provider, model_name
LIMIT 10
""")
result = conn.execute(pricing_detail_query)
pricing_entries = result.fetchall()
logger.info("\n LLM Pricing (per token) - Top 10:")
for entry in pricing_entries:
provider, model, input_cost, output_cost = entry
logger.info(f"{provider}/{model}: ${input_cost:.8f} in, ${output_cost:.8f} out")
logger.debug(f"{provider}/{model}: ${input_cost:.8f} in, ${output_cost:.8f} out")
except Exception as e:
logger.warning(f"Could not check API pricing: {e}")
@@ -183,7 +183,7 @@ def check_existing_tables(engine):
if existing_tables:
logger.warning(f"Found existing billing tables: {[t[0] for t in existing_tables]}")
logger.info("Tables already exist. Skipping creation to preserve data.")
logger.debug("Tables already exist. Skipping creation to preserve data.")
return False
return True
@@ -193,7 +193,7 @@ def check_existing_tables(engine):
return True # Proceed anyway
if __name__ == "__main__":
logger.info("🚀 Starting billing system database migration...")
logger.debug("🚀 Starting billing system database migration...")
try:
# Create engine to check existing tables
@@ -201,7 +201,7 @@ if __name__ == "__main__":
# Check existing tables
if not check_existing_tables(engine):
logger.info("✅ Billing tables already exist, skipping creation")
logger.debug("✅ Billing tables already exist, skipping creation")
sys.exit(0)
# Create tables and initialize data
@@ -210,7 +210,7 @@ if __name__ == "__main__":
logger.info("✅ Billing system migration completed successfully!")
except KeyboardInterrupt:
logger.info("Migration cancelled by user")
logger.warning("Migration cancelled by user")
sys.exit(0)
except Exception as e:
logger.error(f"❌ Migration failed: {e}")

View File

@@ -47,15 +47,26 @@ class AIServiceMetrics:
class AIServiceManager:
"""Centralized AI service management for content planning system."""
_instance = None
_initialized = False
def __new__(cls):
"""Implement singleton pattern to prevent multiple initializations."""
if cls._instance is None:
cls._instance = super(AIServiceManager, cls).__new__(cls)
return cls._instance
def __init__(self):
"""Initialize AI service manager."""
self.logger = logger
self.metrics: List[AIServiceMetrics] = []
self.prompts = self._load_centralized_prompts()
self.schemas = self._load_centralized_schemas()
self.config = self._load_ai_configuration()
logger.info("AIServiceManager initialized")
"""Initialize AI service manager (only once)."""
if not self._initialized:
self.logger = logger
self.metrics: List[AIServiceMetrics] = []
self.prompts = self._load_centralized_prompts()
self.schemas = self._load_centralized_schemas()
self.config = self._load_ai_configuration()
logger.debug("AIServiceManager initialized")
self._initialized = True
def _load_ai_configuration(self) -> Dict[str, Any]:
"""Load AI configuration settings."""

View File

@@ -24,10 +24,21 @@ from services.database import get_db_session
class AIEngineService:
"""AI engine for content planning insights and analysis."""
_instance = None
_initialized = False
def __new__(cls):
"""Implement singleton pattern to prevent multiple initializations."""
if cls._instance is None:
cls._instance = super(AIEngineService, cls).__new__(cls)
return cls._instance
def __init__(self):
"""Initialize the AI engine service."""
self.ai_service_manager = AIServiceManager()
logger.info("AIEngineService initialized")
"""Initialize the AI engine service (only once)."""
if not self._initialized:
self.ai_service_manager = AIServiceManager()
logger.debug("AIEngineService initialized")
self._initialized = True
async def analyze_content_gaps(self, analysis_summary: Dict[str, Any]) -> Dict[str, Any]:
"""

View File

@@ -18,13 +18,24 @@ from services.persona.facebook.facebook_persona_service import FacebookPersonaSe
class CorePersonaService:
"""Core service for generating writing personas using Gemini AI."""
_instance = None
_initialized = False
def __new__(cls):
"""Implement singleton pattern to prevent multiple initializations."""
if cls._instance is None:
cls._instance = super(CorePersonaService, cls).__new__(cls)
return cls._instance
def __init__(self):
"""Initialize the core persona service."""
self.data_collector = OnboardingDataCollector()
self.prompt_builder = PersonaPromptBuilder()
self.linkedin_service = LinkedInPersonaService()
self.facebook_service = FacebookPersonaService()
logger.info("CorePersonaService initialized")
"""Initialize the core persona service (only once)."""
if not self._initialized:
self.data_collector = OnboardingDataCollector()
self.prompt_builder = PersonaPromptBuilder()
self.linkedin_service = LinkedInPersonaService()
self.facebook_service = FacebookPersonaService()
logger.debug("CorePersonaService initialized")
self._initialized = True
def generate_core_persona(self, onboarding_data: Dict[str, Any]) -> Dict[str, Any]:
"""Generate core writing persona using Gemini structured response."""

View File

@@ -26,7 +26,7 @@ class EnhancedLinguisticAnalyzer:
import spacy
self.nlp = spacy.load("en_core_web_sm")
self.spacy_available = True
logger.info("SUCCESS: spaCy model loaded successfully - Enhanced linguistic analysis available")
logger.debug("SUCCESS: spaCy model loaded successfully - Enhanced linguistic analysis available")
except ImportError as e:
logger.error(f"ERROR: spaCy is REQUIRED for persona generation. Install with: pip install spacy && python -m spacy download en_core_web_sm")
raise ImportError("spaCy is required for enhanced persona generation. Install with: pip install spacy && python -m spacy download en_core_web_sm") from e

View File

@@ -20,14 +20,25 @@ from services.llm_providers.gemini_provider import gemini_structured_json_respon
class FacebookPersonaService:
"""Facebook-specific persona generation and optimization service."""
_instance = None
_initialized = False
def __new__(cls):
"""Implement singleton pattern to prevent multiple initializations."""
if cls._instance is None:
cls._instance = super(FacebookPersonaService, cls).__new__(cls)
return cls._instance
def __init__(self):
"""Initialize the Facebook persona service."""
self.schemas = FacebookPersonaSchema
self.constraints = FacebookPersonaConstraints()
self.validation = FacebookPersonaValidation()
self.optimization = FacebookPersonaOptimization()
self.prompts = FacebookPersonaPrompts()
logger.info("FacebookPersonaService initialized")
"""Initialize the Facebook persona service (only once)."""
if not self._initialized:
self.schemas = FacebookPersonaSchema
self.constraints = FacebookPersonaConstraints()
self.validation = FacebookPersonaValidation()
self.optimization = FacebookPersonaOptimization()
self.prompts = FacebookPersonaPrompts()
logger.debug("FacebookPersonaService initialized")
self._initialized = True
def generate_facebook_persona(self, core_persona: Dict[str, Any], onboarding_data: Dict[str, Any]) -> Dict[str, Any]:
"""

View File

@@ -14,11 +14,22 @@ from .linkedin_persona_schemas import LinkedInPersonaSchemas
class LinkedInPersonaService:
"""Service for generating LinkedIn-specific persona adaptations."""
_instance = None
_initialized = False
def __new__(cls):
"""Implement singleton pattern to prevent multiple initializations."""
if cls._instance is None:
cls._instance = super(LinkedInPersonaService, cls).__new__(cls)
return cls._instance
def __init__(self):
"""Initialize the LinkedIn persona service."""
self.prompts = LinkedInPersonaPrompts()
self.schemas = LinkedInPersonaSchemas()
logger.info("LinkedInPersonaService initialized")
"""Initialize the LinkedIn persona service (only once)."""
if not self._initialized:
self.prompts = LinkedInPersonaPrompts()
self.schemas = LinkedInPersonaSchemas()
logger.debug("LinkedInPersonaService initialized")
self._initialized = True
def generate_linkedin_persona(self, core_persona: Dict[str, Any], onboarding_data: Dict[str, Any]) -> Dict[str, Any]:
"""

View File

@@ -24,7 +24,7 @@ class PersonaQualityImprover:
def __init__(self):
"""Initialize the quality improver."""
self.linguistic_analyzer = EnhancedLinguisticAnalyzer()
logger.info("PersonaQualityImprover initialized")
logger.debug("PersonaQualityImprover initialized")
def assess_persona_quality_comprehensive(
self,

View File

@@ -19,13 +19,24 @@ from services.persona.facebook.facebook_persona_service import FacebookPersonaSe
class PersonaAnalysisService:
"""Service for analyzing onboarding data and generating writing personas using Gemini AI."""
_instance = None
_initialized = False
def __new__(cls):
"""Implement singleton pattern to prevent multiple initializations."""
if cls._instance is None:
cls._instance = super(PersonaAnalysisService, cls).__new__(cls)
return cls._instance
def __init__(self):
"""Initialize the persona analysis service."""
self.core_persona_service = CorePersonaService()
self.data_collector = OnboardingDataCollector()
self.linkedin_service = LinkedInPersonaService()
self.facebook_service = FacebookPersonaService()
logger.info("PersonaAnalysisService initialized")
"""Initialize the persona analysis service (only once)."""
if not self._initialized:
self.core_persona_service = CorePersonaService()
self.data_collector = OnboardingDataCollector()
self.linkedin_service = LinkedInPersonaService()
self.facebook_service = FacebookPersonaService()
logger.debug("PersonaAnalysisService initialized")
self._initialized = True
def generate_persona_from_onboarding(self, user_id: int, onboarding_session_id: int = None) -> Dict[str, Any]:
"""

View File

@@ -215,7 +215,7 @@ class PricingService:
self.db.add(pricing)
self.db.commit()
logger.info("Default API pricing initialized")
logger.debug("Default API pricing initialized")
def initialize_default_plans(self):
"""Initialize default subscription plans."""
@@ -318,7 +318,7 @@ class PricingService:
self.db.add(plan)
self.db.commit()
logger.info("Default subscription plans initialized")
logger.debug("Default subscription plans initialized")
def calculate_api_cost(self, provider: APIProvider, model_name: str,
tokens_input: int = 0, tokens_output: int = 0,

View File

@@ -17,28 +17,37 @@ def bootstrap_linguistic_models():
This prevents import-time failures when EnhancedLinguisticAnalyzer is loaded.
"""
import subprocess
import os
print("🔍 Bootstrapping linguistic models...")
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
if verbose:
print("🔍 Bootstrapping linguistic models...")
# Check and download spaCy model
try:
import spacy
try:
nlp = spacy.load("en_core_web_sm")
print(" ✅ spaCy model 'en_core_web_sm' available")
if verbose:
print(" ✅ spaCy model 'en_core_web_sm' available")
except OSError:
print(" ⚠️ spaCy model 'en_core_web_sm' not found, downloading...")
if verbose:
print(" ⚠️ spaCy model 'en_core_web_sm' not found, downloading...")
try:
subprocess.check_call([
sys.executable, "-m", "spacy", "download", "en_core_web_sm"
])
print(" ✅ spaCy model downloaded successfully")
if verbose:
print(" ✅ spaCy model downloaded successfully")
except subprocess.CalledProcessError as e:
print(f" ❌ Failed to download spaCy model: {e}")
print(" Please run: python -m spacy download en_core_web_sm")
if verbose:
print(f" ❌ Failed to download spaCy model: {e}")
print(" Please run: python -m spacy download en_core_web_sm")
return False
except ImportError:
print(" ⚠️ spaCy not installed - skipping")
if verbose:
print(" ⚠️ spaCy not installed - skipping")
# Check and download NLTK data
try:
@@ -52,25 +61,32 @@ def bootstrap_linguistic_models():
for data_package, path in essential_data:
try:
nltk.data.find(path)
print(f" ✅ NLTK {data_package} available")
if verbose:
print(f" ✅ NLTK {data_package} available")
except LookupError:
print(f" ⚠️ NLTK {data_package} not found, downloading...")
if verbose:
print(f" ⚠️ NLTK {data_package} not found, downloading...")
try:
nltk.download(data_package, quiet=True)
print(f" ✅ NLTK {data_package} downloaded")
if verbose:
print(f" ✅ NLTK {data_package} downloaded")
except Exception as e:
print(f" ⚠️ Failed to download {data_package}: {e}")
if verbose:
print(f" ⚠️ Failed to download {data_package}: {e}")
# Try fallback
if data_package == 'punkt_tab':
try:
nltk.download('punkt', quiet=True)
print(f" ✅ NLTK punkt (fallback) downloaded")
if verbose:
print(f" ✅ NLTK punkt (fallback) downloaded")
except:
pass
except ImportError:
print(" ⚠️ NLTK not installed - skipping")
if verbose:
print(" ⚠️ NLTK not installed - skipping")
print("✅ Linguistic model bootstrap complete")
if verbose:
print("✅ Linguistic model bootstrap complete")
return True
@@ -127,11 +143,10 @@ def start_backend(enable_reload=False, production_mode=False):
import uvicorn
# Explicitly initialize database before starting server
print("[DB] Initializing database...")
init_database()
print("[OK] Database initialized successfully")
print("\n🌐 Backend is starting...")
print("\n🌐 ALwrity Backend Server")
print("=" * 50)
print(" 📖 API Documentation: http://localhost:8000/api/docs")
print(" 🔍 Health Check: http://localhost:8000/health")
print(" 📊 ReDoc: http://localhost:8000/api/redoc")
@@ -142,12 +157,13 @@ def start_backend(enable_reload=False, production_mode=False):
print(" 📊 Usage Tracking: http://localhost:8000/api/subscription/usage/demo")
print("\n[STOP] Press Ctrl+C to stop the server")
print("=" * 60)
print("\n💡 Usage:")
print(" Production mode: python start_alwrity_backend.py --production")
print(" Development mode: python start_alwrity_backend.py --dev")
print(" With auto-reload: python start_alwrity_backend.py --reload")
print("=" * 60)
print("=" * 50)
# Set up clean logging for end users
from logging_config import setup_clean_logging, get_uvicorn_log_level
verbose_mode = setup_clean_logging()
uvicorn_log_level = get_uvicorn_log_level()
uvicorn.run(
"app:app",
@@ -186,7 +202,7 @@ def start_backend(enable_reload=False, production_mode=False):
"api/**/*.py",
"services/**/*.py"
],
log_level="info"
log_level=uvicorn_log_level
)
except KeyboardInterrupt:
@@ -205,16 +221,23 @@ def main():
parser.add_argument("--reload", action="store_true", help="Enable auto-reload for development")
parser.add_argument("--dev", action="store_true", help="Enable development mode (auto-reload)")
parser.add_argument("--production", action="store_true", help="Enable production mode (optimized for deployment)")
parser.add_argument("--verbose", action="store_true", help="Enable verbose logging for debugging")
args = parser.parse_args()
# Determine mode
production_mode = args.production
enable_reload = (args.reload or args.dev) and not production_mode
verbose_mode = args.verbose
print("ALwrity Backend Server")
# Set global verbose flag for utilities
os.environ["ALWRITY_VERBOSE"] = "true" if verbose_mode else "false"
print("🚀 ALwrity Backend Server")
print("=" * 40)
print(f"Mode: {'PRODUCTION' if production_mode else 'DEVELOPMENT'}")
print(f"Auto-reload: {'ENABLED' if enable_reload else 'DISABLED'}")
if verbose_mode:
print("Verbose logging: ENABLED")
print("=" * 40)
# Check if we're in the right directory
@@ -230,39 +253,59 @@ def main():
database_setup = DatabaseSetup(production_mode=production_mode)
production_optimizer = ProductionOptimizer()
# Setup progress tracking
setup_steps = [
"Checking dependencies",
"Setting up environment",
"Configuring database",
"Starting server"
]
print("🔧 Initializing ALwrity...")
# Apply production optimizations if needed
if production_mode:
if not production_optimizer.apply_production_optimizations():
print("[ERROR] Production optimization failed")
print(" Production optimization failed")
return False
# Check and install dependencies
# Step 1: Dependencies
print(f" 📦 {setup_steps[0]}...", end=" ", flush=True)
critical_ok, missing_critical = dependency_manager.check_critical_dependencies()
if not critical_ok:
print("[ERROR] Critical dependencies missing, installing...")
print("installing...", end=" ", flush=True)
if not dependency_manager.install_requirements():
print("[ERROR] Failed to install dependencies")
print("❌ Failed")
return False
print("✅ Done")
else:
print("✅ Done")
# Check optional dependencies (non-critical)
dependency_manager.check_optional_dependencies()
# Check optional dependencies (non-critical) - only in verbose mode
if verbose_mode:
dependency_manager.check_optional_dependencies()
# Setup environment
# Step 2: Environment
print(f" 🔧 {setup_steps[1]}...", end=" ", flush=True)
if not environment_setup.setup_directories():
print("[ERROR] Directory setup failed")
print(" Directory setup failed")
return False
if not environment_setup.setup_environment_variables():
print("[ERROR] Environment variable setup failed")
print(" Environment setup failed")
return False
# Create .env file only in development
if not production_mode:
environment_setup.create_env_file()
print("✅ Done")
# Setup database
# Step 3: Database
print(f" 📊 {setup_steps[2]}...", end=" ", flush=True)
if not database_setup.setup_essential_tables():
print("[WARNING] Database setup had issues, continuing...")
print("⚠️ Issues detected, continuing...")
else:
print("✅ Done")
# Setup advanced features in development, verify in all modes
if not production_mode:
@@ -274,7 +317,8 @@ def main():
# Note: Linguistic models (spaCy/NLTK) are bootstrapped before imports
# See bootstrap_linguistic_models() at the top of this file
# Start backend
# Step 4: Start backend
print(f" 🚀 {setup_steps[3]}...")
return start_backend(enable_reload=enable_reload, production_mode=production_mode)