ALwrity + Wordpress + Wix + GSC integration

This commit is contained in:
ajaysi
2025-10-08 14:25:59 +05:30
parent 3bab3450dc
commit 5e3901c1c6
22 changed files with 2284 additions and 1222 deletions

167
.github/README.md vendored
View File

@@ -29,10 +29,59 @@ ALwrity is a **comprehensive AI-powered digital marketing platform** that revolu
- **🌍 Multi-Modal Content Creation**: Text, images, audio, and video content generation
- **📊 Data-Driven Insights**: Web research, competitor analysis, and predictive analytics
- **🤖 AI Agent Teams**: Specialized AI agents for different marketing tasks
- **🔗 Platform Integration**: Direct publishing to WordPress, social media, and more
- **🔗 Platform Integration**: Direct publishing to WordPress, Wix, Google Search Console, and more
- **📈 Performance Optimization**: Continuous learning and strategy refinement
- **🎯 Solopreneur-Focused**: Designed specifically for independent entrepreneurs
- **🛡️ Enterprise Security**: JWT authentication, rate limiting, and comprehensive monitoring
- **✨ Intelligent Onboarding**: AI-powered setup process that analyzes your business and generates personalized strategies
### 🚀 **NEW: Complete Onboarding & Integration System**
**Transform Your Digital Presence in 5 Simple Steps:**
1. **📧 Email Setup & Business Analysis** - AI analyzes your business domain and industry
2. **🎭 AI Persona Generation** - Creates detailed buyer personas and audience insights
3. **🏢 Business Information Collection** - Gathers comprehensive business data for personalized strategies
4. **🔍 Competitor Analysis** - Real-time competitor research and market positioning
5. **🔗 Platform Integrations** - Connect WordPress, Wix, Google Search Console with OAuth security
**🎯 User Impact**: Go from zero to fully optimized digital presence in under 15 minutes!
---
## 🚀 Getting Started (Live Now!)
### **⚡ Quick Start - 3 Steps to Success**
**1. Clone & Setup (2 minutes)**
```bash
git clone https://github.com/AJaySi/AI-Writer.git
cd AI-Writer/backend && pip install -r requirements.txt
cd ../frontend && npm install
```
**2. Launch Platform (1 minute)**
```bash
# Terminal 1: Backend
cd backend && python start_alwrity_backend.py
# Terminal 2: Frontend
cd frontend && npm start
```
**3. Access & Create (Instant)**
- **Frontend**: http://localhost:3000
- **API Docs**: http://localhost:8000/api/docs
- **Complete onboarding** → **Generate content****Publish everywhere**
### **🎯 What You'll Get Immediately:**
-**AI-powered business analysis** and strategy generation
-**LinkedIn content creation** with fact-checking and Google grounding
-**Blog writing** with research, SEO optimization, and metadata
-**Facebook content generation** with platform-specific optimization
-**WordPress & Wix integration** with OAuth security
-**Google Search Console** analytics and insights
-**Competitor analysis** and market intelligence
---
@@ -67,6 +116,9 @@ ALwrity is a **comprehensive AI-powered digital marketing platform** that revolu
- **Google Search Console Integration**: OAuth2 authentication and real-time analytics
- **Hallucination Detection**: AI-powered fact-checking and content verification
- **Persona System**: Advanced writing persona generation and management
- **Google Grounding**: Real-time fact verification using Google Search API
- **Exa AI Integration**: Advanced semantic search and content discovery
- **Assistive Writing**: Real-time writing suggestions and optimization
### **✅ Frontend Development - COMPLETE**
- **React Application**: Modern TypeScript-based frontend with Material-UI
@@ -75,7 +127,9 @@ ALwrity is a **comprehensive AI-powered digital marketing platform** that revolu
- **Real-time Updates**: Live progress tracking and notifications
- **Blog Writer Interface**: Complete WYSIWYG editor with research integration
- **SEO Dashboard**: Comprehensive SEO analysis and metadata generation tools
- **Onboarding System**: Multi-step guided setup with business information collection
- **Complete Onboarding System**: 5-step AI-powered setup with business analysis
- **Platform Integrations**: WordPress, Wix, Google Search Console with OAuth
- **Coming Soon Section**: Interactive preview of upcoming features
### **📅 Launch Timeline**
- **Current**: Full platform operational with all core features
@@ -100,11 +154,39 @@ ALwrity is a **comprehensive AI-powered digital marketing platform** that revolu
| **Platform** | **Content Types** | **Status** |
|--------------|------------------|------------|
| **LinkedIn** | Posts, Articles, Carousels, Video Scripts, Comments, Fact-Checking | ✅ Complete |
| **LinkedIn** | Posts, Articles, Carousels, Video Scripts, Comments, Fact-Checking, Google Grounding | ✅ Complete |
| **Facebook** | Posts, Stories, Ads, Community Content | ✅ Complete |
| **Blog Writer** | Research, Outline, Content Generation, SEO Analysis, Metadata | ✅ Complete |
| **Blog Writer** | Research, Outline, Content Generation, SEO Analysis, Metadata, Exa AI Integration | ✅ Complete |
| **SEO Content** | Blog posts, landing pages, technical content | ✅ Complete |
| **General Content** | Long-form articles, social media posts | ✅ Complete |
| **Assistive Writing** | Real-time suggestions, grammar checking, tone optimization | ✅ Complete |
### 🔍 **Advanced Research & Fact-Checking**
| **Feature** | **AI Capabilities** | **Status** |
|-------------|-------------------|------------|
| **Google Grounding** | Real-time fact verification using Google Search | ✅ Complete |
| **Exa AI Integration** | Semantic search and content discovery | ✅ Complete |
| **Fact-Checking Engine** | AI-powered content verification and source validation | ✅ Complete |
| **Web Research** | Automated competitor analysis and market intelligence | ✅ Complete |
| **Citation Management** | Automatic source tracking and verification | ✅ Complete |
### 🚀 **Complete Onboarding System**
| **Step** | **AI Capabilities** | **User Impact** |
|----------|-------------------|-----------------|
| **📧 Email & Business Analysis** | AI analyzes your domain, industry, and business model | **Instant business insights** and personalized recommendations |
| **🎭 AI Persona Generation** | Creates detailed buyer personas with demographic and psychographic data | **Target the right audience** with precision marketing strategies |
| **🏢 Business Information** | Collects comprehensive business data for strategy personalization | **Customized content strategies** that align with your business goals |
| **🔍 Competitor Analysis** | Real-time competitor research using Exa AI and web scraping | **Stay ahead of competition** with data-driven market positioning |
| **🔗 Platform Integrations** | OAuth-secured connections to WordPress, Wix, Google Search Console | **Publish everywhere** with one-click integration and real-time analytics |
**🎯 User Benefits:**
- **15-minute setup** from zero to fully optimized digital presence
- **Professional marketing strategy** without hiring agencies
- **Automated competitor intelligence** for strategic advantage
- **One-click publishing** across all major platforms
- **Real-time performance tracking** with actionable insights
### 🔍 **Advanced SEO & Technical Optimization**
@@ -117,6 +199,23 @@ ALwrity is a **comprehensive AI-powered digital marketing platform** that revolu
| **SEO Metadata** | Automated title, description, Open Graph, Twitter Cards | ✅ Complete |
| **Google Search Console** | OAuth2 integration, real-time analytics, sitemap analysis | ✅ Complete |
### 🔗 **Platform Integrations & Publishing**
| **Integration** | **Features** | **User Impact** |
|-----------------|-------------|-----------------|
| **WordPress OAuth** | Direct publishing, media management, category/tag sync | **One-click publishing** to WordPress sites with full content optimization |
| **Wix Integration** | Blog post creation, media upload, SEO optimization | **Seamless Wix publishing** with automatic SEO metadata generation |
| **Google Search Console** | Real-time analytics, search performance, keyword tracking | **Data-driven optimization** with actual search performance insights |
| **LinkedIn Publishing** | Direct post creation, article publishing, engagement tracking | **Professional content** published directly to LinkedIn with analytics |
| **Facebook Integration** | Post scheduling, media upload, audience targeting | **Social media automation** with platform-specific optimization |
**🎯 Integration Benefits:**
- **OAuth security** - No password sharing, enterprise-grade authentication
- **Real-time analytics** - Performance tracking across all platforms
- **Automated SEO** - Every published piece optimized for search engines
- **Content synchronization** - Consistent branding and messaging across platforms
- **Performance insights** - Data-driven content optimization recommendations
### 🖼️ **AI Image Generation**
| **Feature** | **Capabilities** | **Status** |
@@ -136,6 +235,23 @@ ALwrity is a **comprehensive AI-powered digital marketing platform** that revolu
| **Automated Optimization** | Continuous strategy refinement | ✅ Complete |
| **Usage Tracking** | Comprehensive API usage and billing analytics | ✅ Complete |
### 🚀 **Coming Soon Features**
| **Feature** | **Status** | **Expected Impact** |
|-------------|------------|-------------------|
| **Social Media OAuth** | 🔄 Awaiting Platform Approval | **Automated LinkedIn & Facebook posting** with advanced scheduling |
| **Instagram Integration** | 📅 Planned | **Story creation, hashtag optimization, and visual content** |
| **Advanced WordPress Features** | 🔄 In Development | **Media library management, advanced SEO tools, auto-publishing** |
| **Mobile Application** | 📅 Q2 2025 | **Content creation and management on-the-go** |
| **AI Agent Marketplace** | 📅 Q3 2025 | **Specialized AI agents for specific marketing tasks** |
| **Enterprise White-Label** | 📅 Q3 2025 | **Customizable platform for agencies and enterprises** |
**🎯 Future Benefits:**
- **Complete social media automation** across all major platforms
- **Mobile-first content creation** for busy entrepreneurs
- **AI agent ecosystem** for specialized marketing tasks
- **Enterprise-grade customization** for agencies and large teams
### 🛡️ **Enterprise Features**
| **Feature** | **Capabilities** | **Status** |
@@ -195,34 +311,27 @@ ALwrity is a **comprehensive AI-powered digital marketing platform** that revolu
---
## 🚀 Getting Started (Right Now!)
## 🎯 **Start Creating Content Now**
### **1. Backend Setup**
```bash
cd backend
pip install -r requirements.txt
python start_alwrity_backend.py
```
### **Complete Onboarding Process**
1. **📧 Email Setup** - Enter your business email for AI analysis
2. **🎭 Persona Generation** - AI creates detailed buyer personas
3. **🏢 Business Info** - Provide comprehensive business details
4. **🔍 Competitor Analysis** - AI researches your competition
5. **🔗 Platform Integration** - Connect WordPress, Wix, GSC
### **2. Frontend Setup**
```bash
cd frontend
npm install
npm start
```
### **Immediate Content Creation**
-**LinkedIn Posts** with fact-checking and Google grounding
-**Blog Writing** with research and SEO optimization
-**Facebook Content** with platform-specific optimization
-**SEO Analysis** with metadata generation
-**Image Generation** with AI-powered visuals
### **3. Access the Platform**
- **Backend API**: http://localhost:8000/api/docs
- **Frontend**: http://localhost:3000
- **Health Check**: http://localhost:8000/health
### **4. Start Creating Content**
- Complete the AI-powered onboarding process
- Generate your personalized content strategy
- Create content across all platforms
- Track performance with real-time analytics
- Use the Blog Writer for complete blog creation
- Leverage SEO analysis and metadata generation
### **Publishing & Analytics**
- **One-click publishing** to WordPress and Wix
- **Real-time analytics** from Google Search Console
- **Performance tracking** across all platforms
- **Data-driven optimization** recommendations
---

293
DEPLOYMENT_GUIDE.md Normal file
View File

