Initial commit: WealthWise financial analytics platform

This commit is contained in:
2026-02-14 21:16:57 +05:30
commit b8588df583
171 changed files with 29048 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
# Schemas package for Pydantic models
from app.schemas.token import TokenPayload, UserContext
from app.schemas.user import (
Token,
TokenPayload as UserTokenPayload,
UserBase,
UserCreate,
UserInDB,
UserPublic,
UserUpdate,
)
__all__ = [
# Token schemas (legacy Supabase support)
"TokenPayload",
"UserContext",
# User schemas (new self-hosted auth)
"Token",
"UserTokenPayload",
"UserBase",
"UserCreate",
"UserPublic",
"UserInDB",
"UserUpdate",
]

View File

@@ -0,0 +1,127 @@
"""Pydantic models for JWT token handling and user context.
This module defines the data structures for:
- TokenPayload: Full JWT payload from Supabase
- UserContext: Clean user representation for API context
"""
from typing import Any, Optional
from pydantic import BaseModel, Field
class TokenPayload(BaseModel):
"""Supabase JWT token payload structure.
This model represents the complete JWT payload that Supabase Auth
includes in the access token. It contains all claims including
standard JWT claims and Supabase-specific claims.
Attributes:
sub: Subject (user UUID)
exp: Expiration timestamp (Unix)
iat: Issued at timestamp (Unix)
aud: Audience (should be "authenticated")
email: User's email address
phone: User's phone number (if available)
app_metadata: Application-specific metadata (includes role)
user_metadata: User-specific metadata
role: User's database role (e.g., "authenticated", "anon")
aal: Authenticator assurance level (aal1, aal2)
amr: Authentication methods reference
session_id: Session UUID
is_anonymous: Whether this is an anonymous user
"""
# Standard JWT claims
sub: str = Field(description="Subject (user UUID)")
exp: Optional[int] = Field(default=None, description="Expiration timestamp (Unix)")
iat: Optional[int] = Field(default=None, description="Issued at timestamp (Unix)")
iss: Optional[str] = Field(default=None, description="Issuer")
aud: str = Field(description="Audience (should be 'authenticated')")
# User identity claims
email: Optional[str] = Field(default=None, description="User's email address")
phone: Optional[str] = Field(default=None, description="User's phone number")
email_confirmed_at: Optional[str] = Field(default=None, description="Email confirmation timestamp")
phone_confirmed_at: Optional[str] = Field(default=None, description="Phone confirmation timestamp")
# Supabase metadata
app_metadata: dict[str, Any] = Field(
default_factory=dict,
description="Application-specific metadata (provider, role, etc.)"
)
user_metadata: dict[str, Any] = Field(
default_factory=dict,
description="User-specific metadata"
)
# Supabase-specific claims
role: Optional[str] = Field(default=None, description="Database role")
aal: Optional[str] = Field(default=None, description="Authenticator assurance level")
amr: Optional[list[dict[str, Any]]] = Field(default=None, description="Authentication methods reference")
session_id: Optional[str] = Field(default=None, description="Session UUID")
is_anonymous: Optional[bool] = Field(default=False, description="Whether user is anonymous")
class Config:
"""Pydantic configuration."""
extra = "allow" # Allow additional claims not explicitly defined
class UserContext(BaseModel):
"""Clean user context for API endpoints.
This model represents the authenticated user information that
is passed to API endpoints via the get_current_user dependency.
It contains only the essential information needed by most endpoints.
Attributes:
id: User UUID from the 'sub' claim
email: User's email address
role: User's role (extracted from app_metadata or role claim)
is_anonymous: Whether this is an anonymous user
session_id: Current session ID
"""
id: str = Field(description="User UUID")
email: Optional[str] = Field(default=None, description="User's email address")
role: str = Field(default="authenticated", description="User's role")
is_anonymous: bool = Field(default=False, description="Whether user is anonymous")
session_id: Optional[str] = Field(default=None, description="Current session ID")
@classmethod
def from_token_payload(cls, payload: TokenPayload) -> "UserContext":
"""Create UserContext from TokenPayload.
Extracts user information from the full JWT payload and creates
a clean UserContext object. Role is extracted from app_metadata
if available, otherwise defaults to the role claim or 'authenticated'.
Args:
payload: Validated TokenPayload from JWT
Returns:
UserContext with extracted user information
"""
# Extract role from app_metadata or fall back to role claim
role = payload.app_metadata.get("role", payload.role or "authenticated")
return cls(
id=payload.sub,
email=payload.email,
role=role,
is_anonymous=payload.is_anonymous or False,
session_id=payload.session_id,
)
class Config:
"""Pydantic configuration."""
json_schema_extra = {
"example": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "user@wealthwise.app",
"role": "authenticated",
"is_anonymous": False,
"session_id": "session-uuid-here",
}
}

View File

@@ -0,0 +1,92 @@
"""User schemas for request and response validation.
This module defines Pydantic schemas for user-related operations including
registration, authentication, and user information retrieval.
"""
from datetime import datetime
from typing import Optional
from uuid import UUID
from pydantic import BaseModel, EmailStr, Field
class UserBase(BaseModel):
"""Base user schema with common attributes."""
email: EmailStr = Field(description="User's email address")
is_active: bool = Field(default=True, description="Whether account is active")
is_superuser: bool = Field(default=False, description="Whether user is admin")
class UserCreate(BaseModel):
"""Schema for user registration.
Attributes:
email: User's email address (must be unique)
password: Plain text password (will be hashed)
"""
email: EmailStr = Field(description="User's email address")
password: str = Field(
min_length=8,
description="User's password (minimum 8 characters)",
)
class UserUpdate(BaseModel):
"""Schema for user updates.
All fields are optional to allow partial updates.
"""
email: Optional[EmailStr] = Field(default=None, description="New email address")
password: Optional[str] = Field(
default=None,
min_length=8,
description="New password (minimum 8 characters)",
)
is_active: Optional[bool] = Field(default=None, description="Account status")
class UserInDB(UserBase):
"""Schema representing user as stored in database.
Includes the hashed password - should never be returned in API responses.
"""
id: UUID
hashed_password: str = Field(description="Bcrypt hashed password")
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class UserPublic(UserBase):
"""Schema for user information returned in API responses.
Excludes sensitive fields like hashed_password.
"""
id: UUID
created_at: datetime
class Config:
from_attributes = True
class Token(BaseModel):
"""Schema for authentication token response."""
access_token: str = Field(description="JWT access token")
token_type: str = Field(default="bearer", description="Token type")
class TokenPayload(BaseModel):
"""Schema for JWT token payload."""
sub: Optional[str] = Field(default=None, description="Subject (user ID)")
exp: Optional[datetime] = Field(default=None, description="Expiration time")
type: Optional[str] = Field(default=None, description="Token type")