Skip to main content

tdm_server_rust/service/
magazine_service.rs

1//! 杂志业务服务 (Magazine Service)
2//!
3//! 杂志信息的增删改查及关联漫画查询。
4
5use crate::{
6    app::AppState,
7    common::PageBean,
8    entity::{magazine::Magazine, manga::MangaListVo},
9    error::{ApiResult, AppError},
10    repository::{magazine_repo::{MagazineRepository, MagazineRow}, manga_repo::MangaRepository},
11    utils::page::paginate,
12};
13
14/// 杂志服务
15pub struct MagazineService;
16
17impl MagazineService {
18    /// 分页查询杂志
19    #[tracing::instrument(skip_all, level = "debug")]
20    pub async fn get_magazines(
21        state: &AppState,
22        page: i32,
23        page_size: i32,
24        magazine_id: Option<i16>,
25        magazine_name: Option<String>,
26    ) -> ApiResult<PageBean<Magazine>> {
27        let repo = MagazineRepository::new(state.db.clone());
28        let all = repo
29            .get_magazines(magazine_id, magazine_name.as_deref())
30            .await?;
31        Ok(paginate(all.into_iter().map(row_to_magazine).collect(), page, page_size))
32    }
33
34    /// 全量杂志列表
35    #[tracing::instrument(skip_all, level = "debug")]
36    pub async fn get_manga_magazine_list(state: &AppState) -> ApiResult<Vec<Magazine>> {
37        let repo = MagazineRepository::new(state.db.clone());
38        let rows = repo.get_manga_magazine_list().await?;
39        Ok(rows.into_iter().map(row_to_magazine).collect())
40    }
41
42    /// 查询杂志关联漫画
43    #[tracing::instrument(skip_all, level = "debug")]
44    pub async fn get_magazine_manga(
45        state: &AppState,
46        page: i32,
47        page_size: i32,
48        id: i32,
49    ) -> ApiResult<PageBean<MangaListVo>> {
50        MangaRepository::new(state.db.clone())
51            .list_by_magazine(id)
52            .await
53            .map(|all| paginate(all, page, page_size))
54    }
55
56    /// 删除杂志
57    #[tracing::instrument(skip_all, level = "debug")]
58    pub async fn delete_magazine(state: &AppState, id: i32) -> ApiResult<()> {
59        let repo = MagazineRepository::new(state.db.clone());
60        if repo.get_related_magazine(id).await? {
61            return Err(AppError::business("该杂志已经绑定漫画了喵!"));
62        }
63        repo.delete_magazine_by_id(id).await
64    }
65
66    /// 新增杂志
67    #[tracing::instrument(skip_all, level = "debug")]
68    pub async fn add_magazine(state: &AppState, magazine: Magazine) -> ApiResult<()> {
69        let repo = MagazineRepository::new(state.db.clone());
70        if let Some(name) = &magazine.magazine_name {
71            if repo.test_magazine_name(name).await?.is_some() {
72                return Err(AppError::unique("杂志名"));
73            }
74        }
75        let row = magazine_to_row(&magazine);
76        repo.insert_magazine(&row).await?;
77        Ok(())
78    }
79
80    /// 按 ID 查询杂志
81    #[tracing::instrument(skip_all, level = "debug")]
82    pub async fn get_magazine_by_id(state: &AppState, id: i32) -> ApiResult<Magazine> {
83        let repo = MagazineRepository::new(state.db.clone());
84        repo.get_magazine_by_id(id)
85            .await?
86            .map(row_to_magazine)
87            .ok_or_else(|| AppError::business("杂志不存在喵"))
88    }
89
90    /// 更新杂志
91    #[tracing::instrument(skip_all, level = "debug")]
92    pub async fn update_magazine(state: &AppState, magazine: Magazine) -> ApiResult<()> {
93        let repo = MagazineRepository::new(state.db.clone());
94        let id = magazine
95            .id
96            .ok_or_else(|| AppError::business("缺少杂志 ID"))?;
97        let mut row = repo
98            .get_magazine_by_id(id)
99            .await?
100            .ok_or_else(|| AppError::business("杂志不存在喵"))?;
101        apply_magazine_fields(&mut row, &magazine);
102        if let Some(name) = &row.magazine_name {
103            if let Some(existing) = repo.test_magazine_name(name).await? {
104                if existing.id != Some(id) {
105                    return Err(AppError::unique("杂志名"));
106                }
107            }
108        }
109        repo.update_magazine(&row).await
110    }
111}
112
113/// 请求字段合并到杂志行
114fn apply_magazine_fields(row: &mut MagazineRow, magazine: &Magazine) {
115    if let Some(v) = &magazine.magazine_name {
116        row.magazine_name = Some(v.clone());
117    }
118    if let Some(v) = magazine.r#type {
119        row.type_ = Some(v);
120    }
121    if let Some(v) = &magazine.update_time {
122        row.update_time = Some(v.clone());
123    }
124    if let Some(v) = magazine.price {
125        row.price = Some(v);
126    }
127}
128
129/// 杂志实体转行
130fn magazine_to_row(magazine: &Magazine) -> MagazineRow {
131    MagazineRow {
132        id: magazine.id,
133        magazine_name: magazine.magazine_name.clone(),
134        type_: magazine.r#type,
135        update_time: magazine.update_time.clone(),
136        price: magazine.price,
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::{apply_magazine_fields, magazine_to_row};
143    use crate::entity::magazine::Magazine;
144    use crate::repository::magazine_repo::MagazineRow;
145
146    #[test]
147    fn magazine_to_row_maps_all_fields() {
148        let m = Magazine {
149            id: None,
150            magazine_name: Some("季刊".into()),
151            r#type: Some(4),
152            update_time: Some("每月1日".into()),
153            price: Some(680),
154        };
155        let row = magazine_to_row(&m);
156        assert_eq!(row.magazine_name.as_deref(), Some("季刊"));
157        assert_eq!(row.type_, Some(4));
158        assert_eq!(row.update_time.as_deref(), Some("每月1日"));
159        assert_eq!(row.price, Some(680));
160    }
161
162    #[test]
163    fn apply_magazine_fields_merges_partial_update() {
164        let mut row = MagazineRow {
165            id: Some(1),
166            magazine_name: Some("旧名".into()),
167            type_: Some(1),
168            update_time: Some("旧时间".into()),
169            price: Some(100),
170        };
171        let patch = Magazine {
172            id: Some(1),
173            magazine_name: None,
174            r#type: Some(5),
175            update_time: Some("新时间".into()),
176            price: None,
177        };
178        apply_magazine_fields(&mut row, &patch);
179        assert_eq!(row.magazine_name.as_deref(), Some("旧名"));
180        assert_eq!(row.type_, Some(5));
181        assert_eq!(row.update_time.as_deref(), Some("新时间"));
182        assert_eq!(row.price, Some(100));
183    }
184}
185
186/// 杂志行转实体
187fn row_to_magazine(row: MagazineRow) -> Magazine {
188    Magazine {
189        id: row.id,
190        magazine_name: row.magazine_name,
191        r#type: row.type_,
192        update_time: row.update_time,
193        price: row.price,
194    }
195}