@@ -0,0 +1,293 @@
# 🚀 ALwrity Deployment Guide
## Vercel Deployment Strategy
Since Vercel is optimized for frontend applications, we'll deploy the frontend on Vercel and the backend on a separate platform.
### **Architecture Overview**
```
Frontend (Vercel) ←→ Backend (Railway/Render) ←→ Database (SQLite/PostgreSQL)
```
---
## 🎯 **Step-by-Step Deployment**
### **Part 1: Deploy Backend (Railway - Recommended)**
#### **Option A: Railway (Recommended)**
1. **Install Railway CLI:**
```bash
npm install -g @railway/cli
```
2. **Login to Railway:**
```bash
railway login
```
3. **Initialize Project:**
```bash
cd backend
railway init
```
4. **Add Environment Variables:**
```bash
railway variables set GEMINI_API_KEY=your_gemini_key
railway variables set OPENAI_API_KEY=your_openai_key
railway variables set ANTHROPIC_API_KEY=your_anthropic_key
railway variables set MISTRAL_API_KEY=your_mistral_key
railway variables set TAVILY_API_KEY=your_tavily_key
railway variables set EXA_API_KEY=your_exa_key
railway variables set SERPER_API_KEY=your_serper_key
railway variables set CLERK_SECRET_KEY=your_clerk_secret
railway variables set GSC_REDIRECT_URI=https://your-frontend.vercel.app/gsc/callback
railway variables set WORDPRESS_REDIRECT_URI=https://your-frontend.vercel.app/wp/callback
railway variables set WIX_REDIRECT_URI=https://your-frontend.vercel.app/wix/callback
```
5. **Deploy:**
```bash
railway up
```
6. **Get Backend URL:**
```bash
railway domain
```
Copy the URL (e.g., `https://your-app.railway.app`)
#### **Option B: Render**
1. Go to [render.com](https://render.com) and connect your GitHub
2. Create a new "Web Service"
3. Select your repository
4. Configure:
- **Build Command:** `pip install -r requirements.txt`
- **Start Command:** `python start_alwrity_backend.py`
- **Environment:** Python 3
5. Add all environment variables in the dashboard
6. Deploy and get your backend URL
---
### **Part 2: Deploy Frontend (Vercel)**
#### **1. Prepare Frontend for Production**
Create a `.env.production` file in the `frontend` directory:
```env
REACT_APP_API_URL=https://your-backend-url.railway.app
REACT_APP_ENVIRONMENT=production
```
#### **2. Deploy to Vercel**
1. **Connect to Vercel:**
- Go to [vercel.com](https://vercel.com)
- Click "New Project"
- Import your GitHub repository
2. **Configure Project:**
- **Framework Preset:** Create React App
- **Root Directory:** `frontend`
- **Build Command:** `npm run build`
- **Output Directory:** `build`
3. **Add Environment Variables:**
```
REACT_APP_API_URL = https://your-backend-url.railway.app
REACT_APP_ENVIRONMENT = production
```
4. **Deploy:**
- Click "Deploy"
- Wait for deployment to complete
- Get your frontend URL (e.g., `https://your-app.vercel.app`)
#### **3. Update Backend URLs**
After getting your frontend URL, update the backend environment variables:
```bash
# Update redirect URIs with your actual Vercel URL
railway variables set GSC_REDIRECT_URI=https://your-app.vercel.app/gsc/callback
railway variables set WORDPRESS_REDIRECT_URI=https://your-app.vercel.app/wp/callback
railway variables set WIX_REDIRECT_URI=https://your-app.vercel.app/wix/callback
```
---
## 🔧 **Alternative: Full-Stack on Vercel (Advanced)**
If you want to deploy everything on Vercel, you can use Vercel's serverless functions:
### **1. Create API Routes**
Create `api/` directory in your project root:
```
api/
├── auth/
│ └── [...auth].ts
├── content/
│ └── generate.ts
└── seo/
└── analyze.ts
```
### **2. Convert FastAPI to Vercel Functions**
This requires significant refactoring of your backend code to work with Vercel's serverless functions.
### **3. Deploy as Monorepo**
```bash
# Install Vercel CLI
npm i -g vercel
# Deploy
vercel --prod
```
---
## 🛠️ **Environment Variables Checklist**
### **Backend Environment Variables:**
```bash
# AI API Keys
GEMINI_API_KEY=your_gemini_key
OPENAI_API_KEY=your_openai_key
ANTHROPIC_API_KEY=your_anthropic_key
MISTRAL_API_KEY=your_mistral_key
# Research APIs
TAVILY_API_KEY=your_tavily_key
EXA_API_KEY=your_exa_key
SERPER_API_KEY=your_serper_key
# Authentication
CLERK_SECRET_KEY=your_clerk_secret
# OAuth Redirects (update with your Vercel URL)
GSC_REDIRECT_URI=https://your-app.vercel.app/gsc/callback
WORDPRESS_REDIRECT_URI=https://your-app.vercel.app/wp/callback
WIX_REDIRECT_URI=https://your-app.vercel.app/wix/callback
```
### **Frontend Environment Variables:**
```bash
REACT_APP_API_URL=https://your-backend-url.railway.app
REACT_APP_ENVIRONMENT=production
```
---
## 🚀 **Post-Deployment Steps**
### **1. Test Your Deployment**
- Visit your Vercel URL
- Complete the onboarding process
- Test content generation
- Verify OAuth integrations
### **2. Configure Custom Domain (Optional)**
- In Vercel dashboard, go to your project
- Click "Domains"
- Add your custom domain
- Update redirect URIs with custom domain
### **3. Monitor Performance**
- Check Vercel analytics
- Monitor Railway/Render logs
- Set up error tracking (Sentry, etc.)
---
## 🔍 **Troubleshooting**
### **Common Issues:**
1. **CORS Errors:**
- Add your Vercel domain to backend CORS settings
- Update `allowed_origins` in FastAPI CORS middleware
2. **Environment Variables:**
- Ensure all variables are set in both platforms
- Check variable names match exactly
3. **OAuth Redirects:**
- Update all redirect URIs with production URLs
- Test OAuth flows end-to-end
4. **Database Issues:**
- Consider upgrading to PostgreSQL for production
- Set up database backups
### **Performance Optimization:**
1. **Frontend:**
- Enable Vercel's edge caching
- Optimize bundle size
- Use CDN for static assets
2. **Backend:**
- Implement connection pooling
- Add caching layers
- Monitor memory usage
---
## 📊 **Monitoring & Maintenance**
### **Health Checks:**
- Frontend: `https://your-app.vercel.app/health`
- Backend: `https://your-backend.railway.app/health`
### **Logs:**
- Vercel: Built-in function logs
- Railway: `railway logs`
- Render: Dashboard logs section
### **Scaling:**
- Vercel: Automatic scaling
- Railway: Manual scaling in dashboard
- Render: Auto-scaling available
---
## 🎯 **Recommended Architecture**
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Frontend │ │ Backend │ │ Database │
│ (Vercel) │◄──►│ (Railway) │◄──►│ (PostgreSQL) │
│ │ │ │ │ │
│ • React App │ │ • FastAPI │ │ • User Data │
│ • Static Files │ │ • AI Services │ │ • Content │
│ • CDN Cached │ │ • OAuth APIs │ │ • Analytics │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
This setup provides:
-**Fast frontend delivery** via Vercel's global CDN
-**Scalable backend** with Railway's infrastructure
-**Reliable database** with PostgreSQL
-**Easy maintenance** with separate concerns
-**Cost-effective** scaling based on usage
---
## 🚀 **Next Steps**
1. **Deploy backend** to Railway/Render
2. **Deploy frontend** to Vercel
3. **Configure environment variables**
4. **Test all integrations**
5. **Set up monitoring**
6. **Configure custom domain** (optional)
Your ALwrity platform will be live and accessible worldwide! 🌍

View File

@@ -0,0 +1,26 @@
"""
ALwrity Utilities Package
Modular utilities for ALwrity backend startup and configuration.
"""
from .dependency_manager import DependencyManager
from .environment_setup import EnvironmentSetup
from .database_setup import DatabaseSetup
from .production_optimizer import ProductionOptimizer
from .health_checker import HealthChecker
from .rate_limiter import RateLimiter
from .frontend_serving import FrontendServing
from .router_manager import RouterManager
from .onboarding_manager import OnboardingManager
__all__ = [
'DependencyManager',
'EnvironmentSetup',
'DatabaseSetup',
'ProductionOptimizer',
'HealthChecker',
'RateLimiter',
'FrontendServing',
'RouterManager',
'OnboardingManager'
]

View File

@@ -0,0 +1,172 @@
"""
Database Setup Module
Handles database initialization and table creation.
"""
from typing import List, Tuple
import sys
from pathlib import Path
class DatabaseSetup:
"""Manages database setup for ALwrity backend."""
def __init__(self, production_mode: bool = False):
self.production_mode = production_mode
def setup_essential_tables(self) -> bool:
"""Set up essential database tables."""
print("📊 Setting up essential database tables...")
try:
from services.database import init_database, engine
# Initialize database connection
init_database()
print(" ✅ Database connection initialized")
# Create essential tables
self._create_monitoring_tables()
self._create_subscription_tables()
self._create_persona_tables()
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
def _create_monitoring_tables(self) -> bool:
"""Create API monitoring tables."""
try:
from models.api_monitoring import Base as MonitoringBase
MonitoringBase.metadata.create_all(bind=engine)
print(" ✅ Monitoring tables created")
return True
except Exception as e:
print(f" ⚠️ Monitoring tables failed: {e}")
return True # Non-critical
def _create_subscription_tables(self) -> bool:
"""Create subscription and billing tables."""
try:
from models.subscription_models import Base as SubscriptionBase
SubscriptionBase.metadata.create_all(bind=engine)
print(" ✅ Subscription tables created")
return True
except Exception as e:
print(f" ⚠️ Subscription tables failed: {e}")
return True # Non-critical
def _create_persona_tables(self) -> bool:
"""Create persona analysis tables."""
try:
from models.persona_models import Base as PersonaBase
PersonaBase.metadata.create_all(bind=engine)
print(" ✅ Persona tables created")
return True
except Exception as e:
print(f" ⚠️ Persona tables failed: {e}")
return True # Non-critical
def verify_tables(self) -> bool:
"""Verify that essential tables exist."""
if self.production_mode:
print("⚠️ Skipping table verification in production mode")
return True
print("🔍 Verifying database tables...")
try:
from services.database import engine
from sqlalchemy import inspect
inspector = inspect(engine)
tables = inspector.get_table_names()
essential_tables = [
'api_monitoring_logs',
'subscription_plans',
'user_subscriptions'
]
existing_tables = [table for table in essential_tables if table in tables]
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}")
return True
except Exception as e:
print(f" ⚠️ Table verification failed: {e}")
return True # Non-critical
def setup_advanced_tables(self) -> bool:
"""Set up advanced tables (non-critical)."""
if self.production_mode:
print("⚠️ Skipping advanced table setup in production mode")
return True
print("🔧 Setting up advanced database features...")
try:
# Set up monitoring tables
self._setup_monitoring_tables()
# Set up billing tables
self._setup_billing_tables()
print("✅ Advanced database features configured")
return True
except Exception as e:
print(f"⚠️ Advanced table setup failed: {e}")
return True # Non-critical
def _setup_monitoring_tables(self) -> bool:
"""Set up API monitoring tables."""
try:
sys.path.append(str(Path(__file__).parent.parent))
from scripts.create_monitoring_tables import create_monitoring_tables
if create_monitoring_tables():
print(" ✅ API monitoring tables created")
return True
else:
print(" ⚠️ API monitoring setup failed")
return True # Non-critical
except Exception as e:
print(f" ⚠️ Monitoring setup failed: {e}")
return True # Non-critical
def _setup_billing_tables(self) -> bool:
"""Set up billing and subscription tables."""
try:
sys.path.append(str(Path(__file__).parent.parent))
from scripts.create_billing_tables import create_billing_tables, check_existing_tables
from services.database import engine
# Check if tables already exist
if check_existing_tables(engine):
print(" ✅ Billing tables already exist")
return True
if create_billing_tables():
print(" ✅ Billing tables created")
return True
else:
print(" ⚠️ Billing setup failed")
return True # Non-critical
except Exception as e:
print(f" ⚠️ Billing setup failed: {e}")
return True # Non-critical

View File

@@ -0,0 +1,150 @@
"""
Dependency Management Module
Handles installation and verification of Python dependencies.
"""
import sys
import subprocess
from pathlib import Path
from typing import List, Tuple
class DependencyManager:
"""Manages Python package dependencies for ALwrity backend."""
def __init__(self, requirements_file: str = "requirements.txt"):
self.requirements_file = Path(requirements_file)
self.critical_packages = [
'fastapi',
'uvicorn',
'pydantic',
'sqlalchemy',
'loguru'
]
self.optional_packages = [
'openai',
'google.generativeai',
'anthropic',
'mistralai',
'spacy',
'nltk'
]
def install_requirements(self) -> bool:
"""Install packages from requirements.txt."""
print("📦 Installing required packages...")
if not self.requirements_file.exists():
print(f"❌ Requirements file not found: {self.requirements_file}")
return False
try:
subprocess.check_call([
sys.executable, "-m", "pip", "install", "-r", str(self.requirements_file)
])
print("✅ All packages installed successfully!")
return True
except subprocess.CalledProcessError as e:
print(f"❌ Error installing packages: {e}")
return False
def check_critical_dependencies(self) -> Tuple[bool, List[str]]:
"""Check if critical dependencies are available."""
print("🔍 Checking critical dependencies...")
missing_packages = []
for package in self.critical_packages:
try:
__import__(package.replace('-', '_'))
print(f"{package}")
except ImportError:
print(f"{package} - MISSING")
missing_packages.append(package)
if missing_packages:
print(f"❌ Missing critical packages: {', '.join(missing_packages)}")
return False, missing_packages
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...")
missing_packages = []
for package in self.optional_packages:
try:
__import__(package.replace('-', '_'))
print(f"{package}")
except ImportError:
print(f" ⚠️ {package} - MISSING (optional)")
missing_packages.append(package)
if missing_packages:
print(f"⚠️ Missing optional packages: {', '.join(missing_packages)}")
print(" Some features may not be available")
return len(missing_packages) == 0, missing_packages
def setup_spacy_model(self) -> bool:
"""Set up spaCy English model (production-optimized)."""
print("🧠 Setting up spaCy model...")
try:
import spacy
model_name = "en_core_web_sm"
try:
# Try to load the model
nlp = spacy.load(model_name)
test_doc = nlp("This is a test sentence.")
if test_doc and len(test_doc) > 0:
print(f"✅ spaCy model '{model_name}' is available")
return True
except OSError:
print(f"⚠️ spaCy model '{model_name}' not found")
print(" Skipping spaCy setup in production mode")
return True # Don't fail for missing spaCy in production
except ImportError:
print("⚠️ spaCy not installed - skipping model setup")
return True # Don't fail for missing spaCy
return True
def setup_nltk_data(self) -> bool:
"""Set up NLTK data (production-optimized)."""
print("📚 Setting up NLTK data...")
try:
import nltk
# Only download essential data
essential_data = ['punkt', 'stopwords']
for data_package in essential_data:
try:
nltk.data.find(f'tokenizers/{data_package}' if data_package == 'punkt'
else f'corpora/{data_package}')
print(f"{data_package}")
except LookupError:
print(f" ⚠️ {data_package} - downloading...")
try:
nltk.download(data_package, quiet=True)
print(f"{data_package} downloaded")
except Exception as e:
print(f" ⚠️ {data_package} download failed: {e}")
print("✅ NLTK data setup complete")
return True
except ImportError:
print("⚠️ NLTK not installed - skipping data setup")
return True # Don't fail for missing NLTK
return True

View File

@@ -0,0 +1,147 @@
"""
Environment Setup Module
Handles environment configuration and directory setup.
"""
import os
from pathlib import Path
from typing import List, Dict, Any
class EnvironmentSetup:
"""Manages environment setup for ALwrity backend."""
def __init__(self, production_mode: bool = False):
self.production_mode = production_mode
# Use safer directory paths that don't conflict with deployment platforms
if production_mode:
# In production, use temp directories or skip directory creation
self.required_directories = []
else:
# In development, use local directories
self.required_directories = [
"lib/workspace/alwrity_content",
"lib/workspace/alwrity_web_research",
"lib/workspace/alwrity_prompts",
"lib/workspace/alwrity_config"
]
def setup_directories(self) -> bool:
"""Create necessary directories for ALwrity."""
print("📁 Setting up directories...")
if not self.required_directories:
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}")
except Exception as e:
print(f" ❌ Failed to create {directory}: {e}")
return False
print("✅ All directories created successfully")
return True
def setup_environment_variables(self) -> bool:
"""Set up environment variables for the application."""
print("🔧 Setting up environment variables...")
# Production environment variables
if self.production_mode:
env_vars = {
"HOST": "0.0.0.0",
"PORT": "8000",
"RELOAD": "false",
"LOG_LEVEL": "INFO",
"DEBUG": "false"
}
else:
env_vars = {
"HOST": "0.0.0.0",
"PORT": "8000",
"RELOAD": "true",
"LOG_LEVEL": "DEBUG",
"DEBUG": "true"
}
for key, value in env_vars.items():
os.environ.setdefault(key, value)
print(f"{key}={value}")
print("✅ Environment variables configured")
return True
def create_env_file(self) -> bool:
"""Create .env file with default configuration (development only)."""
if self.production_mode:
print("⚠️ Skipping .env file creation in production mode")
return True
print("🔧 Creating .env file...")
env_file = Path(".env")
if env_file.exists():
print(" ✅ .env file already exists")
return True
env_content = """# ALwrity Backend Configuration
# 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
# 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
# Authentication
# CLERK_SECRET_KEY=your_clerk_secret_key_here
# 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
# Server Configuration
HOST=0.0.0.0
PORT=8000
DEBUG=true
# Logging
LOG_LEVEL=INFO
"""
try:
with open(env_file, 'w') as f:
f.write(env_content)
print("✅ .env file created successfully")
return True
except Exception as e:
print(f"❌ Error creating .env file: {e}")
return False
def verify_environment(self) -> bool:
"""Verify that the environment is properly configured."""
print("🔍 Verifying environment setup...")
# Check required directories
for directory in self.required_directories:
if not Path(directory).exists():
print(f"❌ Directory missing: {directory}")
return False
# Check environment variables
required_vars = ["HOST", "PORT", "LOG_LEVEL"]
for var in required_vars:
if not os.getenv(var):
print(f"❌ Environment variable missing: {var}")
return False
print("✅ Environment verification complete")
return True

