mantimon-tcg/backend/app/schemas/user.py
Cal Corum ca3aca2b38 Add has_starter_deck to user profile API response
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>
2026-01-30 23:14:04 -06:00

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,
)