Subscription API and API key injection middleware added

This commit is contained in:
ajaysi
2025-10-19 17:56:09 +05:30
parent 1f087aad4c
commit 2240cefa30
8 changed files with 106 additions and 24 deletions

View File

@@ -35,6 +35,7 @@ class DatabaseSetup:
self._create_monitoring_tables()
self._create_subscription_tables()
self._create_persona_tables()
self._create_onboarding_tables()
if verbose:
print("✅ Essential database tables created")
@@ -97,6 +98,22 @@ class DatabaseSetup:
print(f" ⚠️ Persona tables failed: {e}")
return True # Non-critical
def _create_onboarding_tables(self) -> bool:
"""Create onboarding tables."""
import os
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
try:
from models.onboarding import Base as OnboardingBase
OnboardingBase.metadata.create_all(bind=engine)
if verbose:
print(" ✅ Onboarding tables created")
return True
except Exception as e:
if verbose:
print(f" ⚠️ Onboarding tables failed: {e}")
return True # Non-critical
def verify_tables(self) -> bool:
"""Verify that essential tables exist."""
import os
@@ -120,7 +137,9 @@ class DatabaseSetup:
essential_tables = [
'api_monitoring_logs',
'subscription_plans',
'user_subscriptions'
'user_subscriptions',
'onboarding_sessions',
'persona_data'
]
existing_tables = [table for table in essential_tables if table in tables]

View File

@@ -186,8 +186,12 @@ class OnboardingSummaryService:
async def get_research_preferences_data(self) -> Dict[str, Any]:
"""Get research preferences data for the user."""
try:
research_prefs_service = ResearchPreferencesService()
return await research_prefs_service.get_research_preferences(self.user_id)
db = next(get_db())
research_prefs_service = ResearchPreferencesService(db)
# Use the new method that accepts user_id directly
result = research_prefs_service.get_research_preferences_by_user_id(self.user_id)
db.close()
return result
except Exception as e:
logger.error(f"Error getting research preferences data: {e}")
raise

View File

