Replace direct imports from optional modules (catalog, orders, analytics) with provider pattern calls (stats_aggregator, feature_aggregator) and move usage_service from analytics to billing where it belongs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
184 lines
5.2 KiB
Python
184 lines
5.2 KiB
Python
# app/modules/billing/routes/api/store_usage.py
|
|
"""
|
|
Store usage and limits API endpoints.
|
|
|
|
Provides endpoints for:
|
|
- Current usage vs limits
|
|
- Upgrade recommendations
|
|
- Approaching limit warnings
|
|
|
|
Migrated from app/api/v1/store/usage.py to billing module.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.api.deps import get_current_store_api, require_module_access
|
|
from app.core.database import get_db
|
|
from app.modules.billing.services.usage_service import usage_service
|
|
from app.modules.enums import FrontendType
|
|
from models.schema.auth import UserContext
|
|
|
|
store_usage_router = APIRouter(
|
|
prefix="/usage",
|
|
dependencies=[Depends(require_module_access("billing", FrontendType.STORE))],
|
|
)
|
|
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
|
|
# ============================================================================
|
|
|
|
|
|
@store_usage_router.get("", response_model=UsageResponse)
|
|
def get_usage(
|
|
current_user: UserContext = Depends(get_current_store_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.
|
|
"""
|
|
store_id = current_user.token_store_id
|
|
|
|
# Get usage data from service
|
|
usage_data = usage_service.get_store_usage(db, store_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,
|
|
)
|
|
|
|
|
|
@store_usage_router.get("/check/{limit_type}", response_model=LimitCheckResponse)
|
|
def check_limit(
|
|
limit_type: str,
|
|
current_user: UserContext = Depends(get_current_store_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
|
|
"""
|
|
store_id = current_user.token_store_id
|
|
|
|
# Check limit using service
|
|
check_data = usage_service.check_limit(db, store_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,
|
|
)
|