""" Tier Manager — Enforces usage limits for unregistered users. Uses in-memory session tracking keyed by a session token from the frontend (stored in localStorage). Lightweight — no database or IP tracking needed. Unregistered limits: - 5 processing sessions per rolling 7-day window - 3 files per session - 10MB max per file Registered users bypass all limits (placeholder for Supabase auth integration). """ import time from collections import defaultdict from models.schemas import TierStatus, TierCheckResponse # Rolling window: 7 days in seconds WEEK_SECONDS = 7 * 24 * 60 * 60 # In-memory store: { session_token: [timestamp1, timestamp2, ...] } _usage_store: dict[str, list[float]] = defaultdict(list) # Track files per active session: { session_token: int } _session_file_counts: dict[str, int] = defaultdict(int) # Limits UNREGISTERED_SESSIONS_PER_WEEK = 5 UNREGISTERED_FILES_PER_SESSION = 3 UNREGISTERED_MAX_FILE_SIZE_MB = 10 def _prune_old_entries(session_token: str) -> None: """Remove usage entries older than 7 days.""" cutoff = time.time() - WEEK_SECONDS _usage_store[session_token] = [ ts for ts in _usage_store[session_token] if ts > cutoff ] def get_tier_status(session_token: str, is_registered: bool = False) -> TierStatus: """Get the current usage status for a session.""" if is_registered: return TierStatus( is_registered=True, sessions_used=0, sessions_limit=999999, files_this_session=0, files_limit=999999, max_file_size_mb=50, ) _prune_old_entries(session_token) return TierStatus( is_registered=False, sessions_used=len(_usage_store[session_token]), sessions_limit=UNREGISTERED_SESSIONS_PER_WEEK, files_this_session=_session_file_counts.get(session_token, 0), files_limit=UNREGISTERED_FILES_PER_SESSION, max_file_size_mb=UNREGISTERED_MAX_FILE_SIZE_MB, ) def check_upload_allowed( session_token: str, file_size_bytes: int, is_registered: bool = False, ) -> TierCheckResponse: """ Check if the user is allowed to upload a file. Returns a TierCheckResponse indicating whether the upload is permitted. """ tier = get_tier_status(session_token, is_registered) # Registered users bypass all limits if is_registered: return TierCheckResponse(allowed=True, tier=tier, message="Registered user — no limits.") # Check weekly session limit if tier.sessions_used >= UNREGISTERED_SESSIONS_PER_WEEK: return TierCheckResponse( allowed=False, tier=tier, message=f"Weekly limit reached ({UNREGISTERED_SESSIONS_PER_WEEK} sessions). Register for unlimited access!", ) # Check files-per-session limit if tier.files_this_session >= UNREGISTERED_FILES_PER_SESSION: return TierCheckResponse( allowed=False, tier=tier, message=f"File limit reached ({UNREGISTERED_FILES_PER_SESSION} files per session). Register for unlimited uploads!", ) # Check file size max_bytes = UNREGISTERED_MAX_FILE_SIZE_MB * 1024 * 1024 if file_size_bytes > max_bytes: return TierCheckResponse( allowed=False, tier=tier, message=f"File too large (max {UNREGISTERED_MAX_FILE_SIZE_MB}MB for free tier). Register for larger uploads!", ) return TierCheckResponse(allowed=True, tier=tier, message="Upload permitted.") def record_usage(session_token: str) -> None: """Record a processing session usage event.""" _prune_old_entries(session_token) _usage_store[session_token].append(time.time()) _session_file_counts[session_token] = _session_file_counts.get(session_token, 0) + 1 def reset_session_files(session_token: str) -> None: """Reset the file count for a new session (called when session starts fresh).""" _session_file_counts[session_token] = 0