Spaces:
Running
Running
| from fastapi import APIRouter, Depends | |
| from sqlalchemy import func | |
| from sqlalchemy.orm import Session | |
| from datetime import datetime, timedelta, timezone | |
| from app.core.database import get_db | |
| from app.api.deps import get_current_user | |
| from app.models.user import User | |
| from app.models.wardrobe import WardrobeItem | |
| from app.models.chat import ChatMessage | |
| from app.models.tryon import TryOnTask | |
| from app.schemas.subscription import SubscriptionResponse | |
| router = APIRouter() | |
| # Free-tier caps advertised on the landing page's pricing table. | |
| FREE_WARDROBE_ITEM_LIMIT = 30 | |
| FREE_AI_RECOMMENDATIONS_PER_WEEK = 5 | |
| # AI Stylist usage is metered like an LLM API quota, expressed in "credits" | |
| # (1 credit = 1,000 tokens) over a rolling 30-day window. | |
| TOKENS_PER_CREDIT = 1_000 | |
| FREE_AI_CREDIT_LIMIT_PER_30D = 500 | |
| def _build_response(db: Session, user: User) -> SubscriptionResponse: | |
| is_premium = user.plan == "premium" | |
| wardrobe_used = ( | |
| db.query(WardrobeItem).filter(WardrobeItem.user_id == user.id).count() | |
| ) | |
| # "AI outfits" = virtual try-on generations (each one produces one AI-styled outfit). | |
| week_ago = datetime.now(timezone.utc) - timedelta(days=7) | |
| ai_used = ( | |
| db.query(TryOnTask) | |
| .filter( | |
| TryOnTask.user_id == user.id, | |
| TryOnTask.created_at >= week_ago, | |
| ) | |
| .count() | |
| ) | |
| month_ago = datetime.now(timezone.utc) - timedelta(days=30) | |
| tokens_used = ( | |
| db.query(func.coalesce(func.sum(ChatMessage.tokens_used), 0)) | |
| .filter( | |
| ChatMessage.user_id == user.id, | |
| ChatMessage.role == "ai", | |
| ChatMessage.created_at >= month_ago, | |
| ) | |
| .scalar() | |
| ) | |
| # Round up: any partial usage still consumes a whole credit, like real LLM billing. | |
| credits_used = -(-int(tokens_used or 0) // TOKENS_PER_CREDIT) | |
| return SubscriptionResponse( | |
| plan=user.plan, | |
| renews_at=user.premium_renews_at, | |
| wardrobe_items_used=wardrobe_used, | |
| wardrobe_items_limit=None if is_premium else FREE_WARDROBE_ITEM_LIMIT, | |
| ai_recommendations_used=ai_used, | |
| ai_recommendations_limit=None if is_premium else FREE_AI_RECOMMENDATIONS_PER_WEEK, | |
| ai_credits_used=credits_used, | |
| ai_credits_limit=None if is_premium else FREE_AI_CREDIT_LIMIT_PER_30D, | |
| ) | |
| def get_my_subscription( | |
| db: Session = Depends(get_db), | |
| current_user: User = Depends(get_current_user), | |
| ): | |
| return _build_response(db, current_user) | |
| # There is no self-serve payment flow yet, so upgrading to Premium is not exposed here — | |
| # only accounts granted Premium manually (direct DB update) can downgrade themselves back. | |
| def cancel_my_subscription( | |
| db: Session = Depends(get_db), | |
| current_user: User = Depends(get_current_user), | |
| ): | |
| current_user.plan = "free" | |
| current_user.premium_renews_at = None | |
| db.commit() | |
| db.refresh(current_user) | |
| return _build_response(db, current_user) | |