Pricing Page and Subscription Guard

This commit is contained in:
ajaysi
2025-10-13 10:25:57 +05:30
parent 20b01717cd
commit c38812b6c5
11 changed files with 1838 additions and 19 deletions

View File

@@ -14,8 +14,8 @@ from services.database import get_db
from services.usage_tracking_service import UsageTrackingService
from services.pricing_service import PricingService
from models.subscription_models import (
APIProvider, SubscriptionPlan, UserSubscription, UsageSummary,
APIProviderPricing, UsageAlert, SubscriptionTier
APIProvider, SubscriptionPlan, UserSubscription, UsageSummary,
APIProviderPricing, UsageAlert, SubscriptionTier, BillingCycle
)
router = APIRouter(prefix="/api/subscription", tags=["subscription"])
@@ -209,6 +209,181 @@ async def get_user_subscription(
logger.error(f"Error getting user subscription: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/status/{user_id}")
async def get_subscription_status(
user_id: str,
db: Session = Depends(get_db)
) -> Dict[str, Any]:
"""Get simple subscription status for enforcement checks."""
try:
subscription = db.query(UserSubscription).filter(
UserSubscription.user_id == user_id,
UserSubscription.is_active == True
).first()
if not subscription:
# Check if free tier exists
free_plan = db.query(SubscriptionPlan).filter(
SubscriptionPlan.tier == SubscriptionTier.FREE,
SubscriptionPlan.is_active == True
).first()
if free_plan:
return {
"success": True,
"data": {
"active": True,
"plan": "free",
"tier": "free",
"can_use_api": True,
"limits": {
"gemini_calls": free_plan.gemini_calls_limit,
"openai_calls": free_plan.openai_calls_limit,
"anthropic_calls": free_plan.anthropic_calls_limit,
"mistral_calls": free_plan.mistral_calls_limit,
"tavily_calls": free_plan.tavily_calls_limit,
"serper_calls": free_plan.serper_calls_limit,
"metaphor_calls": free_plan.metaphor_calls_limit,
"firecrawl_calls": free_plan.firecrawl_calls_limit,
"stability_calls": free_plan.stability_calls_limit,
"monthly_cost": free_plan.monthly_cost_limit
}
}
}
else:
return {
"success": True,
"data": {
"active": False,
"plan": "none",
"tier": "none",
"can_use_api": False,
"reason": "No active subscription or free tier found"
}
}
# Check if subscription is within valid period
now = datetime.utcnow()
if subscription.current_period_end < now:
return {
"success": True,
"data": {
"active": False,
"plan": subscription.plan.tier.value,
"tier": subscription.plan.tier.value,
"can_use_api": False,
"reason": "Subscription expired"
}
}
return {
"success": True,
"data": {
"active": True,
"plan": subscription.plan.tier.value,
"tier": subscription.plan.tier.value,
"can_use_api": True,
"limits": {
"gemini_calls": subscription.plan.gemini_calls_limit,
"openai_calls": subscription.plan.openai_calls_limit,
"anthropic_calls": subscription.plan.anthropic_calls_limit,
"mistral_calls": subscription.plan.mistral_calls_limit,
"tavily_calls": subscription.plan.tavily_calls_limit,
"serper_calls": subscription.plan.serper_calls_limit,
"metaphor_calls": subscription.plan.metaphor_calls_limit,
"firecrawl_calls": subscription.plan.firecrawl_calls_limit,
"stability_calls": subscription.plan.stability_calls_limit,
"monthly_cost": subscription.plan.monthly_cost_limit
}
}
}
except Exception as e:
logger.error(f"Error getting subscription status: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/subscribe/{user_id}")
async def subscribe_to_plan(
user_id: str,
subscription_data: dict,
db: Session = Depends(get_db)
) -> Dict[str, Any]:
"""Create or update a user's subscription."""
try:
plan_id = subscription_data.get('plan_id')
billing_cycle = subscription_data.get('billing_cycle', 'monthly')
if not plan_id:
raise HTTPException(status_code=400, detail="plan_id is required")
# Get the plan
plan = db.query(SubscriptionPlan).filter(
SubscriptionPlan.id == plan_id,
SubscriptionPlan.is_active == True
).first()
if not plan:
raise HTTPException(status_code=404, detail="Plan not found")
# Check if user already has an active subscription
existing_subscription = db.query(UserSubscription).filter(
UserSubscription.user_id == user_id,
UserSubscription.is_active == True
).first()
now = datetime.utcnow()
if existing_subscription:
# Update existing subscription
existing_subscription.plan_id = plan_id
existing_subscription.billing_cycle = BillingCycle(billing_cycle)
existing_subscription.current_period_start = now
existing_subscription.current_period_end = now + timedelta(
days=365 if billing_cycle == 'yearly' else 30
)
existing_subscription.updated_at = now
subscription = existing_subscription
else:
# Create new subscription
subscription = UserSubscription(
user_id=user_id,
plan_id=plan_id,
billing_cycle=BillingCycle(billing_cycle),
current_period_start=now,
current_period_end=now + timedelta(
days=365 if billing_cycle == 'yearly' else 30
),
status=UsageStatus.ACTIVE,
is_active=True,
auto_renew=True
)
db.add(subscription)
db.commit()
return {
"success": True,
"message": f"Successfully subscribed to {plan.name}",
"data": {
"subscription_id": subscription.id,
"plan_name": plan.name,
"billing_cycle": billing_cycle,
"current_period_start": subscription.current_period_start.isoformat(),
"current_period_end": subscription.current_period_end.isoformat(),
"status": subscription.status.value
}
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error subscribing to plan: {e}")
db.rollback()
raise HTTPException(status_code=500, detail=str(e))
@router.get("/pricing")
async def get_api_pricing(
provider: Optional[str] = Query(None, description="API provider"),

View File

@@ -97,9 +97,8 @@ app.add_middleware(
allow_headers=["*"],
)
# Add API monitoring middleware
# Temporarily disabled for Wix testing
# app.middleware("http")(monitoring_middleware)
# Add API monitoring middleware for subscription enforcement
app.middleware("http")(monitoring_middleware)
# Initialize modular utilities
health_checker = HealthChecker()

View File

@@ -0,0 +1,247 @@
"""
Script to remove Alpha subscription plans and update limits for production testing.
Only keeps: Free, Basic, Pro, Enterprise with updated feature limits.
"""
import sys
import os
from pathlib import Path
# Add the backend directory to Python path
backend_dir = Path(__file__).parent.parent
sys.path.insert(0, str(backend_dir))
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from loguru import logger
from models.subscription_models import SubscriptionPlan, SubscriptionTier
from services.database import DATABASE_URL
def cleanup_alpha_plans():
"""Remove alpha subscription plans and update limits."""
try:
engine = create_engine(DATABASE_URL, echo=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
db = SessionLocal()
try:
# Delete all plans with "Alpha" in the name
alpha_plans = db.query(SubscriptionPlan).filter(
SubscriptionPlan.name.like('%Alpha%')
).all()
for plan in alpha_plans:
logger.info(f"Deleting Alpha plan: {plan.name}")
db.delete(plan)
db.commit()
logger.info(f"✅ Deleted {len(alpha_plans)} Alpha plans")
# Update existing plans with new limits
logger.info("Updating plan limits...")
# Free Plan - Blog, LinkedIn, Facebook writers + Text & Image only
free_plan = db.query(SubscriptionPlan).filter(
SubscriptionPlan.tier == SubscriptionTier.FREE
).first()
if free_plan:
free_plan.name = "Free"
free_plan.description = "Perfect for trying ALwrity with Blog, LinkedIn & Facebook writers"
free_plan.gemini_calls_limit = 100
free_plan.openai_calls_limit = 50
free_plan.anthropic_calls_limit = 0
free_plan.mistral_calls_limit = 50
free_plan.tavily_calls_limit = 20
free_plan.serper_calls_limit = 20
free_plan.metaphor_calls_limit = 10
free_plan.firecrawl_calls_limit = 10
free_plan.stability_calls_limit = 10 # Image generation
free_plan.gemini_tokens_limit = 100000
free_plan.monthly_cost_limit = 5.0
free_plan.features = [
"blog_writer",
"linkedin_writer",
"facebook_writer",
"text_generation",
"image_generation",
"wix_integration",
"wordpress_integration",
"gsc_integration"
]
logger.info("✅ Updated Free plan")
# Basic Plan - Blog, LinkedIn, Facebook writers + Text & Image only
basic_plan = db.query(SubscriptionPlan).filter(
SubscriptionPlan.tier == SubscriptionTier.BASIC
).first()
if basic_plan:
basic_plan.name = "Basic"
basic_plan.description = "Great for solopreneurs with Blog, LinkedIn & Facebook writers"
basic_plan.price_monthly = 29.0
basic_plan.price_yearly = 278.0 # ~20% discount
basic_plan.gemini_calls_limit = 500
basic_plan.openai_calls_limit = 250
basic_plan.anthropic_calls_limit = 100
basic_plan.mistral_calls_limit = 250
basic_plan.tavily_calls_limit = 100
basic_plan.serper_calls_limit = 100
basic_plan.metaphor_calls_limit = 50
basic_plan.firecrawl_calls_limit = 50
basic_plan.stability_calls_limit = 50 # Image generation
basic_plan.gemini_tokens_limit = 500000
basic_plan.openai_tokens_limit = 250000
basic_plan.monthly_cost_limit = 25.0
basic_plan.features = [
"blog_writer",
"linkedin_writer",
"facebook_writer",
"text_generation",
"image_generation",
"wix_integration",
"wordpress_integration",
"gsc_integration",
"priority_support"
]
logger.info("✅ Updated Basic plan")
# Pro Plan - 6 Social Platforms + Website Management + Text, Image, Audio, Video
pro_plan = db.query(SubscriptionPlan).filter(
SubscriptionPlan.tier == SubscriptionTier.PRO
).first()
if pro_plan:
pro_plan.name = "Pro"
pro_plan.description = "Perfect for businesses with 6 social platforms & multimodal AI"
pro_plan.price_monthly = 79.0
pro_plan.price_yearly = 758.0 # ~20% discount
pro_plan.gemini_calls_limit = 2000
pro_plan.openai_calls_limit = 1000
pro_plan.anthropic_calls_limit = 500
pro_plan.mistral_calls_limit = 1000
pro_plan.tavily_calls_limit = 500
pro_plan.serper_calls_limit = 500
pro_plan.metaphor_calls_limit = 250
pro_plan.firecrawl_calls_limit = 250
pro_plan.stability_calls_limit = 200 # Image generation
pro_plan.gemini_tokens_limit = 2000000
pro_plan.openai_tokens_limit = 1000000
pro_plan.anthropic_tokens_limit = 500000
pro_plan.monthly_cost_limit = 100.0
pro_plan.features = [
"blog_writer",
"linkedin_writer",
"facebook_writer",
"instagram_writer",
"twitter_writer",
"tiktok_writer",
"youtube_writer",
"text_generation",
"image_generation",
"audio_generation",
"video_generation",
"wix_integration",
"wordpress_integration",
"gsc_integration",
"website_management",
"content_scheduling",
"advanced_analytics",
"priority_support"
]
logger.info("✅ Updated Pro plan")
# Enterprise Plan - Unlimited with all features
enterprise_plan = db.query(SubscriptionPlan).filter(
SubscriptionPlan.tier == SubscriptionTier.ENTERPRISE
).first()
if enterprise_plan:
enterprise_plan.name = "Enterprise"
enterprise_plan.description = "For large teams with unlimited usage & custom integrations"
enterprise_plan.price_monthly = 199.0
enterprise_plan.price_yearly = 1908.0 # ~20% discount
enterprise_plan.gemini_calls_limit = 0 # Unlimited
enterprise_plan.openai_calls_limit = 0
enterprise_plan.anthropic_calls_limit = 0
enterprise_plan.mistral_calls_limit = 0
enterprise_plan.tavily_calls_limit = 0
enterprise_plan.serper_calls_limit = 0
enterprise_plan.metaphor_calls_limit = 0
enterprise_plan.firecrawl_calls_limit = 0
enterprise_plan.stability_calls_limit = 0
enterprise_plan.gemini_tokens_limit = 0
enterprise_plan.openai_tokens_limit = 0
enterprise_plan.anthropic_tokens_limit = 0
enterprise_plan.mistral_tokens_limit = 0
enterprise_plan.monthly_cost_limit = 0.0 # Unlimited
enterprise_plan.features = [
"blog_writer",
"linkedin_writer",
"facebook_writer",
"instagram_writer",
"twitter_writer",
"tiktok_writer",
"youtube_writer",
"text_generation",
"image_generation",
"audio_generation",
"video_generation",
"wix_integration",
"wordpress_integration",
"gsc_integration",
"website_management",
"content_scheduling",
"advanced_analytics",
"custom_integrations",
"dedicated_account_manager",
"white_label",
"priority_support"
]
logger.info("✅ Updated Enterprise plan")
db.commit()
logger.info("✅ All plans updated successfully!")
# Display summary
logger.info("\n" + "="*60)
logger.info("SUBSCRIPTION PLANS SUMMARY")
logger.info("="*60)
all_plans = db.query(SubscriptionPlan).filter(
SubscriptionPlan.is_active == True
).order_by(SubscriptionPlan.price_monthly).all()
for plan in all_plans:
logger.info(f"\n{plan.name} ({plan.tier.value})")
logger.info(f" Price: ${plan.price_monthly}/mo, ${plan.price_yearly}/yr")
logger.info(f" Gemini: {plan.gemini_calls_limit if plan.gemini_calls_limit > 0 else 'Unlimited'} calls/month")
logger.info(f" OpenAI: {plan.openai_calls_limit if plan.openai_calls_limit > 0 else 'Unlimited'} calls/month")
logger.info(f" Research: {plan.tavily_calls_limit if plan.tavily_calls_limit > 0 else 'Unlimited'} searches/month")
logger.info(f" Images: {plan.stability_calls_limit if plan.stability_calls_limit > 0 else 'Unlimited'} images/month")
logger.info(f" Features: {', '.join(plan.features or [])}")
logger.info("\n" + "="*60)
finally:
db.close()
except Exception as e:
logger.error(f"❌ Error cleaning up plans: {e}")
import traceback
logger.error(traceback.format_exc())
raise
if __name__ == "__main__":
logger.info("🚀 Starting subscription plans cleanup...")
try:
cleanup_alpha_plans()
logger.info("✅ Cleanup completed successfully!")
except Exception as e:
logger.error(f"❌ Cleanup failed: {e}")
sys.exit(1)