from typing import List, Optional from pydantic import BaseModel, Field from app.models.domain.articles import Article from app.models.schemas.rwschema import RWSchema DEFAULT_ARTICLES_LIMIT = 20 DEFAULT_ARTICLES_OFFSET = 0 class ArticleForResponse(RWSchema, Article): """ 返回给前端的文章结构: - 继承 Article(包含 cover、tags、author 等) - tags 字段通过 alias 暴露为 tagList,兼容前端 """ tags: List[str] = Field(..., alias="tagList") class ArticleInResponse(RWSchema): article: ArticleForResponse class ArticleInCreate(RWSchema): """ 创建文章时请求体: { "article": { "title": "...", "description": "...", "body": "...", "tagList": ["..."], "cover": "可选封面URL" } } """ title: str description: str body: str tags: List[str] = Field([], alias="tagList") cover: Optional[str] = None class ArticleInUpdate(RWSchema): """ 更新文章时请求体(全部可选): - 不传的字段不改 - cover: - 不传:不改 - 传 null / "":清空封面(配合 repo 的 cover_provided 使用) - 传字符串:更新为新封面 """ title: Optional[str] = None description: Optional[str] = None body: Optional[str] = None cover: Optional[str] = None is_top: Optional[bool] = None is_featured: Optional[bool] = None sort_weight: Optional[int] = None class ListOfArticlesInResponse(RWSchema): articles: List[ArticleForResponse] articles_count: int class ArticlesFilters(BaseModel): tag: Optional[str] = None tags: Optional[List[str]] = None author: Optional[str] = None favorited: Optional[str] = None search: Optional[str] = None limit: int = Field(DEFAULT_ARTICLES_LIMIT, ge=1) offset: int = Field(DEFAULT_ARTICLES_OFFSET, ge=0)