View File

@@ -0,0 +1,82 @@
"""
Frontend Serving Module
Handles React frontend serving and static file mounting.
"""
import os
from pathlib import Path
from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from loguru import logger
from typing import Dict, Any
class FrontendServing:
"""Manages React frontend serving and static file mounting."""
def __init__(self, app: FastAPI):
self.app = app
self.frontend_build_path = os.path.join(os.path.dirname(__file__), "..", "..", "frontend", "build")
self.static_path = os.path.join(self.frontend_build_path, "static")
def setup_frontend_serving(self) -> bool:
"""Set up React frontend serving and static file mounting."""
try:
logger.info("Setting up frontend serving...")
# Mount static files for React app (only if directory exists)
if os.path.exists(self.static_path):
self.app.mount("/static", StaticFiles(directory=self.static_path), name="static")
logger.info("Frontend static files mounted successfully")
return True
else:
logger.info("Frontend build directory not found. Static files not mounted.")
return False
except Exception as e:
logger.error(f"Could not mount static files: {e}")
return False
def serve_frontend(self) -> FileResponse | Dict[str, Any]:
"""Serve the React frontend."""
try:
# Check if frontend build exists
index_html = os.path.join(self.frontend_build_path, "index.html")
if os.path.exists(index_html):
return FileResponse(index_html)
else:
return {
"message": "Frontend not built. Please run 'npm run build' in the frontend directory.",
"api_docs": "/api/docs"
}
except Exception as e:
logger.error(f"Error serving frontend: {e}")
return {
"message": "Error serving frontend",
"error": str(e),
"api_docs": "/api/docs"
}
def get_frontend_status(self) -> Dict[str, Any]:
"""Get the status of frontend build and serving."""
try:
index_html = os.path.join(self.frontend_build_path, "index.html")
static_exists = os.path.exists(self.static_path)
return {
"frontend_build_path": self.frontend_build_path,
"static_path": self.static_path,
"index_html_exists": os.path.exists(index_html),
"static_files_exist": static_exists,
"frontend_ready": os.path.exists(index_html) and static_exists
}
except Exception as e:
logger.error(f"Error checking frontend status: {e}")
return {
"error": str(e),
"frontend_ready": False
}

View File

@@ -0,0 +1,129 @@
"""
Health Check Module
Handles health check endpoints and database health verification.
"""
from fastapi import HTTPException
from datetime import datetime
from typing import Dict, Any
from loguru import logger
class HealthChecker:
"""Manages health check functionality for ALwrity backend."""
def __init__(self):
self.startup_time = datetime.utcnow()
def basic_health_check(self) -> Dict[str, Any]:
"""Basic health check endpoint."""
try:
return {
"status": "healthy",
"message": "ALwrity backend is running",
"timestamp": datetime.utcnow().isoformat(),
"uptime": str(datetime.utcnow() - self.startup_time)
}
except Exception as e:
logger.error(f"Health check failed: {e}")
return {
"status": "error",
"message": f"Health check failed: {str(e)}",
"timestamp": datetime.utcnow().isoformat()
}
def database_health_check(self) -> Dict[str, Any]:
"""Database health check endpoint including persona tables verification."""
try:
from services.database import get_db_session
from models.persona_models import (
WritingPersona,
PlatformPersona,
PersonaAnalysisResult,
PersonaValidationResult
)
session = get_db_session()
if not session:
return {
"status": "error",
"message": "Could not get database session",
"timestamp": datetime.utcnow().isoformat()
}
# Test all persona tables
tables_status = {}
try:
session.query(WritingPersona).first()
tables_status["writing_personas"] = "ok"
except Exception as e:
tables_status["writing_personas"] = f"error: {str(e)}"
try:
session.query(PlatformPersona).first()
tables_status["platform_personas"] = "ok"
except Exception as e:
tables_status["platform_personas"] = f"error: {str(e)}"
try:
session.query(PersonaAnalysisResult).first()
tables_status["persona_analysis_results"] = "ok"
except Exception as e:
tables_status["persona_analysis_results"] = f"error: {str(e)}"
try:
session.query(PersonaValidationResult).first()
tables_status["persona_validation_results"] = "ok"
except Exception as e:
tables_status["persona_validation_results"] = f"error: {str(e)}"
session.close()
# Check if all tables are ok
all_ok = all(status == "ok" for status in tables_status.values())
return {
"status": "healthy" if all_ok else "warning",
"message": "Database connection successful" if all_ok else "Some persona tables may have issues",
"persona_tables": tables_status,
"timestamp": datetime.utcnow().isoformat()
}
except Exception as e:
logger.error(f"Database health check failed: {e}")
return {
"status": "error",
"message": f"Database health check failed: {str(e)}",
"timestamp": datetime.utcnow().isoformat()
}
def comprehensive_health_check(self) -> Dict[str, Any]:
"""Comprehensive health check including all services."""
try:
# Basic health
basic_health = self.basic_health_check()
# Database health
db_health = self.database_health_check()
# Determine overall status
overall_status = "healthy"
if basic_health["status"] != "healthy" or db_health["status"] == "error":
overall_status = "unhealthy"
elif db_health["status"] == "warning":
overall_status = "degraded"
return {
"status": overall_status,
"basic": basic_health,
"database": db_health,
"timestamp": datetime.utcnow().isoformat()
}
except Exception as e:
logger.error(f"Comprehensive health check failed: {e}")
return {
"status": "error",
"message": f"Comprehensive health check failed: {str(e)}",
"timestamp": datetime.utcnow().isoformat()
}

View File

