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

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")