use serde::{Deserialize, Serialize}; use utoipa::{IntoParams, ToSchema}; use uuid::Uuid; use erp_core::sanitize::{sanitize_option, sanitize_string, strip_html_tags}; // --------------------------------------------------------------------------- // 文章 DTOs // --------------------------------------------------------------------------- #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct ArticleResp { pub id: Uuid, pub title: String, pub summary: Option, pub content: Option, pub cover_image: Option, pub category: Option, pub author: Option, pub published_at: Option>, pub status: String, pub slug: Option, pub content_type: String, pub reviewed_by: Option, pub reviewed_at: Option>, pub review_note: Option, pub view_count: i32, pub sort_order: i32, /// 文章关联的分类 ID(来自 article_category 表) pub category_id: Option, /// 文章关联的标签名称列表 pub tags: Vec, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, pub version: i32, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct ArticleListItem { pub id: Uuid, pub title: String, pub summary: Option, pub cover_image: Option, pub category: Option, pub author: Option, pub published_at: Option>, pub status: String, pub view_count: i32, /// 分类 ID pub category_id: Option, /// 标签名称列表 pub tags: Vec, pub version: i32, } #[derive(Debug, Clone, Deserialize, IntoParams)] pub struct ArticleListParams { pub page: Option, pub page_size: Option, pub category: Option, /// 按状态筛选 pub status: Option, /// 按分类 ID 筛选 pub category_id: Option, /// 按标签 ID 筛选 pub tag_id: Option, /// 关键词搜索(标题模糊匹配) pub keyword: Option, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct CreateArticleReq { pub title: String, pub summary: Option, pub content: Option, pub cover_image: Option, pub category: Option, pub author: Option, pub published_at: Option>, pub slug: Option, pub content_type: Option, /// 分类 ID pub category_id: Option, /// 标签 ID 列表 #[serde(default)] pub tag_ids: Vec, } impl CreateArticleReq { pub fn sanitize(&mut self) { self.title = sanitize_string(&self.title); self.summary = sanitize_option(self.summary.take()); self.content = sanitize_option(self.content.take()); self.category = sanitize_option(self.category.take()); self.author = sanitize_option(self.author.take()); self.slug = sanitize_option(self.slug.take()); self.content_type = sanitize_option(self.content_type.take()); } } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct UpdateArticleReq { pub title: Option, pub summary: Option, pub content: Option, pub cover_image: Option, pub category: Option, pub author: Option, pub published_at: Option>, pub slug: Option, pub content_type: Option, /// 分类 ID pub category_id: Option, /// 标签 ID 列表(传入则整体替换) pub tag_ids: Option>, pub sort_order: Option, pub version: i32, } impl UpdateArticleReq { pub fn sanitize(&mut self) { if let Some(ref mut v) = self.title { *v = strip_html_tags(v); } self.summary = sanitize_option(self.summary.take()); self.content = sanitize_option(self.content.take()); self.category = sanitize_option(self.category.take()); self.author = sanitize_option(self.author.take()); self.slug = sanitize_option(self.slug.take()); self.content_type = sanitize_option(self.content_type.take()); } } /// 审核文章请求 #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct ReviewArticleReq { /// 审核备注 pub note: Option, /// 文章版本号(乐观锁) pub version: Option, } impl ReviewArticleReq { pub fn sanitize(&mut self) { self.note = sanitize_option(self.note.take()); } } // --------------------------------------------------------------------------- // 修订历史 DTOs // --------------------------------------------------------------------------- #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct ArticleRevisionResp { pub id: Uuid, pub article_id: Uuid, pub revision_number: i32, pub title: String, pub summary: Option, pub created_by: Option, pub created_at: chrono::DateTime, } // --------------------------------------------------------------------------- // 分类 DTOs // --------------------------------------------------------------------------- #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct CategoryResp { pub id: Uuid, pub name: String, pub slug: Option, pub parent_id: Option, pub description: Option, pub sort_order: i32, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, pub version: i32, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct CreateCategoryReq { pub name: String, pub slug: Option, pub parent_id: Option, pub description: Option, pub sort_order: Option, } impl CreateCategoryReq { pub fn sanitize(&mut self) { self.name = sanitize_string(&self.name); self.slug = sanitize_option(self.slug.take()); self.description = sanitize_option(self.description.take()); } } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct UpdateCategoryReq { pub name: Option, pub slug: Option, pub parent_id: Option, pub description: Option, pub sort_order: Option, pub version: i32, } impl UpdateCategoryReq { pub fn sanitize(&mut self) { if let Some(ref mut v) = self.name { *v = strip_html_tags(v); } self.slug = sanitize_option(self.slug.take()); self.description = sanitize_option(self.description.take()); } } // --------------------------------------------------------------------------- // 标签 DTOs // --------------------------------------------------------------------------- #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct TagResp { pub id: Uuid, pub name: String, pub created_at: chrono::DateTime, pub updated_at: chrono::DateTime, pub version: i32, } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct CreateTagReq { pub name: String, } impl CreateTagReq { pub fn sanitize(&mut self) { self.name = sanitize_string(&self.name); } } #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct UpdateTagReq { pub name: String, pub version: i32, } impl UpdateTagReq { pub fn sanitize(&mut self) { self.name = sanitize_string(&self.name); } }