Subscription API and API key injection middleware added
This commit is contained in:
@@ -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]
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user