@@ -0,0 +1,455 @@
"""
Onboarding Manager Module
Handles all onboarding-related endpoints and functionality.
"""
from fastapi import FastAPI, HTTPException, Depends, BackgroundTasks
from fastapi.responses import FileResponse
from typing import Dict, Any, Optional
from loguru import logger
# Import onboarding functions
from api.onboarding import (
health_check,
initialize_onboarding,
get_onboarding_status,
get_onboarding_progress_full,
get_step_data,
complete_step,
skip_step,
validate_step_access,
get_api_keys,
get_api_keys_for_onboarding,
save_api_key,
validate_api_keys,
start_onboarding,
complete_onboarding,
reset_onboarding,
get_resume_info,
get_onboarding_config,
get_provider_setup_info,
get_all_providers_info,
validate_provider_key,
get_enhanced_validation_status,
get_onboarding_summary,
get_website_analysis_data,
get_research_preferences_data,
save_business_info,
get_business_info,
get_business_info_by_user,
update_business_info,
generate_writing_personas,
generate_writing_personas_async,
get_persona_task_status,
assess_persona_quality,
regenerate_persona,
get_persona_generation_options,
get_latest_persona,
save_persona_update,
StepCompletionRequest,
APIKeyRequest
)
from middleware.auth_middleware import get_current_user
class OnboardingManager:
"""Manages all onboarding-related endpoints and functionality."""
def __init__(self, app: FastAPI):
self.app = app
self.setup_onboarding_endpoints()
def setup_onboarding_endpoints(self):
"""Set up all onboarding-related endpoints."""
# Onboarding initialization - BATCH ENDPOINT (reduces 4 API calls to 1)
@self.app.get("/api/onboarding/init")
async def onboarding_init(current_user: dict = Depends(get_current_user)):
"""
Batch initialization endpoint - combines user info, status, and progress.
This eliminates 3-4 separate API calls on initial load, reducing latency by 60-75%.
"""
try:
return await initialize_onboarding(current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in onboarding_init: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Onboarding status endpoints
@self.app.get("/api/onboarding/status")
async def onboarding_status(current_user: dict = Depends(get_current_user)):
"""Get the current onboarding status."""
try:
return await get_onboarding_status(current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in onboarding_status: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/progress")
async def onboarding_progress(current_user: dict = Depends(get_current_user)):
"""Get the full onboarding progress data."""
try:
return await get_onboarding_progress_full(current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in onboarding_progress: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Step management endpoints
@self.app.get("/api/onboarding/step/{step_number}")
async def step_data(step_number: int, current_user: dict = Depends(get_current_user)):
"""Get data for a specific step."""
try:
return await get_step_data(step_number, current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in step_data: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/api/onboarding/step/{step_number}/complete")
async def step_complete(step_number: int, request: StepCompletionRequest, current_user: dict = Depends(get_current_user)):
"""Mark a step as completed."""
try:
return await complete_step(step_number, request, current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in step_complete: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/api/onboarding/step/{step_number}/skip")
async def step_skip(step_number: int, current_user: dict = Depends(get_current_user)):
"""Skip a step (for optional steps)."""
try:
return await skip_step(step_number, current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in step_skip: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/step/{step_number}/validate")
async def step_validate(step_number: int, current_user: dict = Depends(get_current_user)):
"""Validate if user can access a specific step."""
try:
return await validate_step_access(step_number, current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in step_validate: {e}")
raise HTTPException(status_code=500, detail=str(e))
# API key management endpoints
@self.app.get("/api/onboarding/api-keys")
async def api_keys():
"""Get all configured API keys (masked)."""
try:
return await get_api_keys()
except Exception as e:
logger.error(f"Error in api_keys: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/api-keys/onboarding")
async def api_keys_for_onboarding():
"""Get all configured API keys for onboarding (unmasked)."""
try:
return await get_api_keys_for_onboarding()
except Exception as e:
logger.error(f"Error in api_keys_for_onboarding: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/api/onboarding/api-keys")
async def api_key_save(request: APIKeyRequest):
"""Save an API key for a provider."""
try:
return await save_api_key(request)
except Exception as e:
logger.error(f"Error in api_key_save: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/api/onboarding/api-keys/validate")
async def api_key_validate():
"""Validate all configured API keys."""
try:
return await validate_api_keys()
except Exception as e:
logger.error(f"Error in api_key_validate: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Onboarding control endpoints
@self.app.post("/api/onboarding/start")
async def onboarding_start(current_user: dict = Depends(get_current_user)):
"""Start a new onboarding session."""
try:
return await start_onboarding(current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in onboarding_start: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/api/onboarding/complete")
async def onboarding_complete(current_user: dict = Depends(get_current_user)):
"""Complete the onboarding process."""
try:
return await complete_onboarding(current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in onboarding_complete: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/api/onboarding/reset")
async def onboarding_reset():
"""Reset the onboarding progress."""
try:
return await reset_onboarding()
except Exception as e:
logger.error(f"Error in onboarding_reset: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Resume functionality
@self.app.get("/api/onboarding/resume")
async def onboarding_resume():
"""Get information for resuming onboarding."""
try:
return await get_resume_info()
except Exception as e:
logger.error(f"Error in onboarding_resume: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Configuration endpoints
@self.app.get("/api/onboarding/config")
async def onboarding_config():
"""Get onboarding configuration and requirements."""
try:
return get_onboarding_config()
except Exception as e:
logger.error(f"Error in onboarding_config: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Enhanced provider endpoints
@self.app.get("/api/onboarding/providers/{provider}/setup")
async def provider_setup_info(provider: str):
"""Get setup information for a specific provider."""
try:
return await get_provider_setup_info(provider)
except Exception as e:
logger.error(f"Error in provider_setup_info: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/providers")
async def all_providers_info():
"""Get setup information for all providers."""
try:
return await get_all_providers_info()
except Exception as e:
logger.error(f"Error in all_providers_info: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/api/onboarding/providers/{provider}/validate")
async def validate_provider_key_endpoint(provider: str, request: APIKeyRequest):
"""Validate a specific provider's API key."""
try:
return await validate_provider_key(provider, request)
except Exception as e:
logger.error(f"Error in validate_provider_key: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/validation/enhanced")
async def enhanced_validation_status():
"""Get enhanced validation status for all configured services."""
try:
return await get_enhanced_validation_status()
except Exception as e:
logger.error(f"Error in enhanced_validation_status: {e}")
raise HTTPException(status_code=500, detail=str(e))
# New endpoints for FinalStep data loading
@self.app.get("/api/onboarding/summary")
async def onboarding_summary(current_user: dict = Depends(get_current_user)):
"""Get comprehensive onboarding summary for FinalStep."""
try:
return await get_onboarding_summary(current_user)
except Exception as e:
logger.error(f"Error in onboarding_summary: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/website-analysis")
async def website_analysis_data(current_user: dict = Depends(get_current_user)):
"""Get website analysis data for FinalStep."""
try:
return await get_website_analysis_data(current_user)
except Exception as e:
logger.error(f"Error in website_analysis_data: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/research-preferences")
async def research_preferences_data(current_user: dict = Depends(get_current_user)):
"""Get research preferences data for FinalStep."""
try:
return await get_research_preferences_data(current_user)
except Exception as e:
logger.error(f"Error in research_preferences_data: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Business Information endpoints
@self.app.post("/api/onboarding/business-info")
async def business_info_save(request: 'BusinessInfoRequest'):
"""Save business information for users without websites."""
try:
from models.business_info_request import BusinessInfoRequest
return await save_business_info(request)
except Exception as e:
logger.error(f"Error in business_info_save: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/business-info/{business_info_id}")
async def business_info_get(business_info_id: int):
"""Get business information by ID."""
try:
return await get_business_info(business_info_id)
except Exception as e:
logger.error(f"Error in business_info_get: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/business-info/user/{user_id}")
async def business_info_get_by_user(user_id: int):
"""Get business information by user ID."""
try:
return await get_business_info_by_user(user_id)
except Exception as e:
logger.error(f"Error in business_info_get_by_user: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.put("/api/onboarding/business-info/{business_info_id}")
async def business_info_update(business_info_id: int, request: 'BusinessInfoRequest'):
"""Update business information."""
try:
from models.business_info_request import BusinessInfoRequest
return await update_business_info(business_info_id, request)
except Exception as e:
logger.error(f"Error in business_info_update: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Persona generation endpoints
@self.app.post("/api/onboarding/step4/generate-personas")
async def generate_personas(request: dict, current_user: dict = Depends(get_current_user)):
"""Generate AI writing personas for Step 4."""
try:
return await generate_writing_personas(request, current_user)
except Exception as e:
logger.error(f"Error in generate_personas: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/api/onboarding/step4/generate-personas-async")
async def generate_personas_async(request: dict, background_tasks: BackgroundTasks, current_user: dict = Depends(get_current_user)):
"""Start async persona generation task."""
try:
return await generate_writing_personas_async(request, current_user, background_tasks)
except Exception as e:
logger.error(f"Error in generate_personas_async: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/step4/persona-task/{task_id}")
async def get_persona_task(task_id: str):
"""Get persona generation task status."""
try:
return await get_persona_task_status(task_id)
except Exception as e:
logger.error(f"Error in get_persona_task: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/step4/persona-latest")
async def persona_latest(current_user: dict = Depends(get_current_user)):
"""Get latest cached persona for current user."""
try:
return await get_latest_persona(current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in persona_latest: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/api/onboarding/step4/persona-save")
async def persona_save(request: dict, current_user: dict = Depends(get_current_user)):
"""Save edited persona back to cache."""
try:
return await save_persona_update(request, current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in persona_save: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/api/onboarding/step4/assess-persona-quality")
async def assess_persona_quality_endpoint(request: dict, current_user: dict = Depends(get_current_user)):
"""Assess the quality of generated personas."""
try:
return await assess_persona_quality(request, current_user)
except Exception as e:
logger.error(f"Error in assess_persona_quality: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.post("/api/onboarding/step4/regenerate-persona")
async def regenerate_persona_endpoint(request: dict, current_user: dict = Depends(get_current_user)):
"""Regenerate a specific persona with improvements."""
try:
return await regenerate_persona(request, current_user)
except Exception as e:
logger.error(f"Error in regenerate_persona: {e}")
raise HTTPException(status_code=500, detail=str(e))
@self.app.get("/api/onboarding/step4/persona-options")
async def get_persona_options(current_user: dict = Depends(get_current_user)):
"""Get persona generation options and configurations."""
try:
return await get_persona_generation_options(current_user)
except Exception as e:
logger.error(f"Error in get_persona_options: {e}")
raise HTTPException(status_code=500, detail=str(e))
def get_onboarding_status(self) -> Dict[str, Any]:
"""Get the status of onboarding endpoints."""
return {
"onboarding_endpoints": [
"/api/onboarding/init",
"/api/onboarding/status",
"/api/onboarding/progress",
"/api/onboarding/step/{step_number}",
"/api/onboarding/step/{step_number}/complete",
"/api/onboarding/step/{step_number}/skip",
"/api/onboarding/step/{step_number}/validate",
"/api/onboarding/api-keys",
"/api/onboarding/api-keys/onboarding",
"/api/onboarding/start",
"/api/onboarding/complete",
"/api/onboarding/reset",
"/api/onboarding/resume",
"/api/onboarding/config",
"/api/onboarding/providers/{provider}/setup",
"/api/onboarding/providers",
"/api/onboarding/providers/{provider}/validate",
"/api/onboarding/validation/enhanced",
"/api/onboarding/summary",
"/api/onboarding/website-analysis",
"/api/onboarding/research-preferences",
"/api/onboarding/business-info",
"/api/onboarding/step4/generate-personas",
"/api/onboarding/step4/generate-personas-async",
"/api/onboarding/step4/persona-task/{task_id}",
"/api/onboarding/step4/persona-latest",
"/api/onboarding/step4/persona-save",
"/api/onboarding/step4/assess-persona-quality",
"/api/onboarding/step4/regenerate-persona",
"/api/onboarding/step4/persona-options"
],
"total_endpoints": 30,
"status": "active"
}

View File

@@ -0,0 +1,137 @@
"""
Production Optimizer Module
Handles production-specific optimizations and configurations.
"""
import os
import sys
from typing import List, Dict, Any
class ProductionOptimizer:
"""Optimizes ALwrity backend for production deployment."""
def __init__(self):
self.production_optimizations = {
'disable_spacy_download': True,
'disable_nltk_download': True,
'skip_linguistic_setup': True,
'minimal_database_setup': True,
'skip_file_creation': True
}
def apply_production_optimizations(self) -> bool:
"""Apply production-specific optimizations."""
print("🚀 Applying production optimizations...")
# Set production environment variables
self._set_production_env_vars()
# Disable heavy operations
self._disable_heavy_operations()
# Optimize logging
self._optimize_logging()
print("✅ Production optimizations applied")
return True
def _set_production_env_vars(self) -> None:
"""Set production-specific environment variables."""
production_vars = {
'HOST': '0.0.0.0',
'PORT': '8000',
'RELOAD': 'false',
'LOG_LEVEL': 'INFO',
'DEBUG': 'false',
'PYTHONUNBUFFERED': '1', # Ensure logs are flushed immediately
'PYTHONDONTWRITEBYTECODE': '1' # Don't create .pyc files
}
for key, value in production_vars.items():
os.environ.setdefault(key, value)
print(f"{key}={value}")
def _disable_heavy_operations(self) -> None:
"""Disable operations that are too heavy for production startup."""
print(" ⚡ Disabling heavy operations for production...")
# Disable spaCy model download
os.environ.setdefault('DISABLE_SPACY_DOWNLOAD', 'true')
# Disable NLTK data download
os.environ.setdefault('DISABLE_NLTK_DOWNLOAD', 'true')
# Skip linguistic analyzer setup
os.environ.setdefault('SKIP_LINGUISTIC_SETUP', 'true')
print(" ✅ Heavy operations disabled")
def _optimize_logging(self) -> None:
"""Optimize logging for production."""
print(" 📝 Optimizing logging for production...")
# Set appropriate log level
os.environ.setdefault('LOG_LEVEL', 'INFO')
# Disable debug logging
os.environ.setdefault('DEBUG', 'false')
print(" ✅ Logging optimized")
def skip_linguistic_setup(self) -> bool:
"""Skip linguistic analysis setup in production."""
if os.getenv('SKIP_LINGUISTIC_SETUP', 'false').lower() == 'true':
print("⚠️ Skipping linguistic analysis setup (production mode)")
return True
return False
def skip_spacy_setup(self) -> bool:
"""Skip spaCy model setup in production."""
if os.getenv('DISABLE_SPACY_DOWNLOAD', 'false').lower() == 'true':
print("⚠️ Skipping spaCy model setup (production mode)")
return True
return False
def skip_nltk_setup(self) -> bool:
"""Skip NLTK data setup in production."""
if os.getenv('DISABLE_NLTK_DOWNLOAD', 'false').lower() == 'true':
print("⚠️ Skipping NLTK data setup (production mode)")
return True
return False
def get_production_config(self) -> Dict[str, Any]:
"""Get production configuration settings."""
return {
'host': os.getenv('HOST', '0.0.0.0'),
'port': int(os.getenv('PORT', '8000')),
'reload': False, # Never reload in production
'log_level': os.getenv('LOG_LEVEL', 'info'),
'access_log': True,
'workers': 1, # Single worker for Render
'timeout_keep_alive': 30,
'timeout_graceful_shutdown': 30
}
def validate_production_environment(self) -> bool:
"""Validate that the environment is ready for production."""
print("🔍 Validating production environment...")
# Check critical environment variables
required_vars = ['HOST', 'PORT', 'LOG_LEVEL']
missing_vars = []
for var in required_vars:
if not os.getenv(var):
missing_vars.append(var)
if missing_vars:
print(f"❌ Missing environment variables: {missing_vars}")
return False
# Check that reload is disabled
if os.getenv('RELOAD', 'false').lower() == 'true':
print("⚠️ Warning: RELOAD is enabled in production")
print("✅ Production environment validated")
return True

View File

@@ -0,0 +1,125 @@
"""
Rate Limiting Module
Handles rate limiting middleware and request tracking.
"""
import time
from collections import defaultdict
from typing import Dict, List, Optional
from fastapi import Request, Response
from fastapi.responses import JSONResponse
from loguru import logger
class RateLimiter:
"""Manages rate limiting for ALwrity backend."""
def __init__(self, window_seconds: int = 60, max_requests: int = 200):
self.window_seconds = window_seconds
self.max_requests = max_requests
self.request_counts: Dict[str, List[float]] = defaultdict(list)
# Endpoints exempt from rate limiting
self.exempt_paths = [
"/stream/strategies",
"/stream/strategic-intelligence",
"/stream/keyword-research",
"/latest-strategy",
"/ai-analytics",
"/gap-analysis",
"/calendar-events",
"/calendar-generation/progress",
"/health",
"/health/database"
]
def is_exempt_path(self, path: str) -> bool:
"""Check if a path is exempt from rate limiting."""
return any(exempt_path in path for exempt_path in self.exempt_paths)
def clean_old_requests(self, client_ip: str, current_time: float) -> None:
"""Clean old requests from the tracking dictionary."""
self.request_counts[client_ip] = [
req_time for req_time in self.request_counts[client_ip]
if current_time - req_time < self.window_seconds
]
def is_rate_limited(self, client_ip: str, current_time: float) -> bool:
"""Check if a client has exceeded the rate limit."""
self.clean_old_requests(client_ip, current_time)
return len(self.request_counts[client_ip]) >= self.max_requests
def add_request(self, client_ip: str, current_time: float) -> None:
"""Add a request to the tracking dictionary."""
self.request_counts[client_ip].append(current_time)
def get_rate_limit_response(self) -> JSONResponse:
"""Get a rate limit exceeded response."""
return JSONResponse(
status_code=429,
content={
"detail": "Too many requests",
"retry_after": self.window_seconds
},
headers={
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Headers": "*"
}
)
async def rate_limit_middleware(self, request: Request, call_next) -> Response:
"""Rate limiting middleware with exemptions for streaming endpoints."""
try:
client_ip = request.client.host if request.client else "unknown"
current_time = time.time()
path = request.url.path
# Check if path is exempt from rate limiting
if self.is_exempt_path(path):
# Allow streaming endpoints without rate limiting
response = await call_next(request)
return response
# Check rate limit
if self.is_rate_limited(client_ip, current_time):
logger.warning(f"Rate limit exceeded for {client_ip}")
return self.get_rate_limit_response()
# Add current request
self.add_request(client_ip, current_time)
response = await call_next(request)
return response
except Exception as e:
logger.error(f"Error in rate limiting middleware: {e}")
# Continue without rate limiting if there's an error
response = await call_next(request)
return response
def get_rate_limit_status(self, client_ip: str) -> Dict[str, any]:
"""Get current rate limit status for a client."""
current_time = time.time()
self.clean_old_requests(client_ip, current_time)
request_count = len(self.request_counts[client_ip])
remaining_requests = max(0, self.max_requests - request_count)
return {
"client_ip": client_ip,
"requests_in_window": request_count,
"max_requests": self.max_requests,
"remaining_requests": remaining_requests,
"window_seconds": self.window_seconds,
"is_limited": request_count >= self.max_requests
}
def reset_rate_limit(self, client_ip: Optional[str] = None) -> Dict[str, any]:
"""Reset rate limit for a specific client or all clients."""
if client_ip:
self.request_counts[client_ip] = []
return {"message": f"Rate limit reset for {client_ip}"}
else:
self.request_counts.clear()
return {"message": "Rate limit reset for all clients"}

View File

@@ -0,0 +1,168 @@
"""
Router Manager Module
Handles FastAPI router inclusion and management.
"""
from fastapi import FastAPI
from loguru import logger
from typing import List, Dict, Any, Optional
class RouterManager:
"""Manages FastAPI router inclusion and organization."""
def __init__(self, app: FastAPI):
self.app = app
self.included_routers = []
self.failed_routers = []
def include_router_safely(self, router, router_name: str = None) -> bool:
"""Include a router safely with error handling."""
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}")
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}")
return False
def include_core_routers(self) -> bool:
"""Include core application routers."""
try:
logger.info("Including core routers...")
# Component logic router
from api.component_logic import router as component_logic_router
self.include_router_safely(component_logic_router, "component_logic")
# Subscription router
from api.subscription_api import router as subscription_router
self.include_router_safely(subscription_router, "subscription")
# GSC router
from routers.gsc_auth import router as gsc_auth_router
self.include_router_safely(gsc_auth_router, "gsc_auth")
# WordPress router
from routers.wordpress_oauth import router as wordpress_oauth_router
self.include_router_safely(wordpress_oauth_router, "wordpress_oauth")
# SEO tools router
from routers.seo_tools import router as seo_tools_router
self.include_router_safely(seo_tools_router, "seo_tools")
# Facebook Writer router
from api.facebook_writer.routers import facebook_router
self.include_router_safely(facebook_router, "facebook_writer")
# LinkedIn routers
from routers.linkedin import router as linkedin_router
self.include_router_safely(linkedin_router, "linkedin")
from api.linkedin_image_generation import router as linkedin_image_router
self.include_router_safely(linkedin_image_router, "linkedin_image")
# Brainstorm router
from api.brainstorm import router as brainstorm_router
self.include_router_safely(brainstorm_router, "brainstorm")
# Hallucination detector and writing assistant
from api.hallucination_detector import router as hallucination_detector_router
self.include_router_safely(hallucination_detector_router, "hallucination_detector")
from api.writing_assistant import router as writing_assistant_router
self.include_router_safely(writing_assistant_router, "writing_assistant")
# Content planning and user data
from api.content_planning.api.router import router as content_planning_router
self.include_router_safely(content_planning_router, "content_planning")
from api.user_data import router as user_data_router
self.include_router_safely(user_data_router, "user_data")
from api.user_environment import router as user_environment_router
self.include_router_safely(user_environment_router, "user_environment")
# Strategy copilot
from api.content_planning.strategy_copilot import router as strategy_copilot_router
self.include_router_safely(strategy_copilot_router, "strategy_copilot")
logger.info("✅ Core routers included successfully")
return True
except Exception as e:
logger.error(f"❌ Error including core routers: {e}")
return False
def include_optional_routers(self) -> bool:
"""Include optional routers with error handling."""
try:
logger.info("Including optional routers...")
# AI Blog Writer router
try:
from api.blog_writer.router import router as blog_writer_router
self.include_router_safely(blog_writer_router, "blog_writer")
except Exception as e:
logger.warning(f"AI Blog Writer router not mounted: {e}")
# Wix Integration router
try:
from api.wix_routes import router as wix_router
self.include_router_safely(wix_router, "wix")
except Exception as e:
logger.warning(f"Wix Integration router not mounted: {e}")
# Blog Writer SEO Analysis router
try:
from api.blog_writer.seo_analysis import router as blog_seo_analysis_router
self.include_router_safely(blog_seo_analysis_router, "blog_seo_analysis")
except Exception as e:
logger.warning(f"Blog Writer SEO Analysis router not mounted: {e}")
# Persona router
try:
from api.persona_routes import router as persona_router
self.include_router_safely(persona_router, "persona")
except Exception as e:
logger.warning(f"Persona router not mounted: {e}")
# Stability AI routers
try:
from routers.stability import router as stability_router
self.include_router_safely(stability_router, "stability")
from routers.stability_advanced import router as stability_advanced_router
self.include_router_safely(stability_advanced_router, "stability_advanced")
from routers.stability_admin import router as stability_admin_router
self.include_router_safely(stability_admin_router, "stability_admin")
except Exception as e:
logger.warning(f"Stability AI routers not mounted: {e}")
# Step 3 Research router
try:
from api.onboarding_utils.step3_routes import router as step3_research_router
self.include_router_safely(step3_research_router, "step3_research")
except Exception as e:
logger.warning(f"Step 3 Research router not mounted: {e}")
logger.info("✅ Optional routers processed")
return True
except Exception as e:
logger.error(f"❌ Error including optional routers: {e}")
return False
def get_router_status(self) -> Dict[str, Any]:
"""Get the status of router inclusion."""
return {
"included_routers": self.included_routers,
"failed_routers": self.failed_routers,
"total_included": len(self.included_routers),
"total_failed": len(self.failed_routers)
}

View File

@@ -3,64 +3,23 @@
from fastapi import FastAPI, HTTPException, Depends, Request, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse, JSONResponse
from fastapi.responses import FileResponse
from pydantic import BaseModel
from typing import Dict, Any, Optional
import os
import time
from collections import defaultdict
from loguru import logger
from dotenv import load_dotenv
import asyncio
from datetime import datetime
from middleware.monitoring_middleware import monitoring_middleware
# Import modular utilities
from alwrity_utils import HealthChecker, RateLimiter, FrontendServing, RouterManager, OnboardingManager
# Load environment variables
load_dotenv()
# Import the new enhanced functions
from api.onboarding import (
health_check,
initialize_onboarding, # NEW: Batch init endpoint
get_onboarding_status,
get_onboarding_progress_full,
get_step_data,
complete_step,
skip_step,
validate_step_access,
get_api_keys,
get_api_keys_for_onboarding,
save_api_key,
validate_api_keys,
start_onboarding,
complete_onboarding,
reset_onboarding,
get_resume_info,
get_onboarding_config,
get_provider_setup_info,
get_all_providers_info,
validate_provider_key,
get_enhanced_validation_status,
get_onboarding_summary,
get_website_analysis_data,
get_research_preferences_data,
save_business_info,
get_business_info,
get_business_info_by_user,
update_business_info,
# Persona generation endpoints
generate_writing_personas,
generate_writing_personas_async,
get_persona_task_status,
assess_persona_quality,
regenerate_persona,
get_persona_generation_options,
# New cache helpers
get_latest_persona,
save_persona_update,
StepCompletionRequest,
APIKeyRequest
)
# Import middleware
from middleware.auth_middleware import get_current_user
# Import component logic endpoints
@@ -138,554 +97,69 @@ app.add_middleware(
# Temporarily disabled for Wix testing
# app.middleware("http")(monitoring_middleware)
# Simple rate limiting
request_counts = defaultdict(list)
RATE_LIMIT_WINDOW = 60 # 60 seconds
RATE_LIMIT_MAX_REQUESTS = 200 # Increased for testing - calendar generation polling
# Initialize modular utilities
health_checker = HealthChecker()
rate_limiter = RateLimiter(window_seconds=60, max_requests=200)
frontend_serving = FrontendServing(app)
router_manager = RouterManager(app)
onboarding_manager = OnboardingManager(app)
@app.middleware("http")
async def rate_limit_middleware(request: Request, call_next):
"""Simple rate limiting middleware with exemptions for streaming endpoints."""
try:
client_ip = request.client.host if request.client else "unknown"
current_time = time.time()
# Exempt streaming endpoints and frequently called endpoints from rate limiting
path = request.url.path
if any(streaming_path in path for streaming_path in [
"/stream/strategies",
"/stream/strategic-intelligence",
"/stream/keyword-research",
"/latest-strategy", # Exempt latest strategy endpoint from rate limiting
"/ai-analytics", # Exempt AI analytics endpoint from rate limiting
"/gap-analysis", # Exempt gap analysis endpoint from rate limiting
"/calendar-events", # Exempt calendar events endpoint from rate limiting
"/calendar-generation/progress", # Exempt calendar generation progress from rate limiting
"/health" # Exempt health check endpoints from rate limiting
]):
# Allow streaming endpoints without rate limiting
response = await call_next(request)
return response
# Clean old requests
request_counts[client_ip] = [req_time for req_time in request_counts[client_ip]
if current_time - req_time < RATE_LIMIT_WINDOW]
# Check rate limit
if len(request_counts[client_ip]) >= RATE_LIMIT_MAX_REQUESTS:
logger.warning(f"Rate limit exceeded for {client_ip}")
return JSONResponse(
status_code=429,
content={"detail": "Too many requests", "retry_after": RATE_LIMIT_WINDOW},
headers={
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Headers": "*"
}
)
# Add current request
request_counts[client_ip].append(current_time)
response = await call_next(request)
return response
except Exception as e:
logger.error(f"Error in rate limiting middleware: {e}")
# Continue without rate limiting if there's an error
response = await call_next(request)
return response
"""Rate limiting middleware using modular utilities."""
return await rate_limiter.rate_limit_middleware(request, call_next)
# Health check endpoint
# Health check endpoints using modular utilities
@app.get("/health")
async def health():
"""Health check endpoint."""
return health_check()
return health_checker.basic_health_check()
@app.get("/health/database")
async def database_health_check():
"""Database health check endpoint including persona tables verification."""
try:
from services.database import get_db_session
from models.persona_models import WritingPersona, PlatformPersona, PersonaAnalysisResult, PersonaValidationResult
session = get_db_session()
if not session:
return {"status": "error", "message": "Could not get database session"}
# Test all persona tables
tables_status = {}
try:
session.query(WritingPersona).first()
tables_status["writing_personas"] = "ok"
except Exception as e:
tables_status["writing_personas"] = f"error: {str(e)}"
try:
session.query(PlatformPersona).first()
tables_status["platform_personas"] = "ok"
except Exception as e:
tables_status["platform_personas"] = f"error: {str(e)}"
try:
session.query(PersonaAnalysisResult).first()
tables_status["persona_analysis_results"] = "ok"
except Exception as e:
tables_status["persona_analysis_results"] = f"error: {str(e)}"
try:
session.query(PersonaValidationResult).first()
tables_status["persona_validation_results"] = "ok"
except Exception as e:
tables_status["persona_validation_results"] = f"error: {str(e)}"
session.close()
# Check if all tables are ok
all_ok = all(status == "ok" for status in tables_status.values())
return {
"status": "healthy" if all_ok else "warning",
"message": "Database connection successful" if all_ok else "Some persona tables may have issues",
"persona_tables": tables_status,
"timestamp": datetime.utcnow().isoformat()
}
except Exception as e:
return {
"status": "error",
"message": f"Database health check failed: {str(e)}",
"timestamp": datetime.utcnow().isoformat()
}
async def database_health():
"""Database health check endpoint."""
return health_checker.database_health_check()
# Onboarding initialization - BATCH ENDPOINT (reduces 4 API calls to 1)
@app.get("/api/onboarding/init")
async def onboarding_init(current_user: dict = Depends(get_current_user)):
"""
Batch initialization endpoint - combines user info, status, and progress.
This eliminates 3-4 separate API calls on initial load, reducing latency by 60-75%.
"""
try:
return await initialize_onboarding(current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in onboarding_init: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/health/comprehensive")
async def comprehensive_health():
"""Comprehensive health check endpoint."""
return health_checker.comprehensive_health_check()
# Onboarding status endpoints
# Rate limiting management endpoints
@app.get("/api/rate-limit/status")
async def rate_limit_status(request: Request):
"""Get current rate limit status for the requesting client."""
client_ip = request.client.host if request.client else "unknown"
return rate_limiter.get_rate_limit_status(client_ip)
@app.post("/api/rate-limit/reset")
async def reset_rate_limit(request: Request, client_ip: Optional[str] = None):
"""Reset rate limit for a specific client or all clients."""
if client_ip is None:
client_ip = request.client.host if request.client else "unknown"
return rate_limiter.reset_rate_limit(client_ip)
# Frontend serving management endpoints
@app.get("/api/frontend/status")
async def frontend_status():
"""Get frontend serving status."""
return frontend_serving.get_frontend_status()
# Router management endpoints
@app.get("/api/routers/status")
async def router_status():
"""Get router inclusion status."""
return router_manager.get_router_status()
# Onboarding management endpoints
@app.get("/api/onboarding/status")
async def onboarding_status(current_user: dict = Depends(get_current_user)):
"""Get the current onboarding status."""
try:
# Pass current_user explicitly to user-scoped handler
return await get_onboarding_status(current_user)
except HTTPException as he:
# Preserve HTTP error codes like 401 Unauthorized
raise he
except Exception as e:
logger.error(f"Error in onboarding_status: {e}")
raise HTTPException(status_code=500, detail=str(e))
async def onboarding_status():
"""Get onboarding manager status."""
return onboarding_manager.get_onboarding_status()
@app.get("/api/onboarding/progress")
async def onboarding_progress(current_user: dict = Depends(get_current_user)):
"""Get the full onboarding progress data."""
try:
return await get_onboarding_progress_full(current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in onboarding_progress: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Step management endpoints
@app.get("/api/onboarding/step/{step_number}")
async def step_data(step_number: int, current_user: dict = Depends(get_current_user)):
"""Get data for a specific step."""
try:
return await get_step_data(step_number, current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in step_data: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/onboarding/step/{step_number}/complete")
async def step_complete(step_number: int, request: StepCompletionRequest, current_user: dict = Depends(get_current_user)):
"""Mark a step as completed."""
try:
return await complete_step(step_number, request, current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in step_complete: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/onboarding/step/{step_number}/skip")
async def step_skip(step_number: int, current_user: dict = Depends(get_current_user)):
"""Skip a step (for optional steps)."""
try:
return await skip_step(step_number, current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in step_skip: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/onboarding/step/{step_number}/validate")
async def step_validate(step_number: int, current_user: dict = Depends(get_current_user)):
"""Validate if user can access a specific step."""
try:
return await validate_step_access(step_number, current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in step_validate: {e}")
raise HTTPException(status_code=500, detail=str(e))
# API key management endpoints
@app.get("/api/onboarding/api-keys")
async def api_keys():
"""Get all configured API keys (masked)."""
try:
return await get_api_keys()
except Exception as e:
logger.error(f"Error in api_keys: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/onboarding/api-keys/onboarding")
async def api_keys_for_onboarding():
"""Get all configured API keys for onboarding (unmasked)."""
try:
return await get_api_keys_for_onboarding()
except Exception as e:
logger.error(f"Error in api_keys_for_onboarding: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/onboarding/api-keys")
async def api_key_save(request: APIKeyRequest):
"""Save an API key for a provider."""
try:
return await save_api_key(request)
except Exception as e:
logger.error(f"Error in api_key_save: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/onboarding/api-keys/validate")
async def api_key_validate():
"""Validate all configured API keys."""
try:
return await validate_api_keys()
except Exception as e:
logger.error(f"Error in api_key_validate: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Onboarding control endpoints
@app.post("/api/onboarding/start")
async def onboarding_start(current_user: dict = Depends(get_current_user)):
"""Start a new onboarding session."""
try:
return await start_onboarding(current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in onboarding_start: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/onboarding/complete")
async def onboarding_complete(current_user: dict = Depends(get_current_user)):
"""Complete the onboarding process."""
try:
return await complete_onboarding(current_user)
except HTTPException as he:
raise he
except Exception as e:
logger.error(f"Error in onboarding_complete: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/onboarding/reset")
async def onboarding_reset():
"""Reset the onboarding progress."""
try:
return await reset_onboarding()
except Exception as e:
logger.error(f"Error in onboarding_reset: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Resume functionality
@app.get("/api/onboarding/resume")
async def onboarding_resume():
"""Get information for resuming onboarding."""
try:
return await get_resume_info()
except Exception as e:
logger.error(f"Error in onboarding_resume: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Configuration endpoints
@app.get("/api/onboarding/config")
async def onboarding_config():
"""Get onboarding configuration and requirements."""
try:
return get_onboarding_config()
except Exception as e:
logger.error(f"Error in onboarding_config: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Enhanced provider endpoints
@app.get("/api/onboarding/providers/{provider}/setup")
async def provider_setup_info(provider: str):
"""Get setup information for a specific provider."""
try:
return await get_provider_setup_info(provider)
except Exception as e:
logger.error(f"Error in provider_setup_info: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/onboarding/providers")
async def all_providers_info():
"""Get setup information for all providers."""
try:
return await get_all_providers_info()
except Exception as e:
logger.error(f"Error in all_providers_info: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/onboarding/providers/{provider}/validate")
async def validate_provider_key_endpoint(provider: str, request: APIKeyRequest):
"""Validate a specific provider's API key."""
try:
return await validate_provider_key(provider, request)
except Exception as e:
logger.error(f"Error in validate_provider_key: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/onboarding/validation/enhanced")
async def enhanced_validation_status():
"""Get enhanced validation status for all configured services."""
try:
return await get_enhanced_validation_status()
except Exception as e:
logger.error(f"Error in enhanced_validation_status: {e}")
raise HTTPException(status_code=500, detail=str(e))
# New endpoints for FinalStep data loading
@app.get("/api/onboarding/summary")
async def onboarding_summary(current_user: dict = Depends(get_current_user)):
"""Get comprehensive onboarding summary for FinalStep."""
try:
return await get_onboarding_summary(current_user)
except Exception as e:
logger.error(f"Error in onboarding_summary: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/onboarding/website-analysis")
async def website_analysis_data(current_user: dict = Depends(get_current_user)):
"""Get website analysis data for FinalStep."""
try:
return await get_website_analysis_data(current_user)
except Exception as e:
logger.error(f"Error in website_analysis_data: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/onboarding/research-preferences")
async def research_preferences_data(current_user: dict = Depends(get_current_user)):
"""Get research preferences data for FinalStep."""
try:
return await get_research_preferences_data(current_user)
except Exception as e:
logger.error(f"Error in research_preferences_data: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Business Information endpoints
@app.post("/api/onboarding/business-info")
async def business_info_save(request: 'BusinessInfoRequest'):
"""Save business information for users without websites."""
try:
from models.business_info_request import BusinessInfoRequest
return await save_business_info(request)
except Exception as e:
logger.error(f"Error in business_info_save: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/onboarding/business-info/{business_info_id}")
async def business_info_get(business_info_id: int):
"""Get business information by ID."""
try:
return await get_business_info(business_info_id)
except Exception as e:
logger.error(f"Error in business_info_get: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/onboarding/business-info/user/{user_id}")
async def business_info_get_by_user(user_id: int):
"""Get business information by user ID."""
try:
return await get_business_info_by_user(user_id)
except Exception as e:
logger.error(f"Error in business_info_get_by_user: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.put("/api/onboarding/business-info/{business_info_id}")
async def business_info_update(business_info_id: int, request: 'BusinessInfoRequest'):
"""Update business information."""
try:
from models.business_info_request import BusinessInfoRequest
return await update_business_info(business_info_id, request)
except Exception as e:
logger.error(f"Error in business_info_update: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Persona generation endpoints
@app.post("/api/onboarding/step4/generate-personas")
async def generate_personas(request: dict, current_user: dict = Depends(get_current_user)):
"""Generate AI writing personas for Step 4."""
try:
return await generate_writing_personas(request, current_user)
except Exception as e:
logger.error(f"Error in generate_personas: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/onboarding/step4/generate-personas-async")
async def generate_personas_async(request: dict, background_tasks: BackgroundTasks, current_user: dict = Depends(get_current_user)):
"""Start async persona generation task."""
try:
return await generate_writing_personas_async(request, current_user, background_tasks)
except Exception as e:
logger.error(f"Error in generate_personas_async: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/onboarding/step4/persona-task/{task_id}")
async def get_persona_task(task_id: str):
"""Get persona generation task status."""
try:
return await get_persona_task_status(task_id)
except Exception as e:
logger.error(f"Error in get_persona_task: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/onboarding/step4/persona-latest")
async def persona_latest(current_user: dict = Depends(get_current_user)):
"""Get latest cached persona for current user."""
try:
return await get_latest_persona(current_user)
except HTTPException as he:
# Re-raise HTTP exceptions (like 404) as-is
raise he
except Exception as e:
logger.error(f"Error in persona_latest: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/onboarding/step4/persona-save")
async def persona_save(request: dict, current_user: dict = Depends(get_current_user)):
"""Save edited persona back to cache."""
try:
return await save_persona_update(request, current_user)
except HTTPException as he:
# Re-raise HTTP exceptions as-is
raise he
except Exception as e:
logger.error(f"Error in persona_save: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/onboarding/step4/assess-persona-quality")
async def assess_persona_quality_endpoint(request: dict, current_user: dict = Depends(get_current_user)):
"""Assess the quality of generated personas."""
try:
return await assess_persona_quality(request, current_user)
except Exception as e:
logger.error(f"Error in assess_persona_quality: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/onboarding/step4/regenerate-persona")
async def regenerate_persona_endpoint(request: dict, current_user: dict = Depends(get_current_user)):
"""Regenerate a specific persona with improvements."""
try:
return await regenerate_persona(request, current_user)
except Exception as e:
logger.error(f"Error in regenerate_persona: {e}")
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/onboarding/step4/persona-options")
async def get_persona_options(current_user: dict = Depends(get_current_user)):
"""Get persona generation options and configurations."""
try:
return await get_persona_generation_options(current_user)
except Exception as e:
logger.error(f"Error in get_persona_options: {e}")
raise HTTPException(status_code=500, detail=str(e))
# Include component logic router
app.include_router(component_logic_router)
# Include subscription and usage tracking router
app.include_router(subscription_router)
# Include GSC router
from routers.gsc_auth import router as gsc_auth_router
app.include_router(gsc_auth_router)
# Include WordPress router
from routers.wordpress_oauth import router as wordpress_oauth_router
app.include_router(wordpress_oauth_router)
# Include SEO tools router
app.include_router(seo_tools_router)
# Include Facebook Writer router
app.include_router(facebook_router)
# Include LinkedIn content generation router
app.include_router(linkedin_router)
# Include LinkedIn image generation router
app.include_router(linkedin_image_router)
app.include_router(brainstorm_router)
# Include hallucination detector router
app.include_router(hallucination_detector_router)
app.include_router(writing_assistant_router)
# Include user data router
# Include content planning router
app.include_router(content_planning_router)
app.include_router(user_data_router)
app.include_router(strategy_copilot_router)
app.include_router(user_environment_router)
# Include AI Blog Writer router
try:
from api.blog_writer.router import router as blog_writer_router
app.include_router(blog_writer_router)
except Exception as e:
logger.warning(f"AI Blog Writer router not mounted: {e}")
# Include Wix Integration router
try:
from api.wix_routes import router as wix_router
app.include_router(wix_router)
except Exception as e:
logger.warning(f"Wix Integration router not mounted: {e}")
# Include Blog Writer SEO Analysis router (comprehensive SEO analysis)
try:
from api.blog_writer.seo_analysis import router as blog_seo_analysis_router
app.include_router(blog_seo_analysis_router)
except Exception as e:
logger.warning(f"Blog Writer SEO Analysis router not mounted: {e}")
# Include persona router
from api.persona_routes import router as persona_router
app.include_router(persona_router)
# Include Stability AI routers
from routers.stability import router as stability_router
from routers.stability_advanced import router as stability_advanced_router
from routers.stability_admin import router as stability_admin_router
app.include_router(stability_router)
app.include_router(stability_advanced_router)
app.include_router(stability_admin_router)
# Step 3 Research router
from api.onboarding_utils.step3_routes import router as step3_research_router
app.include_router(step3_research_router)
# Include routers using modular utilities
router_manager.include_core_routers()
router_manager.include_optional_routers()
# SEO Dashboard endpoints
@app.get("/api/seo-dashboard/data")
@@ -744,33 +218,14 @@ async def batch_analyze_urls_endpoint(urls: list[str]):
"""Analyze multiple URLs in batch."""
return await batch_analyze_urls(urls)
# Setup frontend serving using modular utilities
frontend_serving.setup_frontend_serving()
# Serve React frontend (for production)
@app.get("/")
async def serve_frontend():
"""Serve the React frontend."""
# Check if frontend build exists
frontend_path = os.path.join(os.path.dirname(__file__), "..", "frontend", "build")
index_html = os.path.join(frontend_path, "index.html")
if os.path.exists(index_html):
return FileResponse(index_html)
else:
return {
"message": "Frontend not built. Please run 'npm run build' in the frontend directory.",
"api_docs": "/api/docs"
}
# Mount static files for React app (only if directory exists)
try:
frontend_build_path = os.path.join(os.path.dirname(__file__), "..", "frontend", "build")
static_path = os.path.join(frontend_build_path, "static")
if os.path.exists(static_path):
app.mount("/static", StaticFiles(directory=static_path), name="static")
logger.info("Frontend static files mounted successfully")
else:
logger.info("Frontend build directory not found. Static files not mounted.")
except Exception as e:
logger.info(f"Could not mount static files: {e}")
return frontend_serving.serve_frontend()
# Startup event
@app.on_event("startup")

View File

@@ -17,7 +17,7 @@ import os
import time
# Logging configuration
LOG_BASE_DIR = "/workspace/backend/logs"
LOG_BASE_DIR = "logs"
os.makedirs(LOG_BASE_DIR, exist_ok=True)
# Ensure subdirectories exist

44
backend/render.yaml Normal file
View File

@@ -0,0 +1,44 @@
# Render deployment configuration for ALwrity Backend
services:
- type: web
name: alwrity-backend
env: python
buildCommand: pip install -r requirements.txt
startCommand: python start_alwrity_backend.py --production
healthCheckPath: /health
envVars:
- key: HOST
value: 0.0.0.0
- key: PORT
value: 8000
- key: RELOAD
value: false
- key: LOG_LEVEL
value: INFO
- key: PYTHONPATH
value: /opt/render/project/src/backend
- key: TMPDIR
value: /tmp
# Add your environment variables here:
# - key: GEMINI_API_KEY
# value: your_gemini_key_here
# - key: OPENAI_API_KEY
# value: your_openai_key_here
# - key: ANTHROPIC_API_KEY
# value: your_anthropic_key_here
# - key: MISTRAL_API_KEY
# value: your_mistral_key_here
# - key: TAVILY_API_KEY
# value: your_tavily_key_here
# - key: EXA_API_KEY
# value: your_exa_key_here
# - key: SERPER_API_KEY
# value: your_serper_key_here
# - key: CLERK_SECRET_KEY
# value: your_clerk_secret_here
# - key: GSC_REDIRECT_URI
# value: https://your-frontend.vercel.app/gsc/callback
# - key: WORDPRESS_REDIRECT_URI
# value: https://your-frontend.vercel.app/wp/callback
# - key: WIX_REDIRECT_URI
# value: https://your-frontend.vercel.app/wix/callback

View File

@@ -33,7 +33,7 @@ from middleware.logging_middleware import log_api_call, save_to_file
router = APIRouter(prefix="/api/seo", tags=["AI SEO Tools"])
# Configuration for intelligent logging
LOG_DIR = "/workspace/backend/logs/seo_tools"
LOG_DIR = "logs/seo_tools"
os.makedirs(LOG_DIR, exist_ok=True)
# Request/Response Models

View File

@@ -18,14 +18,32 @@ class UserWorkspaceManager:
def __init__(self, db_session: Session):
self.db = db_session
self.base_workspace_dir = Path("lib/workspace")
self.user_workspaces_dir = self.base_workspace_dir / "users"
# Use environment-safe paths for production
if os.getenv("RENDER") or os.getenv("RAILWAY") or os.getenv("HEROKU"):
# In production, use temp directories or skip file operations
self.base_workspace_dir = Path("/tmp/alwrity_workspace")
self.user_workspaces_dir = self.base_workspace_dir / "users"
else:
# In development, use local directories
self.base_workspace_dir = Path("lib/workspace")
self.user_workspaces_dir = self.base_workspace_dir / "users"
def create_user_workspace(self, user_id: str) -> Dict[str, Any]:
"""Create a complete user workspace with progressive setup."""
try:
logger.info(f"Creating workspace for user {user_id}")
# Check if we're in production and skip file operations if needed
if os.getenv("RENDER") or os.getenv("RAILWAY") or os.getenv("HEROKU"):
logger.info("Production environment detected - skipping file workspace creation")
return {
"user_id": user_id,
"workspace_path": "/tmp/alwrity_workspace/users/user_" + user_id,
"config": self._create_user_config(user_id),
"created_at": datetime.utcnow().isoformat(),
"production_mode": True
}
# Create user-specific directories
user_dir = self.user_workspaces_dir / f"user_{user_id}"
user_dir.mkdir(parents=True, exist_ok=True)

View File

@@ -1,524 +1,25 @@
#!/usr/bin/env python3
"""
ALwrity Backend Server - Comprehensive Startup Script
Handles setup, dependency installation, and server startup.
ALwrity Backend Server - Modular Startup Script
Handles setup, dependency installation, and server startup using modular utilities.
Run this from the backend directory to set up and start the FastAPI server.
"""
import os
import sys
import subprocess
import time
import argparse
from pathlib import Path
def install_requirements():
"""Install required Python packages."""
print("📦 Installing required packages...")
requirements_file = Path(__file__).parent / "requirements.txt"
try:
subprocess.check_call([
sys.executable, "-m", "pip", "install", "-r", str(requirements_file)
])
print("[OK] All packages installed successfully!")
return True
except subprocess.CalledProcessError as e:
print(f"[ERROR] Error installing packages: {e}")
return False
# Import modular utilities
from alwrity_utils import (
DependencyManager,
EnvironmentSetup,
DatabaseSetup,
ProductionOptimizer
)
def create_env_file():
"""Create a .env file with default configuration."""
env_file = Path(__file__).parent / ".env"
if env_file.exists():
print("[INFO] .env file already exists")
return True
print("🔧 Creating .env file with default configuration...")
env_content = """# ALwrity Backend Configuration
# 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
# Research API Keys (Optional)
# TAVILY_API_KEY=your_tavily_api_key_here
# SERPER_API_KEY=your_serper_api_key_here
# METAPHOR_API_KEY=your_metaphor_api_key_here
# FIRECRAWL_API_KEY=your_firecrawl_api_key_here
# Server Configuration
HOST=0.0.0.0
PORT=8000
DEBUG=true
# Logging
LOG_LEVEL=INFO
"""
try:
with open(env_file, 'w') as f:
f.write(env_content)
print("[OK] .env file created successfully!")
return True
except Exception as e:
print(f"[ERROR] Error creating .env file: {e}")
return False
def setup_monitoring_tables():
"""Set up API monitoring database tables."""
print("📊 Setting up API monitoring tables...")
try:
# Import and run the monitoring table creation
sys.path.append(str(Path(__file__).parent))
from scripts.create_monitoring_tables import create_monitoring_tables
if create_monitoring_tables():
print("[OK] API monitoring tables created successfully!")
return True
else:
print("[WARNING] Warning: Failed to create monitoring tables, continuing anyway...")
return True # Don't fail startup for monitoring issues
except Exception as e:
print(f"[WARNING] Warning: Could not set up monitoring tables: {e}")
print(" Monitoring will be disabled. Continuing startup...")
return True # Don't fail startup for monitoring issues
def setup_billing_tables():
"""Set up billing and subscription database tables."""
print("💳 Setting up billing and subscription tables...")
try:
# Import and run the billing table creation
sys.path.append(str(Path(__file__).parent))
from scripts.create_billing_tables import create_billing_tables, check_existing_tables
from services.database import DATABASE_URL
from sqlalchemy import create_engine
# Create engine to check existing tables
engine = create_engine(DATABASE_URL, echo=False)
# Check existing tables
if not check_existing_tables(engine):
print("[OK] Billing tables already exist, skipping creation")
return True
if create_billing_tables():
print("[OK] Billing and subscription tables created successfully!")
return True
else:
print("[WARNING] Warning: Failed to create billing tables, continuing anyway...")
return True # Don't fail startup for billing issues
except Exception as e:
print(f"[WARNING] Warning: Could not set up billing tables: {e}")
print(" Billing system will be disabled. Continuing startup...")
return True # Don't fail startup for billing issues
def setup_monitoring_middleware():
"""Set up monitoring middleware in app.py if not already present."""
print("🔍 Setting up API monitoring middleware...")
app_file = Path(__file__).parent / "app.py"
if not app_file.exists():
print("[WARNING] Warning: app.py not found, skipping middleware setup")
return True
try:
with open(app_file, 'r') as f:
content = f.read()
# Check if monitoring middleware is already set up
if "monitoring_middleware" in content:
print("[OK] Monitoring middleware already configured")
return True
# Add monitoring middleware import and setup
monitoring_import = "from middleware.monitoring_middleware import monitoring_middleware\n"
monitoring_setup = "app.middleware(\"http\")(monitoring_middleware)\n"
# Find the right place to add the import (after other imports)
lines = content.split('\n')
import_end_index = 0
for i, line in enumerate(lines):
if line.strip().startswith('import ') or line.strip().startswith('from '):
import_end_index = i + 1
elif line.strip() and not line.strip().startswith('#'):
break
# Insert monitoring import
lines.insert(import_end_index, monitoring_import)
# Find the right place to add middleware setup (after app creation)
app_creation_index = -1
for i, line in enumerate(lines):
if 'app = FastAPI(' in line or 'app = FastAPI()' in line:
app_creation_index = i
break
if app_creation_index != -1:
# Find the end of app configuration
setup_index = app_creation_index + 1
for i in range(app_creation_index + 1, len(lines)):
if lines[i].strip() and not lines[i].strip().startswith('#'):
setup_index = i + 1
break
lines.insert(setup_index, monitoring_setup)
# Write back to file
with open(app_file, 'w') as f:
f.write('\n'.join(lines))
print("[OK] Monitoring middleware configured successfully!")
return True
except Exception as e:
print(f"[WARNING] Warning: Could not set up monitoring middleware: {e}")
print(" Monitoring will be disabled. Continuing startup...")
return True # Don't fail startup for monitoring issues
def setup_spacy_model():
"""Set up spaCy English model for linguistic analysis."""
print("Setting up spaCy English model...")
try:
import spacy
# Check if en_core_web_sm model is already installed
model_name = "en_core_web_sm"
try:
# Try to load the model directly
nlp = spacy.load(model_name)
# Test the model with a simple sentence
test_doc = nlp("This is a test sentence.")
if test_doc and len(test_doc) > 0:
print(f"SUCCESS: spaCy model '{model_name}' is already installed and working")
print(f" Test: Processed {len(test_doc)} tokens successfully")
return True
else:
raise OSError("Model loaded but not functioning correctly")
except OSError:
print(f"INFO: spaCy model '{model_name}' not found or not working, downloading...")
# Try to download the model using subprocess
try:
print(f" Downloading {model_name}...")
result = subprocess.run([
sys.executable, "-m", "spacy", "download", model_name
], capture_output=True, text=True, timeout=300) # 5 minute timeout
if result.returncode == 0:
print(f" SUCCESS: Model download completed")
else:
print(f" WARNING: Download warning: {result.stderr}")
except subprocess.TimeoutExpired:
print(f" ERROR: Download timed out after 5 minutes")
return False
except subprocess.CalledProcessError as e:
print(f" ERROR: Download failed: {e}")
return False
# Verify the model was downloaded correctly
try:
nlp = spacy.load(model_name)
# Test the model
test_doc = nlp("This is a test sentence.")
if test_doc and len(test_doc) > 0:
print(f"SUCCESS: spaCy model '{model_name}' downloaded and verified successfully")
print(f" Test: Processed {len(test_doc)} tokens successfully")
return True
else:
print(f"ERROR: Model downloaded but not functioning correctly")
return False
except OSError as e:
print(f"ERROR: Model downloaded but failed to load: {e}")
return False
except subprocess.CalledProcessError as e:
print(f"ERROR: Error downloading spaCy model: {e}")
print(" Manual installation required:")
print(" 1. Install spaCy: pip install spacy>=3.7.0")
print(" 2. Download model: python -m spacy download en_core_web_sm")
print(" 3. Test setup: python -c \"import spacy; nlp=spacy.load('en_core_web_sm'); print('spaCy working!')\"")
print(" 4. Restart the backend")
return False
except ImportError as e:
print(f"ERROR: spaCy not installed: {e}")
print(" Manual installation required:")
print(" 1. Install spaCy: pip install spacy>=3.7.0")
print(" 2. Download model: python -m spacy download en_core_web_sm")
print(" 3. Test setup: python -c \"import spacy; nlp=spacy.load('en_core_web_sm'); print('spaCy working!')\"")
print(" 4. Restart the backend")
return False
except Exception as e:
print(f"ERROR: Error setting up spaCy model: {e}")
print(" Manual installation required:")
print(" 1. Install spaCy: pip install spacy>=3.7.0")
print(" 2. Download model: python -m spacy download en_core_web_sm")
print(" 3. Test setup: python -c \"import spacy; nlp=spacy.load('en_core_web_sm'); print('spaCy working!')\"")
print(" 4. Restart the backend")
return False
def setup_nltk_data():
"""Set up required NLTK data for linguistic analysis."""
print("Setting up NLTK data...")
try:
import nltk
# Required NLTK data packages
required_data = [
'punkt_tab', # Updated for newer NLTK versions
'stopwords',
'averaged_perceptron_tagger_eng', # Updated for newer NLTK versions
'wordnet',
'omw-1.4'
]
for data_package in required_data:
try:
nltk.data.find(f'tokenizers/{data_package}' if data_package in ['punkt', 'punkt_tab']
else f'corpora/{data_package}' if data_package in ['stopwords', 'wordnet', 'omw-1.4']
else f'taggers/{data_package}' if data_package in ['averaged_perceptron_tagger', 'averaged_perceptron_tagger_eng']
else f'corpora/{data_package}')
print(f" SUCCESS: {data_package}")
except LookupError:
print(f" INFO: Downloading {data_package}...")
nltk.download(data_package, quiet=True)
print(f" SUCCESS: {data_package} downloaded")
print("SUCCESS: All required NLTK data is available")
return True
except Exception as e:
print(f"ERROR: Error setting up NLTK data: {e}")
return False
def check_dependencies():
"""Check if required dependencies are installed."""
print("🔍 Checking dependencies...")
required_packages = [
'fastapi',
'uvicorn',
'pydantic',
'loguru',
'openai',
'google.generativeai',
'anthropic',
'mistralai',
'sqlalchemy',
'spacy', # Added spaCy to required packages
'nltk' # Added NLTK to required packages
]
missing_packages = []
for package in required_packages:
try:
__import__(package.replace('-', '_'))
print(f" [OK] {package}")
except ImportError:
print(f" [ERROR] {package} - MISSING")
missing_packages.append(package)
if missing_packages:
print(f"\n[ERROR] Missing packages: {', '.join(missing_packages)}")
print("Installing missing packages...")
return install_requirements()
else:
print("\n[OK] All dependencies are available!")
return True
def setup_environment():
"""Set up the environment for the backend."""
print("🔧 Setting up environment...")
# Create necessary directories
directories = [
"lib/workspace/alwrity_content",
"lib/workspace/alwrity_web_research",
"lib/workspace/alwrity_prompts",
"lib/workspace/alwrity_config"
]
for directory in directories:
Path(directory).mkdir(parents=True, exist_ok=True)
print(f" [OK] Created directory: {directory}")
# Create .env file if it doesn't exist
create_env_file()
# Set up monitoring
setup_monitoring_tables()
setup_monitoring_middleware()
# Set up billing and subscription system
setup_billing_tables()
# Set up persona tables
if setup_persona_tables():
# Verify persona tables were created successfully
verify_persona_tables()
else:
print("[WARNING] Warning: Persona tables setup failed, but continuing...")
# Set up linguistic analysis dependencies (Required for persona generation)
print("🧠 Setting up linguistic analysis dependencies...")
# Set up spaCy model (REQUIRED for persona generation)
if not setup_spacy_model():
print("[ERROR] CRITICAL: spaCy model setup failed - persona generation will not work!")
print(" Please ensure spaCy is installed and en_core_web_sm model is available")
return False
# Set up NLTK data (supplementary to spaCy)
if not setup_nltk_data():
print("[WARNING] Warning: NLTK data setup failed, but continuing...")
print("[OK] Environment setup complete")
return True
def setup_persona_tables():
"""Set up persona database tables."""
print("🔧 Setting up persona tables...")
try:
from services.database import engine
from models.persona_models import Base as PersonaBase
# Create persona tables
PersonaBase.metadata.create_all(bind=engine)
print("[OK] Persona tables created successfully")
# Verify tables were created
from sqlalchemy import inspect
inspector = inspect(engine)
tables = inspector.get_table_names()
persona_tables = [
'writing_personas',
'platform_personas',
'persona_analysis_results',
'persona_validation_results'
]
created_tables = [table for table in persona_tables if table in tables]
print(f"[OK] Verified persona tables created: {created_tables}")
if len(created_tables) != len(persona_tables):
missing = [table for table in persona_tables if table not in created_tables]
print(f"[WARNING] Warning: Missing persona tables: {missing}")
return False
return True
except Exception as e:
print(f"[ERROR] Error setting up persona tables: {e}")
return False
def verify_persona_tables():
"""Verify that persona tables exist and are accessible."""
print("🔍 Verifying persona tables...")
try:
from services.database import get_db_session
from models.persona_models import WritingPersona, PlatformPersona, PersonaAnalysisResult, PersonaValidationResult
session = get_db_session()
if session:
# Try to query all persona tables to verify they exist
session.query(WritingPersona).first()
session.query(PlatformPersona).first()
session.query(PersonaAnalysisResult).first()
session.query(PersonaValidationResult).first()
session.close()
print("[OK] All persona tables verified successfully")
return True
else:
print("[WARNING] Warning: Could not get database session")
return False
except Exception as e:
print(f"[WARNING] Warning: Could not verify persona tables: {e}")
return False
def verify_linguistic_analyzer():
"""Verify that the linguistic analyzer is working correctly."""
print("Verifying linguistic analyzer setup...")
try:
from services.persona.enhanced_linguistic_analyzer import EnhancedLinguisticAnalyzer
# Try to initialize the linguistic analyzer
analyzer = EnhancedLinguisticAnalyzer()
# Test with a sample text
test_texts = [
"This is a test sentence for linguistic analysis.",
"ALwrity provides high-quality AI writing assistance.",
"The persona generation system uses advanced NLP techniques."
]
# Perform a simple analysis
analysis_result = analyzer.analyze_writing_style(test_texts)
if analysis_result and 'basic_metrics' in analysis_result:
print("SUCCESS: Linguistic analyzer verified successfully")
print(f" Analyzed {len(test_texts)} text samples")
print(f" Analysis keys: {list(analysis_result.keys())}")
return True
else:
print("WARNING: Linguistic analyzer returned unexpected result")
print(f" Result: {analysis_result}")
return False
except Exception as e:
print(f"WARNING: Could not verify linguistic analyzer: {e}")
return False
def verify_billing_tables():
"""Verify that billing and subscription tables exist and are accessible."""
print("🔍 Verifying billing and subscription tables...")
try:
from services.database import get_db_session
from models.subscription_models import (
SubscriptionPlan, UserSubscription, APIUsageLog,
UsageSummary, APIProviderPricing, UsageAlert
)
session = get_db_session()
if session:
# Try to query all billing tables to verify they exist
session.query(SubscriptionPlan).first()
session.query(UserSubscription).first()
session.query(APIUsageLog).first()
session.query(UsageSummary).first()
session.query(APIProviderPricing).first()
session.query(UsageAlert).first()
session.close()
print("[OK] All billing and subscription tables verified successfully")
return True
else:
print("[WARNING] Warning: Could not get database session")
return False
except Exception as e:
print(f"[WARNING] Warning: Could not verify billing tables: {e}")
return False
def start_backend(enable_reload=False):
def start_backend(enable_reload=False, production_mode=False):
"""Start the backend server."""
print("🚀 Starting ALwrity Backend...")
@@ -527,7 +28,7 @@ def start_backend(enable_reload=False):
os.environ.setdefault("PORT", "8000")
# Set reload based on argument or environment variable
if enable_reload:
if enable_reload and not production_mode:
os.environ.setdefault("RELOAD", "true")
print(" 🔄 Development mode: Auto-reload enabled")
else:
@@ -553,26 +54,20 @@ def start_backend(enable_reload=False):
init_database()
print("[OK] Database initialized successfully")
# Verify persona tables exist
verify_persona_tables()
# Verify linguistic analyzer is working
verify_linguistic_analyzer()
# Verify billing tables exist
verify_billing_tables()
print("\n🌐 Backend is starting...")
print(" 📖 API Documentation: http://localhost:8000/api/docs")
print(" 🔍 Health Check: http://localhost:8000/health")
print(" 📊 ReDoc: http://localhost:8000/api/redoc")
print(" 📈 API Monitoring: http://localhost:8000/api/content-planning/monitoring/health")
print(" 💳 Billing Dashboard: http://localhost:8000/api/subscription/plans")
print(" 📊 Usage Tracking: http://localhost:8000/api/subscription/usage/demo")
if not production_mode:
print(" 📈 API Monitoring: http://localhost:8000/api/content-planning/monitoring/health")
print(" 💳 Billing Dashboard: http://localhost:8000/api/subscription/plans")
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 (default): python start_alwrity_backend.py")
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)
@@ -606,7 +101,8 @@ def start_backend(enable_reload=False):
"temp/*",
"middleware/*",
"models/*",
"scripts/*"
"scripts/*",
"alwrity_utils/*"
],
reload_includes=[
"app.py",
@@ -624,16 +120,25 @@ def start_backend(enable_reload=False):
return True
def main():
"""Main function to set up and start the backend."""
# Parse command line arguments
parser = argparse.ArgumentParser(description="ALwrity Backend Server")
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)")
args = parser.parse_args()
# Determine mode
production_mode = args.production
enable_reload = (args.reload or args.dev) and not production_mode
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'}")
print("=" * 40)
# Check if we're in the right directory
if not os.path.exists("app.py"):
@@ -642,21 +147,63 @@ def main():
print(" Expected files:", [f for f in os.listdir('.') if f.endswith('.py')])
return False
# Initialize modular components
dependency_manager = DependencyManager()
environment_setup = EnvironmentSetup(production_mode=production_mode)
database_setup = DatabaseSetup(production_mode=production_mode)
production_optimizer = ProductionOptimizer()
# Apply production optimizations if needed
if production_mode:
if not production_optimizer.apply_production_optimizations():
print("[ERROR] Production optimization failed")
return False
# Check and install dependencies
if not check_dependencies():
print("[ERROR] Failed to install dependencies")
return False
critical_ok, missing_critical = dependency_manager.check_critical_dependencies()
if not critical_ok:
print("[ERROR] Critical dependencies missing, installing...")
if not dependency_manager.install_requirements():
print("[ERROR] Failed to install dependencies")
return False
# Check optional dependencies (non-critical)
dependency_manager.check_optional_dependencies()
# Setup environment
if not setup_environment():
print("[ERROR] Environment setup failed")
if not environment_setup.setup_directories():
print("[ERROR] Directory setup failed")
return False
# Start backend with reload option
enable_reload = args.reload or args.dev
return start_backend(enable_reload=enable_reload)
if not environment_setup.setup_environment_variables():
print("[ERROR] Environment variable setup failed")
return False
# Create .env file only in development
if not production_mode:
environment_setup.create_env_file()
# Setup database
if not database_setup.setup_essential_tables():
print("[WARNING] Database setup had issues, continuing...")
# Setup advanced features only in development
if not production_mode:
database_setup.setup_advanced_tables()
database_setup.verify_tables()
# Setup linguistic analysis (skip in production)
if not production_mode and not production_optimizer.skip_linguistic_setup():
if not production_optimizer.skip_spacy_setup():
dependency_manager.setup_spacy_model()
if not production_optimizer.skip_nltk_setup():
dependency_manager.setup_nltk_data()
# Start backend
return start_backend(enable_reload=enable_reload, production_mode=production_mode)
if __name__ == "__main__":
success = main()
if not success:
sys.exit(1)
sys.exit(1)

View File

@@ -0,0 +1,11 @@
# Production Environment Variables for Vercel
# Copy this file to .env.production and update with your actual values
# Backend API URL (from Railway/Render deployment)
REACT_APP_API_URL=https://your-backend-url.railway.app
# Environment
REACT_APP_ENVIRONMENT=production
# Optional: Custom domain for OAuth redirects
# REACT_APP_DOMAIN=your-custom-domain.com

View File

@@ -7,9 +7,17 @@ export const setAuthTokenGetter = (getter: () => Promise<string | null>) => {
authTokenGetter = getter;
};
// Create a shared axios instance for all API calls (same-origin; CRA proxy forwards to backend)
// Get API URL from environment variables
const getApiUrl = () => {
if (process.env.NODE_ENV === 'production') {
return process.env.REACT_APP_API_URL || 'https://your-backend-url.railway.app';
}
return ''; // Use proxy in development
};
// Create a shared axios instance for all API calls
export const apiClient = axios.create({
baseURL: '',
baseURL: getApiUrl(),
timeout: 60000, // Increased to 60 seconds for regular API calls
headers: {
'Content-Type': 'application/json',
@@ -18,7 +26,7 @@ export const apiClient = axios.create({
// Create a specialized client for AI operations with extended timeout
export const aiApiClient = axios.create({
baseURL: '',
baseURL: getApiUrl(),
timeout: 180000, // 3 minutes timeout for AI operations (matching 20-25 second responses)
headers: {
'Content-Type': 'application/json',
@@ -27,7 +35,7 @@ export const aiApiClient = axios.create({
// Create a specialized client for long-running operations like SEO analysis
export const longRunningApiClient = axios.create({
baseURL: '',
baseURL: getApiUrl(),
timeout: 300000, // 5 minutes timeout for SEO analysis
headers: {
'Content-Type': 'application/json',
@@ -36,7 +44,7 @@ export const longRunningApiClient = axios.create({
// Create a specialized client for polling operations with reasonable timeout
export const pollingApiClient = axios.create({
baseURL: '',
baseURL: getApiUrl(),
timeout: 60000, // 60 seconds timeout for polling status checks
headers: {
'Content-Type': 'application/json',

View File

@@ -42,53 +42,18 @@ export const useWixConnection = () => {
if (connectedFlag && tokensRaw) {
const tokens = JSON.parse(tokensRaw);
// Try to get actual site information from Wix API
try {
const { createClient, OAuthStrategy } = await import('@wix/sdk');
const wixClient = createClient({
auth: OAuthStrategy({ clientId: '75d88e36-1c76-4009-b769-15f4654556df' })
});
wixClient.auth.setTokens(tokens);
// Get member info to extract site URL
const memberInfo = await wixClient.auth.getMemberInfo();
console.log('Wix member info:', memberInfo);
// Try to extract site URL from member info or use a default
let siteUrl = 'Connected Wix Site';
if (memberInfo?.member?.email) {
// Extract domain from email or use email as identifier
const email = memberInfo.member.email;
const domain = email.split('@')[1];
siteUrl = `https://${domain}`;
}
setStatus({
connected: true,
sites: [{
id: 'wix-site-1',
blog_url: siteUrl,
blog_id: 'wix-blog',
created_at: new Date().toISOString(),
scope: 'BLOG.CREATE-DRAFT,BLOG.PUBLISH,MEDIA.MANAGE'
}],
total_sites: 1
});
} catch (apiError) {
console.log('Wix API error, using fallback:', apiError);
// Fallback if API call fails
setStatus({
connected: true,
sites: [{
id: 'wix-site-1',
blog_url: 'Connected Wix Site',
blog_id: 'wix-blog',
created_at: new Date().toISOString(),
scope: 'BLOG.CREATE-DRAFT,BLOG.PUBLISH,MEDIA.MANAGE'
}],
total_sites: 1
});
}
// Set connected status with site information from tokens
setStatus({
connected: true,
sites: [{
id: 'wix-site-1',
blog_url: 'Connected Wix Site',
blog_id: 'wix-blog',
created_at: new Date().toISOString(),
scope: 'BLOG.CREATE-DRAFT,BLOG.PUBLISH,MEDIA.MANAGE'
}],
total_sites: 1
});
console.log('Wix status checked: connected via sessionStorage');
} else {

21
vercel.json Normal file
View File

@@ -0,0 +1,21 @@
{
"version": 2,
"builds": [
{
"src": "frontend/package.json",
"use": "@vercel/static-build",
"config": {
"distDir": "build"
}
}
],
"routes": [
{
"src": "/(.*)",
"dest": "/frontend/$1"
}
],
"env": {
"REACT_APP_ENVIRONMENT": "production"
}
}