The frontend routing guard checks has_starter_deck to decide whether to redirect users to starter selection. The field was missing from the API response, causing authenticated users with a starter deck to be incorrectly redirected to /starter on page refresh. - Add has_starter_deck computed property to User model - Add has_starter_deck field to UserResponse schema - Add unit tests for User model properties - Add API tests for has_starter_deck in profile response Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
118 lines
4.1 KiB
Python
118 lines
4.1 KiB
Python
"""User schemas for Mantimon TCG.
|
|
|
|
This module defines Pydantic models for user-related API requests
|
|
and responses.
|
|
|
|
Example:
|
|
user_response = UserResponse(
|
|
id="550e8400-e29b-41d4-a716-446655440000",
|
|
email="player@example.com",
|
|
display_name="Player1",
|
|
is_premium=False,
|
|
created_at=datetime.now(UTC)
|
|
)
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, EmailStr, Field
|
|
|
|
|
|
class UserResponse(BaseModel):
|
|
"""Public user information returned by API endpoints.
|
|
|
|
Attributes:
|
|
id: User's unique identifier.
|
|
email: User's email address.
|
|
display_name: User's public display name.
|
|
avatar_url: URL to user's avatar image.
|
|
is_premium: Whether user has active premium subscription.
|
|
premium_until: When premium subscription expires (if premium).
|
|
created_at: When the account was created.
|
|
"""
|
|
|
|
id: UUID = Field(..., description="User ID")
|
|
email: EmailStr = Field(..., description="User's email address")
|
|
display_name: str = Field(..., description="Public display name")
|
|
avatar_url: str | None = Field(default=None, description="Avatar image URL")
|
|
is_premium: bool = Field(default=False, description="Premium subscription status")
|
|
premium_until: datetime | None = Field(default=None, description="Premium expiration date")
|
|
has_starter_deck: bool = Field(
|
|
default=False, description="Whether user has selected a starter deck"
|
|
)
|
|
created_at: datetime = Field(..., description="Account creation date")
|
|
|
|
model_config = {"from_attributes": True}
|
|
|
|
|
|
class UserCreate(BaseModel):
|
|
"""Internal schema for creating a user from OAuth data.
|
|
|
|
Not exposed via API - used internally by auth service.
|
|
|
|
Attributes:
|
|
email: User's email from OAuth provider.
|
|
display_name: User's name from OAuth provider.
|
|
avatar_url: Avatar URL from OAuth provider.
|
|
oauth_provider: OAuth provider name (google, discord).
|
|
oauth_id: Unique ID from the OAuth provider.
|
|
"""
|
|
|
|
email: EmailStr = Field(..., description="Email from OAuth provider")
|
|
display_name: str = Field(..., max_length=50, description="Display name")
|
|
avatar_url: str | None = Field(default=None, description="Avatar URL")
|
|
oauth_provider: str = Field(..., description="OAuth provider (google, discord)")
|
|
oauth_id: str = Field(..., description="Unique ID from OAuth provider")
|
|
|
|
|
|
class UserUpdate(BaseModel):
|
|
"""Schema for updating user profile.
|
|
|
|
All fields are optional - only provided fields are updated.
|
|
|
|
Attributes:
|
|
display_name: New display name.
|
|
avatar_url: New avatar URL.
|
|
"""
|
|
|
|
display_name: str | None = Field(
|
|
default=None, min_length=1, max_length=50, description="New display name"
|
|
)
|
|
avatar_url: str | None = Field(default=None, description="New avatar URL")
|
|
|
|
|
|
class OAuthUserInfo(BaseModel):
|
|
"""Normalized user information from OAuth providers.
|
|
|
|
This provides a consistent structure regardless of whether
|
|
the user authenticated via Google or Discord.
|
|
|
|
Attributes:
|
|
provider: OAuth provider name.
|
|
oauth_id: Unique ID from the provider.
|
|
email: User's email address.
|
|
name: User's display name from provider.
|
|
avatar_url: Avatar URL from provider.
|
|
"""
|
|
|
|
provider: str = Field(..., description="OAuth provider (google, discord)")
|
|
oauth_id: str = Field(..., description="Unique ID from provider")
|
|
email: EmailStr = Field(..., description="User's email")
|
|
name: str = Field(..., description="User's name from provider")
|
|
avatar_url: str | None = Field(default=None, description="Avatar URL from provider")
|
|
|
|
def to_user_create(self) -> UserCreate:
|
|
"""Convert OAuth info to UserCreate schema.
|
|
|
|
Returns:
|
|
UserCreate instance ready for user creation.
|
|
"""
|
|
return UserCreate(
|
|
email=self.email,
|
|
display_name=self.name[:50], # Enforce max length
|
|
avatar_url=self.avatar_url,
|
|
oauth_provider=self.provider,
|
|
oauth_id=self.oauth_id,
|
|
)
|