138 lines
3.9 KiB
Python
138 lines
3.9 KiB
Python
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
|