Files
orion/app/api/v1/vendor/usage.py
Samir Boulahtit aa4b5a4c63 test: add service tests and fix architecture violations
- 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>
2025-12-31 18:48:59 +01:00

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,
)