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

@@ -163,3 +163,109 @@ class VendorUserResponse(BaseModel):
is_active: bool
model_config = {"from_attributes": True}
class UserContext(BaseModel):
"""
User context for dependency injection in API endpoints.
This schema replaces direct use of the User database model in API routes,
following the principle that routes should not import database models directly.
Used by:
- get_current_admin_api / get_current_admin_from_cookie_or_header
- get_current_vendor_api / get_current_vendor_from_cookie_or_header
- get_current_super_admin
For admin users:
- is_super_admin indicates full platform access
- accessible_platform_ids is None for super admins (all platforms)
- accessible_platform_ids is a list for platform admins
For vendor users:
- token_vendor_id/code/role come from JWT token
- These indicate which vendor context the user is operating in
"""
# Core user fields
id: int
email: str
username: str
role: str # "admin" or "vendor"
is_active: bool = True
# Admin-specific fields
is_super_admin: bool = False
accessible_platform_ids: list[int] | None = None # None = all platforms (super admin)
# Vendor-specific fields (from JWT token)
token_vendor_id: int | None = None
token_vendor_code: str | None = None
token_vendor_role: str | None = None
# Optional profile fields
first_name: str | None = None
last_name: str | None = None
preferred_language: str | None = None
model_config = ConfigDict(from_attributes=True)
@property
def full_name(self) -> str:
"""Returns the full name of the user."""
if self.first_name and self.last_name:
return f"{self.first_name} {self.last_name}"
return self.username
@property
def is_admin(self) -> bool:
"""Check if user is a platform admin."""
return self.role == "admin"
@property
def is_vendor(self) -> bool:
"""Check if user is a vendor."""
return self.role == "vendor"
@classmethod
def from_user(cls, user, include_vendor_context: bool = True) -> "UserContext":
"""
Create UserContext from a User database model.
Args:
user: User database model instance
include_vendor_context: Whether to include token_vendor_* fields
Returns:
UserContext instance
"""
data = {
"id": user.id,
"email": user.email,
"username": user.username,
"role": user.role,
"is_active": user.is_active,
"is_super_admin": getattr(user, "is_super_admin", False),
"first_name": getattr(user, "first_name", None),
"last_name": getattr(user, "last_name", None),
"preferred_language": getattr(user, "preferred_language", None),
}
# Add admin platform access info
if user.role == "admin":
if getattr(user, "is_super_admin", False):
data["accessible_platform_ids"] = None # All platforms
else:
# Get platform IDs from admin_platforms relationship
admin_platforms = getattr(user, "admin_platforms", [])
data["accessible_platform_ids"] = [
ap.platform_id for ap in admin_platforms if ap.is_active
]
# Add vendor context from JWT token if present
if include_vendor_context:
data["token_vendor_id"] = getattr(user, "token_vendor_id", None)
data["token_vendor_code"] = getattr(user, "token_vendor_code", None)
data["token_vendor_role"] = getattr(user, "token_vendor_role", None)
return cls(**data)