from pydantic import BaseModel, field_validator from typing import Optional from datetime import datetime import re class ContentVideoBase(BaseModel): youtube_id: str title_ko: str title_en: Optional[str] = None title_ja: Optional[str] = None title_zh: Optional[str] = None description_ko: Optional[str] = None description_en: Optional[str] = None description_ja: Optional[str] = None description_zh: Optional[str] = None entity_type: str # "project", "solution", "product" entity_id: int display_order: int = 0 is_active: bool = True @field_validator('youtube_id', mode='before') @classmethod def extract_youtube_id(cls, v): """Extract YouTube ID from various URL formats or plain ID""" if not v: return v # Already a plain ID (11 characters) if re.match(r'^[a-zA-Z0-9_-]{11}$', v): return v # Extract from various YouTube URL formats patterns = [ r'(?:youtube\.com/watch\?v=|youtu\.be/|youtube\.com/embed/)([a-zA-Z0-9_-]{11})', r'youtube\.com/watch\?.*v=([a-zA-Z0-9_-]{11})', ] for pattern in patterns: match = re.search(pattern, v) if match: return match.group(1) # Return as-is if no pattern matches return v @field_validator('entity_type') @classmethod def validate_entity_type(cls, v): allowed = ['project', 'solution', 'product'] if v not in allowed: raise ValueError(f'entity_type must be one of: {allowed}') return v class ContentVideoCreate(ContentVideoBase): pass class ContentVideoUpdate(BaseModel): youtube_id: Optional[str] = None title_ko: Optional[str] = None title_en: Optional[str] = None title_ja: Optional[str] = None title_zh: Optional[str] = None description_ko: Optional[str] = None description_en: Optional[str] = None description_ja: Optional[str] = None description_zh: Optional[str] = None display_order: Optional[int] = None is_active: Optional[bool] = None @field_validator('youtube_id', mode='before') @classmethod def extract_youtube_id(cls, v): if not v: return v if re.match(r'^[a-zA-Z0-9_-]{11}$', v): return v patterns = [ r'(?:youtube\.com/watch\?v=|youtu\.be/|youtube\.com/embed/)([a-zA-Z0-9_-]{11})', r'youtube\.com/watch\?.*v=([a-zA-Z0-9_-]{11})', ] for pattern in patterns: match = re.search(pattern, v) if match: return match.group(1) return v class ContentVideoResponse(BaseModel): id: int youtube_id: str title_ko: str title_en: Optional[str] = None title_ja: Optional[str] = None title_zh: Optional[str] = None description_ko: Optional[str] = None description_en: Optional[str] = None description_ja: Optional[str] = None description_zh: Optional[str] = None entity_type: str entity_id: int display_order: int is_active: bool created_at: Optional[datetime] = None updated_at: Optional[datetime] = None # Computed fields youtube_url: str = "" youtube_embed_url: str = "" thumbnail_url: str = "" class Config: from_attributes = True def __init__(self, **data): super().__init__(**data) youtube_id = data.get('youtube_id', '') self.youtube_url = f"https://www.youtube.com/watch?v={youtube_id}" self.youtube_embed_url = f"https://www.youtube.com/embed/{youtube_id}" self.thumbnail_url = f"https://img.youtube.com/vi/{youtube_id}/maxresdefault.jpg" class ContentVideoPublic(BaseModel): """Public response with language-specific fields""" id: int youtube_id: str title: str description: Optional[str] = None youtube_url: str youtube_embed_url: str thumbnail_url: str display_order: int class Config: from_attributes = True