diff --git a/app/api/files.py b/app/api/files.py index 35fb3022..35691a7b 100644 --- a/app/api/files.py +++ b/app/api/files.py @@ -1303,7 +1303,8 @@ async def ui_upload(request: Request, db: DbSession, file: UploadFile = File(... try: check_upload_allowed(db, upload_owner_id, tier_id) except QuotaExceeded as qe: - # Clean up the already-saved file before rejecting + # Clean up the temporarily written file before returning the error + # to avoid consuming disk space for a rejected upload. if os.path.exists(target_path): os.remove(target_path) raise HTTPException( diff --git a/app/api/subscriptions.py b/app/api/subscriptions.py index e34dfb69..031d5527 100644 --- a/app/api/subscriptions.py +++ b/app/api/subscriptions.py @@ -14,6 +14,7 @@ from fastapi import APIRouter, Depends, HTTPException, Request, status from sqlalchemy import func from sqlalchemy.orm import Session +from app.api.admin_users import _require_admin from app.database import get_db from app.utils.subscription import ( TIER_ORDER, @@ -29,22 +30,7 @@ logger = logging.getLogger(__name__) router = APIRouter(prefix="/subscriptions", tags=["subscriptions"]) DbSession = Annotated[Session, Depends(get_db)] - - -# --------------------------------------------------------------------------- -# Auth helpers -# --------------------------------------------------------------------------- - - -def _get_current_user(request: Request) -> dict | None: - return request.session.get("user") - - -def _require_admin(request: Request) -> dict: - user = request.session.get("user") - if not user or not user.get("is_admin"): - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Admin access required") - return user +AdminUser = Annotated[dict, Depends(_require_admin)] # --------------------------------------------------------------------------- @@ -67,7 +53,7 @@ def my_subscription(request: Request, db: DbSession) -> dict[str, Any]: """Return the authenticated user's subscription tier and current usage counts.""" from app.config import settings - user = _get_current_user(request) + user = request.session.get("user") if not settings.multi_user_enabled: # In single-user mode there is no concept of a subscription plan @@ -94,10 +80,8 @@ def my_subscription(request: Request, db: DbSession) -> dict[str, Any]: @router.get("/platform", summary="Platform-wide usage statistics (admin only)") -def platform_stats(request: Request, db: DbSession) -> dict[str, Any]: +def platform_stats(request: Request, db: DbSession, _admin: AdminUser) -> dict[str, Any]: """Return aggregate statistics across all users and tiers (admin only).""" - _require_admin(request) - from app.models import FileRecord, UserProfile today = datetime.now(timezone.utc).date() diff --git a/app/utils/subscription.py b/app/utils/subscription.py index 711c0888..ecdfa8a2 100644 --- a/app/utils/subscription.py +++ b/app/utils/subscription.py @@ -170,15 +170,17 @@ def _today_utc() -> date: return datetime.now(timezone.utc).date() +def _scalar_count(query) -> int: + """Execute a count query and return an int, defaulting to 0 for NULL.""" + return query.scalar() or 0 + + def get_lifetime_file_count(db: Session, owner_id: str) -> int: """Total files ever processed by this user (not counting duplicates).""" from app.models import FileRecord - return ( - db.query(func.count(FileRecord.id)) - .filter(FileRecord.owner_id == owner_id, FileRecord.is_duplicate.is_(False)) - .scalar() - or 0 + return _scalar_count( + db.query(func.count(FileRecord.id)).filter(FileRecord.owner_id == owner_id, FileRecord.is_duplicate.is_(False)) ) @@ -187,15 +189,12 @@ def get_today_file_count(db: Session, owner_id: str) -> int: from app.models import FileRecord today = _today_utc() - return ( - db.query(func.count(FileRecord.id)) - .filter( + return _scalar_count( + db.query(func.count(FileRecord.id)).filter( FileRecord.owner_id == owner_id, FileRecord.is_duplicate.is_(False), func.date(FileRecord.created_at) == today, ) - .scalar() - or 0 ) @@ -204,15 +203,12 @@ def get_month_file_count(db: Session, owner_id: str) -> int: from app.models import FileRecord today = _today_utc() - return ( - db.query(func.count(FileRecord.id)) - .filter( + return _scalar_count( + db.query(func.count(FileRecord.id)).filter( FileRecord.owner_id == owner_id, FileRecord.is_duplicate.is_(False), func.strftime("%Y-%m", FileRecord.created_at) == today.strftime("%Y-%m"), ) - .scalar() - or 0 ) diff --git a/frontend/templates/index.html b/frontend/templates/index.html index 3e6cd250..eaf520a1 100644 --- a/frontend/templates/index.html +++ b/frontend/templates/index.html @@ -113,7 +113,7 @@ {% if user_tier.lifetime_file_limit > 0 %} {% set pct = ([((user_usage.lifetime / user_tier.lifetime_file_limit) * 100) | int, 100] | min) %} -
@@ -132,7 +132,7 @@
{% if user_tier.daily_upload_limit > 0 %} {% set pct = ([((user_usage.today / user_tier.daily_upload_limit) * 100) | int, 100] | min) %} -
@@ -151,7 +151,7 @@
{% if user_tier.monthly_upload_limit > 0 %} {% set pct = ([((user_usage.month / user_tier.monthly_upload_limit) * 100) | int, 100] | min) %} -
diff --git a/frontend/templates/pricing.html b/frontend/templates/pricing.html index cc267bf6..fe4d58a1 100644 --- a/frontend/templates/pricing.html +++ b/frontend/templates/pricing.html @@ -31,9 +31,6 @@ > Annual Save ~17% - - -
@@ -221,7 +218,7 @@ {% if tier.max_ocr_pages_monthly == 0 %} Unlimited {% else %} - {{ tier.max_ocr_pages_monthly | int | string | replace("2500", "2 500") }} + {{ "{:,}".format(tier.max_ocr_pages_monthly) }} {% endif %} {% endfor %} diff --git a/frontend/templates/subscription.html b/frontend/templates/subscription.html index 60b302c6..ef9a476b 100644 --- a/frontend/templates/subscription.html +++ b/frontend/templates/subscription.html @@ -68,7 +68,7 @@ {% if tier.lifetime_file_limit > 0 %}
{% set lifetime_pct = ((usage.lifetime / tier.lifetime_file_limit) * 100) | int %} -
0 %}
{% set today_pct = ((usage.today / tier.daily_upload_limit) * 100) | int %} -
0 %}
{% set month_pct = ((usage.month / tier.monthly_upload_limit) * 100) | int %} -