Skip to main content

tdm_server_rust/repository/
magazine_repo.rs

1//! 杂志数据访问层 (Magazine Repository)
2//!
3//! 封装 `magazine` 表的增删改查(SeaORM)。
4
5use crate::db::DbConn;
6use crate::entity::{magazine::Magazine, manga::Manga};
7use crate::sea_entity::{magazine, mangamagazine, mangatb};
8use sea_orm::{
9    ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, JoinType, PaginatorTrait,
10    QueryFilter, QueryOrder, QuerySelect, RelationTrait, Set,
11};
12use sqlx::postgres::PgPool;
13
14/// 杂志完整字段(含种类、价格)
15#[derive(Debug, Clone)]
16pub struct MagazineRow {
17    /// 杂志 ID
18    pub id: Option<i32>,
19    /// 杂志名
20    pub magazine_name: Option<String>,
21    /// 种类
22    pub type_: Option<i16>,
23    /// 更新时间字符串
24    pub update_time: Option<String>,
25    /// 每期价格
26    pub price: Option<i32>,
27}
28
29/// 杂志仓储
30pub struct MagazineRepository {
31    /// SeaORM 数据库连接
32    db: DbConn,
33}
34
35impl MagazineRepository {
36    /// 从 `PgPool` 构造
37    pub fn new(pool: PgPool) -> Self {
38        Self {
39            db: crate::db::from_sqlx_pool(pool),
40        }
41    }
42
43    /// 从 `DatabaseConnection` 构造
44    pub fn from_db(db: DatabaseConnection) -> Self {
45        Self { db }
46    }
47
48    /// 条件查询杂志列表
49    #[tracing::instrument(skip_all, level = "debug")]
50    pub async fn get_magazines(
51        &self,
52        magazine_id: Option<i16>,
53        magazine_name: Option<&str>,
54    ) -> crate::error::ApiResult<Vec<MagazineRow>> {
55        let mut q = magazine::Entity::find();
56        if let Some(id) = magazine_id {
57            q = q.filter(magazine::Column::Id.eq(id as i32));
58        }
59        if let Some(name) = magazine_name {
60            q = q.filter(magazine::Column::MagazineName.eq(name));
61        }
62        let rows = q.all(&self.db).await?;
63        Ok(rows.into_iter().map(model_to_magazine_row).collect())
64    }
65
66    /// 按 ID 删除杂志
67    #[tracing::instrument(skip_all, level = "debug")]
68    pub async fn delete_magazine_by_id(&self, id: i32) -> crate::error::ApiResult<()> {
69        magazine::Entity::delete_by_id(id).exec(&self.db).await?;
70        Ok(())
71    }
72
73    /// 新增杂志
74    #[tracing::instrument(skip_all, level = "debug")]
75    pub async fn insert_magazine(&self, row: &MagazineRow) -> crate::error::ApiResult<i32> {
76        let name = row
77            .magazine_name
78            .as_deref()
79            .ok_or_else(|| crate::error::AppError::business("杂志名不能为空"))?;
80        let model = magazine::ActiveModel {
81            magazine_name: Set(name.to_string()),
82            r#type: Set(row.type_.map(|v| v as i32)),
83            update_time: Set(row.update_time.clone()),
84            price: Set(row.price),
85            ..Default::default()
86        };
87        let inserted = model.insert(&self.db).await?;
88        Ok(inserted.id)
89    }
90
91    /// 按 ID 查询杂志
92    #[tracing::instrument(skip_all, level = "debug")]
93    pub async fn get_magazine_by_id(
94        &self,
95        id: i32,
96    ) -> crate::error::ApiResult<Option<MagazineRow>> {
97        let row = magazine::Entity::find_by_id(id).one(&self.db).await?;
98        Ok(row.map(model_to_magazine_row))
99    }
100
101    /// 更新杂志(COALESCE 语义:None 字段保持原值)
102    #[tracing::instrument(skip_all, level = "debug")]
103    pub async fn update_magazine(&self, row: &MagazineRow) -> crate::error::ApiResult<()> {
104        let id = row
105            .id
106            .ok_or_else(|| crate::error::AppError::business("杂志 ID 不能为空"))?;
107        let existing = magazine::Entity::find_by_id(id)
108            .one(&self.db)
109            .await?
110            .ok_or_else(|| crate::error::AppError::business("杂志不存在"))?;
111        let mut am: magazine::ActiveModel = existing.into();
112        if let Some(t) = row.type_ {
113            am.r#type = Set(Some(t as i32));
114        }
115        if let Some(ref name) = row.magazine_name {
116            am.magazine_name = Set(name.clone());
117        }
118        if let Some(ref ut) = row.update_time {
119            am.update_time = Set(Some(ut.clone()));
120        }
121        if let Some(p) = row.price {
122            am.price = Set(Some(p));
123        }
124        am.update(&self.db).await?;
125        Ok(())
126    }
127
128    /// 按名称检测杂志是否已存在
129    #[tracing::instrument(skip_all, level = "debug")]
130    pub async fn test_magazine_name(
131        &self,
132        magazine_name: &str,
133    ) -> crate::error::ApiResult<Option<MagazineRow>> {
134        let row = magazine::Entity::find()
135            .filter(magazine::Column::MagazineName.eq(magazine_name))
136            .one(&self.db)
137            .await?;
138        Ok(row.map(model_to_magazine_row))
139    }
140
141    /// 删除前检查是否绑定漫画
142    #[tracing::instrument(skip_all, level = "debug")]
143    pub async fn get_related_magazine(&self, id: i32) -> crate::error::ApiResult<bool> {
144        let n = mangamagazine::Entity::find()
145            .filter(mangamagazine::Column::MagazineId.eq(id))
146            .count(&self.db)
147            .await?;
148        Ok(n > 0)
149    }
150
151    /// 获取最大杂志 ID
152    #[tracing::instrument(skip_all, level = "debug")]
153    pub async fn get_max_magazine_id(&self) -> crate::error::ApiResult<Option<i32>> {
154        let row = magazine::Entity::find()
155            .order_by_desc(magazine::Column::Id)
156            .one(&self.db)
157            .await?;
158        Ok(row.map(|r| r.id))
159    }
160
161    /// 无条件查询全部杂志
162    #[tracing::instrument(skip_all, level = "debug")]
163    pub async fn get_manga_magazine_list(&self) -> crate::error::ApiResult<Vec<MagazineRow>> {
164        let rows = magazine::Entity::find().all(&self.db).await?;
165        Ok(rows.into_iter().map(model_to_magazine_row).collect())
166    }
167
168    /// 查询杂志关联的全部漫画
169    #[tracing::instrument(skip_all, level = "debug")]
170    pub async fn get_magazine_manga(&self, id: i32) -> crate::error::ApiResult<Vec<Manga>> {
171        let rows = mangatb::Entity::find()
172            .join(
173                JoinType::InnerJoin,
174                mangamagazine::Relation::Mangatb.def().rev(),
175            )
176            .filter(mangamagazine::Column::MagazineId.eq(id))
177            .distinct()
178            .order_by_desc(mangatb::Column::UpdateTime)
179            .all(&self.db)
180            .await?;
181        Ok(rows.into_iter().map(model_to_manga).collect())
182    }
183
184    /// 将完整行转为实体 Magazine(仅 ID 与名称)
185    pub fn to_entity(row: &MagazineRow) -> Magazine {
186        Magazine {
187            id: row.id,
188            magazine_name: row.magazine_name.clone(),
189            r#type: row.type_,
190            update_time: row.update_time.clone(),
191            price: row.price,
192        }
193    }
194}
195
196/// Model 映射为杂志行
197fn model_to_magazine_row(m: magazine::Model) -> MagazineRow {
198    MagazineRow {
199        id: Some(m.id),
200        magazine_name: Some(m.magazine_name),
201        type_: m.r#type.map(|v| v as i16),
202        update_time: m.update_time,
203        price: m.price,
204    }
205}
206
207/// Model 映射为漫画 POJO
208fn model_to_manga(m: mangatb::Model) -> Manga {
209    Manga {
210        id: Some(m.id),
211        manga_tran_name: Some(m.manga_tran_name),
212        manga_ori_name: Some(m.manga_ori_name),
213        category: Some(m.category as i16),
214        manga_status: Some(m.manga_status as i16),
215        img_url: Some(m.image),
216        link: m.link,
217        introduction: m.introduction,
218        update_time: Some(m.update_time.and_utc()),
219    }
220}