@@ -13,6 +13,7 @@ from functools import lru_cache
from services.database import get_db
from services.usage_tracking_service import UsageTrackingService
from services.pricing_service import PricingService
from middleware.auth_middleware import get_current_user
from models.subscription_models import (
APIProvider, SubscriptionPlan, UserSubscription, UsageSummary,
APIProviderPricing, UsageAlert, SubscriptionTier, BillingCycle, UsageStatus
@@ -30,10 +31,15 @@ _DASHBOARD_CACHE_TTL_SEC = 2.0
async def get_user_usage(
user_id: str,
billing_period: Optional[str] = Query(None, description="Billing period (YYYY-MM)"),
db: Session = Depends(get_db)
db: Session = Depends(get_db),
current_user: Dict[str, Any] = Depends(get_current_user)
) -> Dict[str, Any]:
"""Get comprehensive usage statistics for a user."""
# Verify user can only access their own data
if current_user.get('id') != user_id:
raise HTTPException(status_code=403, detail="Access denied")
try:
usage_service = UsageTrackingService(db)
stats = usage_service.get_user_usage_stats(user_id, billing_period)
@@ -122,10 +128,15 @@ async def get_subscription_plans(
@router.get("/user/{user_id}/subscription")
async def get_user_subscription(
user_id: str,
db: Session = Depends(get_db)
db: Session = Depends(get_db),
current_user: Dict[str, Any] = Depends(get_current_user)
) -> Dict[str, Any]:
"""Get user's current subscription information."""
# Verify user can only access their own data
if current_user.get('id') != user_id:
raise HTTPException(status_code=403, detail="Access denied")
try:
subscription = db.query(UserSubscription).filter(
UserSubscription.user_id == user_id,
@@ -212,10 +223,15 @@ async def get_user_subscription(
@router.get("/status/{user_id}")
async def get_subscription_status(
user_id: str,
db: Session = Depends(get_db)
db: Session = Depends(get_db),
current_user: Dict[str, Any] = Depends(get_current_user)
) -> Dict[str, Any]:
"""Get simple subscription status for enforcement checks."""
# Verify user can only access their own data
if current_user.get('id') != user_id:
raise HTTPException(status_code=403, detail="Access denied")
try:
subscription = db.query(UserSubscription).filter(
UserSubscription.user_id == user_id,

View File

@@ -120,7 +120,8 @@ async def rate_limit_middleware(request: Request, call_next):
return await rate_limiter.rate_limit_middleware(request, call_next)
# 3. LAST REGISTERED (runs FIRST) - API key injection
# API key injection middleware removed - now using environment variables directly
from middleware.api_key_injection_middleware import api_key_injection_middleware
app.middleware("http")(api_key_injection_middleware)
# Health check endpoints using modular utilities
@app.get("/health")

View File

@@ -41,12 +41,17 @@ class APIKeyInjectionMiddleware:
if user:
# Try different possible keys for user_id
user_id = user.get('user_id') or user.get('clerk_user_id') or user.get('id')
logger.debug(f"[API Key Injection] Extracted user_id: {user_id}")
# Store user_id in request.state for monitoring middleware
request.state.user_id = user_id
if user_id:
logger.info(f"[API Key Injection] Extracted user_id: {user_id}")
# Store user_id in request.state for monitoring middleware
request.state.user_id = user_id
else:
logger.warning(f"[API Key Injection] User object missing ID: {user}")
else:
logger.warning("[API Key Injection] Token verification failed")
except Exception as e:
logger.debug(f"[API Key Injection] Could not extract user from token: {e}")
logger.error(f"[API Key Injection] Could not extract user from token: {e}")
if not user_id:
# No authenticated user, proceed without injection

View File

@@ -488,9 +488,9 @@ async def monitoring_middleware(request: Request, call_next):
# Check for authorization header with user info
elif 'authorization' in request.headers:
# Auth middleware should have set request.state.user_id
# If not, skip usage limits (unauthenticated or auth will handle)
# If not, this indicates an authentication failure that should be logged
user_id = None
logger.debug("Monitoring: Auth header present but no user_id in state - skipping limits")
logger.warning("Monitoring: Auth header present but no user_id in state - authentication may have failed")
# For alpha testing, use IP address as user identifier if no other ID found
# But only if there's no auth header (truly anonymous)

View File

@@ -101,6 +101,32 @@ class ResearchPreferencesService:
logger.error(f"Error getting research preferences: {e}")
return None
def get_research_preferences_by_user_id(self, user_id: str) -> Optional[Dict[str, Any]]:
"""
Get research preferences for a user by their Clerk user ID.
Args:
user_id: Clerk user ID (string)
Returns:
Research preferences data or None if not found
"""
try:
# First get the onboarding session for this user
session = self.db.query(OnboardingSession).filter_by(user_id=user_id).first()
if not session:
logger.warning(f"No onboarding session found for user {user_id}")
return None
# Then get the research preferences for that session
preferences = self.db.query(ResearchPreferences).filter_by(session_id=session.id).first()
if preferences:
return preferences.to_dict()
return None
except Exception as e:
logger.error(f"Error getting research preferences by user_id: {e}")
return None
def get_style_data_from_analysis(self, session_id: int) -> Optional[Dict[str, Any]]:
"""
Get style detection data from website analysis for a session.

View File

@@ -64,13 +64,19 @@ const BackgroundJobManager: React.FC<BackgroundJobManagerProps> = ({
const [loading, setLoading] = useState(false);
const [selectedJob, setSelectedJob] = useState<Job | null>(null);
const [jobDialogOpen, setJobDialogOpen] = useState(false);
const [hasRunningJobs, setHasRunningJobs] = useState(false);
// Fetch user jobs
const fetchJobs = useCallback(async () => {
try {
const response = await apiClient.get('/api/background-jobs/user-jobs?limit=10');
if (response.data.success) {
setJobs(response.data.data.jobs || []);
const newJobs = response.data.data.jobs || [];
setJobs(newJobs);
// Update running jobs state
const runningJobs = newJobs.some((job: Job) => job.status === 'running' || job.status === 'pending');
setHasRunningJobs(runningJobs);
}
} catch (error) {
console.error('Error fetching jobs:', error);
@@ -204,16 +210,21 @@ const BackgroundJobManager: React.FC<BackgroundJobManagerProps> = ({
useEffect(() => {
fetchJobs();
// Poll every 5 seconds for running jobs
const interval = setInterval(() => {
const hasRunningJobs = jobs.some(job => job.status === 'running' || job.status === 'pending');
if (hasRunningJobs) {
fetchJobs();
}
}, 5000);
// Only start polling if there are running jobs
let interval: NodeJS.Timeout | null = null;
if (hasRunningJobs) {
interval = setInterval(() => {
fetchJobs().catch(console.error);
}, 5000);
}
return () => clearInterval(interval);
}, [fetchJobs, jobs]);
return () => {
if (interval) {
clearInterval(interval);
}
};
}, [fetchJobs, hasRunningJobs]); // Only depend on hasRunningJobs state
return (
<Box>