Files
hms/crates/erp-health/src/dto/article_dto.rs
iven fca0b5a78f feat(health): 新增公开文章列表端点 /public/articles 供小程序访客首页使用
访客首页文章列表调用 /health/articles 需要 JWT 认证导致 401。
新增 GET /public/articles?tenant_id=xxx 端点,强制只返回已发布文章,
无需认证。小程序访客首页改用此公开端点。
2026-05-10 19:14:31 +08:00

255 lines
7.6 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<String>,
pub content: Option<String>,
pub cover_image: Option<String>,
pub category: Option<String>,
pub author: Option<String>,
pub published_at: Option<chrono::DateTime<chrono::Utc>>,
pub status: String,
pub slug: Option<String>,
pub content_type: String,
pub reviewed_by: Option<Uuid>,
pub reviewed_at: Option<chrono::DateTime<chrono::Utc>>,
pub review_note: Option<String>,
pub view_count: i32,
pub sort_order: i32,
/// 文章关联的分类 ID来自 article_category 表)
pub category_id: Option<Uuid>,
/// 文章关联的标签名称列表
pub tags: Vec<String>,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
pub version: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ArticleListItem {
pub id: Uuid,
pub title: String,
pub summary: Option<String>,
pub cover_image: Option<String>,
pub category: Option<String>,
pub author: Option<String>,
pub published_at: Option<chrono::DateTime<chrono::Utc>>,
pub status: String,
pub view_count: i32,
/// 分类 ID
pub category_id: Option<Uuid>,
/// 标签名称列表
pub tags: Vec<String>,
pub version: i32,
}
#[derive(Debug, Clone, Deserialize, IntoParams)]
pub struct ArticleListParams {
/// 公开端点必需:租户 ID
pub tenant_id: Option<Uuid>,
pub page: Option<u64>,
pub page_size: Option<u64>,
pub category: Option<String>,
/// 按状态筛选
pub status: Option<String>,
/// 按分类 ID 筛选
pub category_id: Option<Uuid>,
/// 按标签 ID 筛选
pub tag_id: Option<Uuid>,
/// 关键词搜索(标题模糊匹配)
pub keyword: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct CreateArticleReq {
pub title: String,
pub summary: Option<String>,
pub content: Option<String>,
pub cover_image: Option<String>,
pub category: Option<String>,
pub author: Option<String>,
pub published_at: Option<chrono::DateTime<chrono::Utc>>,
pub slug: Option<String>,
pub content_type: Option<String>,
/// 分类 ID
pub category_id: Option<Uuid>,
/// 标签 ID 列表
#[serde(default)]
pub tag_ids: Vec<Uuid>,
}
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<String>,
pub summary: Option<String>,
pub content: Option<String>,
pub cover_image: Option<String>,
pub category: Option<String>,
pub author: Option<String>,
pub published_at: Option<chrono::DateTime<chrono::Utc>>,
pub slug: Option<String>,
pub content_type: Option<String>,
/// 分类 ID
pub category_id: Option<Uuid>,
/// 标签 ID 列表(传入则整体替换)
pub tag_ids: Option<Vec<Uuid>>,
pub sort_order: Option<i32>,
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<String>,
/// 文章版本号(乐观锁)
pub version: Option<i32>,
}
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<String>,
pub created_by: Option<Uuid>,
pub created_at: chrono::DateTime<chrono::Utc>,
}
// ---------------------------------------------------------------------------
// 分类 DTOs
// ---------------------------------------------------------------------------
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct CategoryResp {
pub id: Uuid,
pub name: String,
pub slug: Option<String>,
pub parent_id: Option<Uuid>,
pub description: Option<String>,
pub sort_order: i32,
pub created_at: chrono::DateTime<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
pub version: i32,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct CreateCategoryReq {
pub name: String,
pub slug: Option<String>,
pub parent_id: Option<Uuid>,
pub description: Option<String>,
pub sort_order: Option<i32>,
}
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<String>,
pub slug: Option<String>,
pub parent_id: Option<Uuid>,
pub description: Option<String>,
pub sort_order: Option<i32>,
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<chrono::Utc>,
pub updated_at: chrono::DateTime<chrono::Utc>,
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);
}
}