refactor(api): introduce UserContext schema for API dependency injection

Replace direct User database model imports in API endpoints with UserContext
schema, following the architecture principle that API routes should not import
database models directly.

Changes:
- Create UserContext schema in models/schema/auth.py with from_user() factory
- Update app/api/deps.py to return UserContext from all auth dependencies
- Add _get_user_model() helper for functions needing User model access
- Update 58 API endpoint files to use UserContext instead of User
- Add noqa comments for 4 legitimate edge cases (enums, internal helpers)

Architecture validation: 0 errors (down from 61), 11 warnings remain

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-30 20:47:33 +01:00
parent 1ad30bd77e
commit cad862f469
60 changed files with 755 additions and 589 deletions

View File

@@ -21,7 +21,7 @@ from app.core.config import settings
from app.core.database import get_db
from app.services.billing_service import billing_service
from app.services.subscription_service import subscription_service
from models.database.user import User
from models.schema.auth import UserContext
router = APIRouter(prefix="/billing")
logger = logging.getLogger(__name__)
@@ -217,7 +217,7 @@ class AddOnCancelResponse(BaseModel):
@router.get("/subscription", response_model=SubscriptionStatusResponse)
def get_subscription_status(
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get current subscription status and usage metrics."""
@@ -260,7 +260,7 @@ def get_subscription_status(
@router.get("/tiers", response_model=TierListResponse)
def get_available_tiers(
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get available subscription tiers for upgrade/downgrade."""
@@ -278,7 +278,7 @@ def get_available_tiers(
@router.post("/checkout", response_model=CheckoutResponse)
def create_checkout_session(
request: CheckoutRequest,
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Create a Stripe checkout session for subscription."""
@@ -305,7 +305,7 @@ def create_checkout_session(
@router.post("/portal", response_model=PortalResponse)
def create_portal_session(
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Create a Stripe customer portal session."""
@@ -322,7 +322,7 @@ def create_portal_session(
def get_invoices(
skip: int = Query(0, ge=0),
limit: int = Query(20, ge=1, le=100),
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get invoice history."""
@@ -352,7 +352,7 @@ def get_invoices(
@router.get("/addons", response_model=list[AddOnResponse])
def get_available_addons(
category: str | None = Query(None, description="Filter by category"),
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get available add-on products."""
@@ -376,7 +376,7 @@ def get_available_addons(
@router.get("/my-addons", response_model=list[VendorAddOnResponse])
def get_vendor_addons(
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Get vendor's purchased add-ons."""
@@ -402,7 +402,7 @@ def get_vendor_addons(
@router.post("/cancel", response_model=CancelResponse)
def cancel_subscription(
request: CancelRequest,
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Cancel subscription."""
@@ -424,7 +424,7 @@ def cancel_subscription(
@router.post("/reactivate")
def reactivate_subscription(
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Reactivate a cancelled subscription."""
@@ -438,7 +438,7 @@ def reactivate_subscription(
@router.get("/upcoming-invoice", response_model=UpcomingInvoiceResponse)
def get_upcoming_invoice(
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Preview the upcoming invoice."""
@@ -457,7 +457,7 @@ def get_upcoming_invoice(
@router.post("/change-tier", response_model=ChangeTierResponse)
def change_tier(
request: ChangeTierRequest,
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Change subscription tier (upgrade/downgrade)."""
@@ -481,7 +481,7 @@ def change_tier(
@router.post("/addons/purchase")
def purchase_addon(
request: AddOnPurchaseRequest,
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Purchase an add-on product."""
@@ -510,7 +510,7 @@ def purchase_addon(
@router.delete("/addons/{addon_id}", response_model=AddOnCancelResponse)
def cancel_addon(
addon_id: int,
current_user: User = Depends(get_current_vendor_api),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""Cancel a purchased add-on."""