353 lines
9.6 KiB
Python
353 lines
9.6 KiB
Python
"""
|
|
Dashboard API Schemas
|
|
|
|
대시보드 API의 요청/응답 Pydantic 스키마를 정의합니다.
|
|
YouTube Analytics API 데이터를 프론트엔드에 전달하기 위한 모델입니다.
|
|
|
|
사용 예시:
|
|
from app.dashboard.schemas import DashboardResponse, ContentMetric
|
|
|
|
# 라우터에서 response_model로 사용
|
|
@router.get("/dashboard/stats", response_model=DashboardResponse)
|
|
async def get_dashboard_stats():
|
|
...
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from typing import Any, Literal, Optional
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
|
|
def to_camel(string: str) -> str:
|
|
"""snake_case를 camelCase로 변환
|
|
|
|
Args:
|
|
string: snake_case 문자열 (예: "label_en")
|
|
|
|
Returns:
|
|
camelCase 문자열 (예: "labelEn")
|
|
|
|
Example:
|
|
>>> to_camel("content_metrics")
|
|
"contentMetrics"
|
|
>>> to_camel("this_year")
|
|
"thisYear"
|
|
"""
|
|
components = string.split("_")
|
|
return components[0] + "".join(x.capitalize() for x in components[1:])
|
|
|
|
|
|
class ContentMetric(BaseModel):
|
|
"""KPI 지표 카드
|
|
|
|
대시보드 상단에 표시되는 핵심 성과 지표(KPI) 카드입니다.
|
|
|
|
Attributes:
|
|
id: 지표 고유 ID (예: "total-views", "total-engagement")
|
|
label: 한글 라벨 (예: "총 조회수")
|
|
label_en: 영문 라벨 (예: "Total Views")
|
|
value: 포맷팅된 값 (예: "1.2M", "8.2%")
|
|
trend: 증감 추세 값 (예: 12.5 → 12.5% 증가)
|
|
trend_direction: 추세 방향 ("up" 또는 "down")
|
|
|
|
Example:
|
|
>>> metric = ContentMetric(
|
|
... id="total-views",
|
|
... label="총 조회수",
|
|
... label_en="Total Views",
|
|
... value="1.2M",
|
|
... trend=12.5,
|
|
... trend_direction="up"
|
|
... )
|
|
"""
|
|
|
|
id: str
|
|
label: str
|
|
label_en: str
|
|
value: str
|
|
trend: float
|
|
trend_direction: Literal["up", "down", "-"] = Field(alias="trendDirection")
|
|
|
|
model_config = ConfigDict(
|
|
alias_generator=to_camel,
|
|
populate_by_name=True,
|
|
)
|
|
|
|
|
|
class MonthlyData(BaseModel):
|
|
"""월별 추이 데이터
|
|
|
|
전년 대비 월별 조회수 비교 데이터입니다.
|
|
|
|
Attributes:
|
|
month: 월 표시 (예: "1월", "2월")
|
|
this_year: 올해 해당 월 조회수
|
|
last_year: 작년 해당 월 조회수
|
|
|
|
Example:
|
|
>>> data = MonthlyData(
|
|
... month="1월",
|
|
... this_year=150000,
|
|
... last_year=120000
|
|
... )
|
|
"""
|
|
|
|
month: str
|
|
this_year: int = Field(alias="thisYear")
|
|
last_year: int = Field(alias="lastYear")
|
|
|
|
model_config = ConfigDict(
|
|
alias_generator=to_camel,
|
|
populate_by_name=True,
|
|
)
|
|
|
|
|
|
class DailyData(BaseModel):
|
|
"""일별 추이 데이터 (mode=day 전용)
|
|
|
|
최근 30일과 이전 30일의 일별 조회수 비교 데이터입니다.
|
|
|
|
Attributes:
|
|
date: 날짜 표시 (예: "1/18", "1/19")
|
|
this_period: 최근 30일 조회수
|
|
last_period: 이전 30일 동일 요일 조회수
|
|
"""
|
|
|
|
date: str
|
|
this_period: int = Field(alias="thisPeriod")
|
|
last_period: int = Field(alias="lastPeriod")
|
|
|
|
model_config = ConfigDict(
|
|
alias_generator=to_camel,
|
|
populate_by_name=True,
|
|
)
|
|
|
|
|
|
class TopContent(BaseModel):
|
|
"""인기 영상
|
|
|
|
조회수 기준 상위 인기 영상 정보입니다.
|
|
|
|
Attributes:
|
|
id: YouTube 영상 ID
|
|
title: 영상 제목
|
|
thumbnail: 썸네일 이미지 URL
|
|
platform: 플랫폼 ("youtube" 또는 "instagram")
|
|
views: 포맷팅된 조회수 (예: "125K")
|
|
engagement: 참여율 (예: "8.2%")
|
|
date: 업로드 날짜 (예: "2026.01.15")
|
|
|
|
Example:
|
|
>>> content = TopContent(
|
|
... id="video-id-1",
|
|
... title="힐링 영상",
|
|
... thumbnail="https://i.ytimg.com/...",
|
|
... platform="youtube",
|
|
... views="125K",
|
|
... engagement="8.2%",
|
|
... date="2026.01.15"
|
|
... )
|
|
"""
|
|
|
|
id: str
|
|
title: str
|
|
thumbnail: str
|
|
platform: Literal["youtube", "instagram"]
|
|
views: str
|
|
engagement: str
|
|
date: str
|
|
|
|
model_config = ConfigDict(
|
|
alias_generator=to_camel,
|
|
populate_by_name=True,
|
|
)
|
|
|
|
|
|
class AudienceData(BaseModel):
|
|
"""시청자 분석 데이터
|
|
|
|
시청자의 연령대, 성별, 지역 분포 데이터입니다.
|
|
|
|
Attributes:
|
|
age_groups: 연령대별 비율 리스트
|
|
[{"label": "18-24", "percentage": 35}, ...]
|
|
gender: 성별 조회수
|
|
{"male": 45000, "female": 55000}
|
|
top_regions: 상위 지역 리스트
|
|
[{"region": "서울", "percentage": 42}, ...]
|
|
|
|
Example:
|
|
>>> data = AudienceData(
|
|
... age_groups=[{"label": "18-24", "percentage": 35}],
|
|
... gender={"male": 45, "female": 55},
|
|
... top_regions=[{"region": "서울", "percentage": 42}]
|
|
... )
|
|
"""
|
|
|
|
age_groups: list[dict[str, Any]] = Field(alias="ageGroups")
|
|
gender: dict[str, int]
|
|
top_regions: list[dict[str, Any]] = Field(alias="topRegions")
|
|
|
|
model_config = ConfigDict(
|
|
alias_generator=to_camel,
|
|
populate_by_name=True,
|
|
)
|
|
|
|
|
|
class PlatformMetric(BaseModel):
|
|
"""플랫폼별 메트릭
|
|
|
|
플랫폼별 세부 지표입니다 (예: 구독자 증가, 구독 취소).
|
|
|
|
Attributes:
|
|
id: 메트릭 고유 ID (예: "subscribers-gained")
|
|
label: 지표명 (예: "구독자 증가")
|
|
value: 포맷팅된 값 (예: "1.2K")
|
|
unit: 단위 (선택, 예: "명", "%")
|
|
trend: 증감 추세 값
|
|
trend_direction: 추세 방향 ("up" 또는 "down")
|
|
|
|
Example:
|
|
>>> metric = PlatformMetric(
|
|
... id="subscribers-gained",
|
|
... label="구독자 증가",
|
|
... value="1.2K",
|
|
... trend=8.5,
|
|
... trend_direction="up"
|
|
... )
|
|
"""
|
|
|
|
id: str
|
|
label: str
|
|
value: str
|
|
unit: Optional[str] = None
|
|
trend: float
|
|
trend_direction: Literal["up", "down", "-"] = Field(alias="trendDirection")
|
|
|
|
model_config = ConfigDict(
|
|
alias_generator=to_camel,
|
|
populate_by_name=True,
|
|
)
|
|
|
|
|
|
class PlatformData(BaseModel):
|
|
"""플랫폼별 데이터
|
|
|
|
플랫폼별로 그룹화된 메트릭 데이터입니다.
|
|
|
|
Attributes:
|
|
platform: 플랫폼 종류 ("youtube" 또는 "instagram")
|
|
display_name: 표시할 플랫폼 이름 (예: "YouTube")
|
|
metrics: 플랫폼별 메트릭 리스트
|
|
|
|
Example:
|
|
>>> data = PlatformData(
|
|
... platform="youtube",
|
|
... display_name="YouTube",
|
|
... metrics=[
|
|
... PlatformMetric(
|
|
... id="subscribers",
|
|
... label="구독자 증가",
|
|
... value="1.2K",
|
|
... trend=8.5,
|
|
... trend_direction="up"
|
|
... )
|
|
... ]
|
|
... )
|
|
"""
|
|
|
|
platform: Literal["youtube", "instagram"]
|
|
display_name: str = Field(alias="displayName")
|
|
metrics: list[PlatformMetric]
|
|
|
|
model_config = ConfigDict(
|
|
alias_generator=to_camel,
|
|
populate_by_name=True,
|
|
)
|
|
|
|
|
|
class DashboardResponse(BaseModel):
|
|
"""대시보드 전체 응답
|
|
|
|
GET /api/dashboard/stats 엔드포인트의 전체 응답 스키마입니다.
|
|
모든 대시보드 데이터를 포함합니다.
|
|
|
|
Attributes:
|
|
content_metrics: KPI 지표 카드 리스트
|
|
monthly_data: 월별 추이 데이터 (전년 대비)
|
|
top_content: 인기 영상 리스트
|
|
audience_data: 시청자 분석 데이터
|
|
platform_data: 플랫폼별 메트릭 데이터
|
|
|
|
Example:
|
|
>>> response = DashboardResponse(
|
|
... content_metrics=[...],
|
|
... monthly_data=[...],
|
|
... top_content=[...],
|
|
... audience_data=AudienceData(...),
|
|
... platform_data=[...]
|
|
... )
|
|
>>> json_str = response.model_dump_json() # JSON 직렬화
|
|
"""
|
|
|
|
content_metrics: list[ContentMetric] = Field(alias="contentMetrics")
|
|
monthly_data: list[MonthlyData] = Field(default=[], alias="monthlyData")
|
|
daily_data: list[DailyData] = Field(default=[], alias="dailyData")
|
|
top_content: list[TopContent] = Field(alias="topContent")
|
|
audience_data: AudienceData = Field(alias="audienceData")
|
|
platform_data: list[PlatformData] = Field(default=[], alias="platformData")
|
|
|
|
model_config = ConfigDict(
|
|
alias_generator=to_camel,
|
|
populate_by_name=True,
|
|
)
|
|
|
|
|
|
class ConnectedAccount(BaseModel):
|
|
"""연결된 소셜 계정 정보
|
|
|
|
Attributes:
|
|
id: SocialAccount 테이블 ID (계정 선택 시 이 값을 social_account_id로 전달)
|
|
platform: 플랫폼 (예: "youtube")
|
|
platform_username: 플랫폼 사용자명 (예: "@channelname")
|
|
platform_user_id: 플랫폼 사용자 고유 ID
|
|
channel_title: YouTube 채널 제목 (platform_data에서 추출)
|
|
connected_at: 연동 일시
|
|
is_active: 활성화 상태
|
|
"""
|
|
|
|
id: int
|
|
platform: str
|
|
platform_user_id: str
|
|
platform_username: Optional[str] = None
|
|
channel_title: Optional[str] = None
|
|
connected_at: datetime
|
|
is_active: bool
|
|
|
|
model_config = ConfigDict(
|
|
alias_generator=to_camel,
|
|
populate_by_name=True,
|
|
)
|
|
|
|
|
|
class ConnectedAccountsResponse(BaseModel):
|
|
"""연결된 소셜 계정 목록 응답
|
|
|
|
Attributes:
|
|
accounts: 연결된 계정 목록
|
|
"""
|
|
|
|
accounts: list[ConnectedAccount]
|
|
|
|
|
|
class CacheDeleteResponse(BaseModel):
|
|
"""캐시 삭제 응답
|
|
|
|
Attributes:
|
|
deleted_count: 삭제된 캐시 키 개수
|
|
message: 처리 결과 메시지
|
|
"""
|
|
|
|
deleted_count: int
|
|
message: str
|