# app/api/v1/vendor/features.py """ Vendor features API endpoints. Provides feature availability information for the frontend to: - Show/hide UI elements based on tier - Display upgrade prompts for unavailable features - Load feature metadata for dynamic rendering Endpoints: - GET /features/available - List of feature codes (for quick checks) - GET /features - Full feature list with availability and metadata - GET /features/{code} - Single feature details with upgrade info - GET /features/categories - List feature categories """ import logging from fastapi import APIRouter, Depends, Query 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.exceptions import FeatureNotFoundError from app.services.feature_service import feature_service from models.database.user import User router = APIRouter(prefix="/features") logger = logging.getLogger(__name__) # ============================================================================ # Response Schemas # ============================================================================ class FeatureCodeListResponse(BaseModel): """Simple list of available feature codes for quick checks.""" features: list[str] tier_code: str tier_name: str class FeatureResponse(BaseModel): """Full feature information.""" code: str name: str description: str | None = None category: str ui_location: str | None = None ui_icon: str | None = None ui_route: str | None = None ui_badge_text: str | None = None is_available: bool minimum_tier_code: str | None = None minimum_tier_name: str | None = None class FeatureListResponse(BaseModel): """List of features with metadata.""" features: list[FeatureResponse] available_count: int total_count: int tier_code: str tier_name: str class FeatureDetailResponse(BaseModel): """Single feature detail with upgrade info.""" code: str name: str description: str | None = None category: str ui_location: str | None = None ui_icon: str | None = None ui_route: str | None = None is_available: bool # Upgrade info (only if not available) upgrade_tier_code: str | None = None upgrade_tier_name: str | None = None upgrade_tier_price_monthly_cents: int | None = None class CategoryListResponse(BaseModel): """List of feature categories.""" categories: list[str] class FeatureGroupedResponse(BaseModel): """Features grouped by category.""" categories: dict[str, list[FeatureResponse]] available_count: int total_count: int class FeatureCheckResponse(BaseModel): """Quick feature availability check response.""" has_feature: bool feature_code: str # ============================================================================ # Endpoints # ============================================================================ @router.get("/available", response_model=FeatureCodeListResponse) def get_available_features( current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Get list of feature codes available to vendor. This is a lightweight endpoint for quick feature checks. Use this to populate a frontend feature store on app init. Returns: List of feature codes the vendor has access to """ vendor_id = current_user.token_vendor_id # Get subscription for tier info from app.services.subscription_service import subscription_service subscription = subscription_service.get_or_create_subscription(db, vendor_id) tier = subscription.tier_obj # Get available features feature_codes = feature_service.get_available_feature_codes(db, vendor_id) return FeatureCodeListResponse( features=feature_codes, tier_code=subscription.tier, tier_name=tier.name if tier else subscription.tier.title(), ) @router.get("", response_model=FeatureListResponse) def get_features( category: str | None = Query(None, description="Filter by category"), include_unavailable: bool = Query(True, description="Include features not available to vendor"), current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Get all features with availability status and metadata. This is a comprehensive endpoint for building feature-gated UIs. Each feature includes: - Availability status - UI metadata (icon, route, location) - Minimum tier required Args: category: Filter to specific category (orders, inventory, etc.) include_unavailable: Whether to include locked features Returns: List of features with metadata and availability """ vendor_id = current_user.token_vendor_id # Get subscription for tier info from app.services.subscription_service import subscription_service subscription = subscription_service.get_or_create_subscription(db, vendor_id) tier = subscription.tier_obj # Get features features = feature_service.get_vendor_features( db, vendor_id, category=category, include_unavailable=include_unavailable, ) available_count = sum(1 for f in features if f.is_available) return FeatureListResponse( features=[ FeatureResponse( code=f.code, name=f.name, description=f.description, category=f.category, ui_location=f.ui_location, ui_icon=f.ui_icon, ui_route=f.ui_route, ui_badge_text=f.ui_badge_text, is_available=f.is_available, minimum_tier_code=f.minimum_tier_code, minimum_tier_name=f.minimum_tier_name, ) for f in features ], available_count=available_count, total_count=len(features), tier_code=subscription.tier, tier_name=tier.name if tier else subscription.tier.title(), ) @router.get("/categories", response_model=CategoryListResponse) def get_feature_categories( current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Get list of feature categories. Returns: List of category names """ categories = feature_service.get_categories(db) return CategoryListResponse(categories=categories) @router.get("/grouped", response_model=FeatureGroupedResponse) def get_features_grouped( current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Get features grouped by category. Useful for rendering feature comparison tables or settings pages. """ vendor_id = current_user.token_vendor_id grouped = feature_service.get_features_grouped_by_category(db, vendor_id) # Convert to response format categories_response = {} total = 0 available = 0 for category, features in grouped.items(): categories_response[category] = [ FeatureResponse( code=f.code, name=f.name, description=f.description, category=f.category, ui_location=f.ui_location, ui_icon=f.ui_icon, ui_route=f.ui_route, ui_badge_text=f.ui_badge_text, is_available=f.is_available, minimum_tier_code=f.minimum_tier_code, minimum_tier_name=f.minimum_tier_name, ) for f in features ] total += len(features) available += sum(1 for f in features if f.is_available) return FeatureGroupedResponse( categories=categories_response, available_count=available, total_count=total, ) @router.get("/{feature_code}", response_model=FeatureDetailResponse) def get_feature_detail( feature_code: str, current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Get detailed information about a specific feature. Includes upgrade information if the feature is not available. Use this for upgrade prompts and feature explanation modals. Args: feature_code: The feature code Returns: Feature details with upgrade info if locked """ vendor_id = current_user.token_vendor_id # Get feature feature = feature_service.get_feature_by_code(db, feature_code) if not feature: raise FeatureNotFoundError(feature_code) # Check availability is_available = feature_service.has_feature(db, vendor_id, feature_code) # Get upgrade info if not available upgrade_tier_code = None upgrade_tier_name = None upgrade_tier_price = None if not is_available: upgrade_info = feature_service.get_feature_upgrade_info(db, feature_code) if upgrade_info: upgrade_tier_code = upgrade_info.required_tier_code upgrade_tier_name = upgrade_info.required_tier_name upgrade_tier_price = upgrade_info.required_tier_price_monthly_cents return FeatureDetailResponse( code=feature.code, name=feature.name, description=feature.description, category=feature.category, ui_location=feature.ui_location, ui_icon=feature.ui_icon, ui_route=feature.ui_route, is_available=is_available, upgrade_tier_code=upgrade_tier_code, upgrade_tier_name=upgrade_tier_name, upgrade_tier_price_monthly_cents=upgrade_tier_price, ) @router.get("/check/{feature_code}", response_model=FeatureCheckResponse) def check_feature( feature_code: str, current_user: User = Depends(get_current_vendor_api), db: Session = Depends(get_db), ): """ Quick check if vendor has access to a feature. Returns simple boolean response for inline checks. Args: feature_code: The feature code Returns: has_feature and feature_code """ vendor_id = current_user.token_vendor_id has_feature = feature_service.has_feature(db, vendor_id, feature_code) return FeatureCheckResponse(has_feature=has_feature, feature_code=feature_code)