from datetime import date, datetime from pydantic import BaseModel, ConfigDict, field_validator from app.models.journal import JournalAttachmentType, JournalMood class JournalCategoryBase(BaseModel): name: str color: str | None = None content_template: str | None = None @field_validator("name") @classmethod def name_not_blank(cls, v: str) -> str: v = v.strip() if not v: raise ValueError("카테고리 이름을 입력해주세요") return v @field_validator("color") @classmethod def blank_color_to_none(cls, v: str | None) -> str | None: if v is None: return None v = v.strip() return v or None @field_validator("content_template") @classmethod def blank_template_to_none(cls, v: str | None) -> str | None: # color/name과 달리 여기선 .strip()으로 값 자체를 바꾸지 않는다 — 템플릿 맨 끝의 # "- "(대시+공백)처럼 의미 있는 trailing whitespace가 있을 수 있고, 그걸 지우면 # markdown이 그 줄을 목록으로 인식하지 못하게 된다(빈 값인지 판단만 strip으로 하고, # 실제로 저장하는 값은 원본을 그대로 쓴다). if v is None or not v.strip(): return None return v class JournalCategoryCreate(JournalCategoryBase): pass class JournalCategoryOut(BaseModel): model_config = ConfigDict(from_attributes=True) id: int name: str color: str | None content_template: str | None sort_order: int | None created_at: datetime class JournalCategoryReorderRequest(BaseModel): category_ids: list[int] class JournalEntryBase(BaseModel): category_id: int entry_date: date title: str | None = None content: str moods: list[JournalMood] = [] tags: list[str] = [] @field_validator("content") @classmethod def content_not_blank(cls, v: str) -> str: v = v.strip() if not v: raise ValueError("내용을 입력해주세요") return v @field_validator("title") @classmethod def blank_title_to_none(cls, v: str | None) -> str | None: if v is None: return None v = v.strip() return v or None @field_validator("moods") @classmethod def dedupe_moods(cls, v: list[JournalMood]) -> list[JournalMood]: cleaned: list[JournalMood] = [] for mood in v: if mood not in cleaned: cleaned.append(mood) return cleaned @field_validator("tags") @classmethod def clean_tags(cls, v: list[str]) -> list[str]: cleaned: list[str] = [] for raw in v: name = raw.strip() if name and name not in cleaned: cleaned.append(name) return cleaned class JournalEntryCreate(JournalEntryBase): pass class JournalEntryUpdate(JournalEntryBase): pass class JournalAttachmentOut(BaseModel): id: int media_type: JournalAttachmentType original_filename: str has_thumbnail: bool class JournalCalendarDay(BaseModel): entry_date: date total_count: int category_colors: list[str] # 그 날 엔트리가 있는 카테고리들의 색상(점 표시용, 중복 제거) class JournalDayDetailItem(BaseModel): id: int category_id: int category_name: str category_color: str | None title: str | None content: str moods: list[JournalMood] tags: list[str] attachments: list[JournalAttachmentOut] class JournalOnThisDayItem(JournalDayDetailItem): entry_date: date years_ago: int