- Add comprehensive unit tests for FeatureService (24 tests) - Add comprehensive unit tests for UsageService (11 tests) - Fix API-002/API-003 architecture violations in feature/usage APIs - Move database queries from API layer to service layer - Create UsageService for usage and limits management - Create custom exceptions (FeatureNotFoundError, TierNotFoundError) - Fix ValidationException usage in content_pages.py - Refactor vendor features API to use proper response models - All 35 new tests passing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
178 lines
4.9 KiB
Python
178 lines
4.9 KiB
Python
# app/api/v1/vendor/usage.py
|
|
"""
|
|
Vendor usage and limits API endpoints.
|
|
|
|
Provides endpoints for:
|
|
- Current usage vs limits
|
|
- Upgrade recommendations
|
|
- Approaching limit warnings
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.api.deps import get_current_vendor_api
|
|
from app.core.database import get_db
|
|
from app.services.usage_service import usage_service
|
|
from models.database.user import User
|
|
|
|
router = APIRouter(prefix="/usage")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# ============================================================================
|
|
# Response Schemas
|
|
# ============================================================================
|
|
|
|
|
|
class UsageMetric(BaseModel):
|
|
"""Single usage metric."""
|
|
|
|
name: str
|
|
current: int
|
|
limit: int | None # None = unlimited
|
|
percentage: float # 0-100, or 0 if unlimited
|
|
is_unlimited: bool
|
|
is_at_limit: bool
|
|
is_approaching_limit: bool # >= 80%
|
|
|
|
|
|
class TierInfo(BaseModel):
|
|
"""Current tier information."""
|
|
|
|
code: str
|
|
name: str
|
|
price_monthly_cents: int
|
|
is_highest_tier: bool
|
|
|
|
|
|
class UpgradeTierInfo(BaseModel):
|
|
"""Next tier upgrade information."""
|
|
|
|
code: str
|
|
name: str
|
|
price_monthly_cents: int
|
|
price_increase_cents: int
|
|
benefits: list[str]
|
|
|
|
|
|
class UsageResponse(BaseModel):
|
|
"""Full usage response with limits and upgrade info."""
|
|
|
|
tier: TierInfo
|
|
usage: list[UsageMetric]
|
|
has_limits_approaching: bool
|
|
has_limits_reached: bool
|
|
upgrade_available: bool
|
|
upgrade_tier: UpgradeTierInfo | None = None
|
|
upgrade_reasons: list[str]
|
|
|
|
|
|
class LimitCheckResponse(BaseModel):
|
|
"""Response for checking a specific limit."""
|
|
|
|
limit_type: str
|
|
can_proceed: bool
|
|
current: int
|
|
limit: int | None
|
|
percentage: float
|
|
message: str | None = None
|
|
upgrade_tier_code: str | None = None
|
|
upgrade_tier_name: str | None = None
|
|
|
|
|
|
# ============================================================================
|
|
# Endpoints
|
|
# ============================================================================
|
|
|
|
|
|
@router.get("", response_model=UsageResponse)
|
|
def get_usage(
|
|
current_user: User = Depends(get_current_vendor_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Get current usage, limits, and upgrade recommendations.
|
|
|
|
Returns comprehensive usage info for displaying in dashboard
|
|
and determining when to show upgrade prompts.
|
|
"""
|
|
vendor_id = current_user.token_vendor_id
|
|
|
|
# Get usage data from service
|
|
usage_data = usage_service.get_vendor_usage(db, vendor_id)
|
|
|
|
# Convert to response
|
|
return UsageResponse(
|
|
tier=TierInfo(
|
|
code=usage_data.tier.code,
|
|
name=usage_data.tier.name,
|
|
price_monthly_cents=usage_data.tier.price_monthly_cents,
|
|
is_highest_tier=usage_data.tier.is_highest_tier,
|
|
),
|
|
usage=[
|
|
UsageMetric(
|
|
name=m.name,
|
|
current=m.current,
|
|
limit=m.limit,
|
|
percentage=m.percentage,
|
|
is_unlimited=m.is_unlimited,
|
|
is_at_limit=m.is_at_limit,
|
|
is_approaching_limit=m.is_approaching_limit,
|
|
)
|
|
for m in usage_data.usage
|
|
],
|
|
has_limits_approaching=usage_data.has_limits_approaching,
|
|
has_limits_reached=usage_data.has_limits_reached,
|
|
upgrade_available=usage_data.upgrade_available,
|
|
upgrade_tier=(
|
|
UpgradeTierInfo(
|
|
code=usage_data.upgrade_tier.code,
|
|
name=usage_data.upgrade_tier.name,
|
|
price_monthly_cents=usage_data.upgrade_tier.price_monthly_cents,
|
|
price_increase_cents=usage_data.upgrade_tier.price_increase_cents,
|
|
benefits=usage_data.upgrade_tier.benefits,
|
|
)
|
|
if usage_data.upgrade_tier
|
|
else None
|
|
),
|
|
upgrade_reasons=usage_data.upgrade_reasons,
|
|
)
|
|
|
|
|
|
@router.get("/check/{limit_type}", response_model=LimitCheckResponse)
|
|
def check_limit(
|
|
limit_type: str,
|
|
current_user: User = Depends(get_current_vendor_api),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""
|
|
Check a specific limit before performing an action.
|
|
|
|
Use this before creating orders, products, or inviting team members.
|
|
|
|
Args:
|
|
limit_type: One of "orders", "products", "team_members"
|
|
|
|
Returns:
|
|
Whether the action can proceed and upgrade info if not
|
|
"""
|
|
vendor_id = current_user.token_vendor_id
|
|
|
|
# Check limit using service
|
|
check_data = usage_service.check_limit(db, vendor_id, limit_type)
|
|
|
|
return LimitCheckResponse(
|
|
limit_type=check_data.limit_type,
|
|
can_proceed=check_data.can_proceed,
|
|
current=check_data.current,
|
|
limit=check_data.limit,
|
|
percentage=check_data.percentage,
|
|
message=check_data.message,
|
|
upgrade_tier_code=check_data.upgrade_tier_code,
|
|
upgrade_tier_name=check_data.upgrade_tier_name,
|
|
)
|