from datetime import datetime from typing import Optional, List from pydantic import BaseModel, Field from bson import ObjectId class PyObjectId(ObjectId): """Custom ObjectId type for Pydantic""" @classmethod def __get_validators__(cls): yield cls.validate @classmethod def validate(cls, v): if not ObjectId.is_valid(v): raise ValueError("Invalid ObjectId") return ObjectId(v) @classmethod def __get_pydantic_json_schema__(cls, field_schema): field_schema.update(type="string") class Application(BaseModel): """OAuth2 Application data model""" id: Optional[PyObjectId] = Field(default=None, alias="_id") name: str = Field(..., min_length=1, max_length=100) client_id: str = Field(..., description="OAuth2 Client ID (unique)") client_secret: str = Field(..., description="Hashed client secret") redirect_uris: List[str] = Field(default_factory=list) grant_types: List[str] = Field( default_factory=lambda: ["authorization_code", "refresh_token"] ) scopes: List[str] = Field( default_factory=lambda: ["read", "write"] ) owner_id: str = Field(..., description="User ID who owns this application") created_at: datetime = Field(default_factory=datetime.utcnow) updated_at: datetime = Field(default_factory=datetime.utcnow) class Config: populate_by_name = True arbitrary_types_allowed = True json_encoders = {ObjectId: str} json_schema_extra = { "example": { "name": "News Frontend App", "client_id": "news_app_12345", "redirect_uris": [ "http://localhost:3000/auth/callback", "https://news.example.com/auth/callback" ], "grant_types": ["authorization_code", "refresh_token"], "scopes": ["read", "write"], "owner_id": "507f1f77bcf86cd799439011" } }