Skip to main content

tdm_server_rust/repository/
editor_unit_repo.rs

1//! 编辑器标记单元数据访问层 (Editor Unit Repository)
2//!
3//! 封装 `editor_unit`(HASH 分区表)的查询与写入。所有操作强制携带 `episode_id`
4//! (分片键),保证命中单一分区、禁止跨分区全扫,并为未来多库分片预留路由空间。
5
6use crate::db::DbConn;
7use crate::entity::editor::EditorUnitInput;
8use crate::error::ApiResult;
9use crate::sea_entity::editor_unit;
10use sea_orm::{
11    ColumnTrait, ConnectionTrait, DatabaseConnection, EntityTrait, PaginatorTrait, QueryFilter,
12    QueryOrder, Set, TransactionTrait,
13};
14use sqlx::postgres::PgPool;
15
16/// 编辑器标记单元仓储
17pub struct EditorUnitRepository {
18    /// SeaORM 连接
19    db: DbConn,
20}
21
22impl EditorUnitRepository {
23    /// 从 `PgPool` 构造
24    pub fn new(pool: PgPool) -> Self {
25        Self {
26            db: crate::db::from_sqlx_pool(pool),
27        }
28    }
29
30    /// 从 `DatabaseConnection` 构造
31    pub fn from_db(db: DatabaseConnection) -> Self {
32        Self { db }
33    }
34
35    /// 查询某话数全部标记单元(编辑器整话加载)
36    #[tracing::instrument(skip_all, level = "debug")]
37    pub async fn list_by_episode(&self, episode_id: i32) -> ApiResult<Vec<editor_unit::Model>> {
38        let rows = editor_unit::Entity::find()
39            .filter(editor_unit::Column::EpisodeId.eq(episode_id))
40            .order_by_asc(editor_unit::Column::PageId)
41            .order_by_asc(editor_unit::Column::UnitIndex)
42            .all(&self.db)
43            .await?;
44        Ok(rows)
45    }
46
47    /// 查询某页标记单元
48    #[tracing::instrument(skip_all, level = "debug")]
49    pub async fn list_by_page(
50        &self,
51        episode_id: i32,
52        page_id: i64,
53    ) -> ApiResult<Vec<editor_unit::Model>> {
54        let rows = editor_unit::Entity::find()
55            .filter(editor_unit::Column::EpisodeId.eq(episode_id))
56            .filter(editor_unit::Column::PageId.eq(page_id))
57            .order_by_asc(editor_unit::Column::UnitIndex)
58            .all(&self.db)
59            .await?;
60        Ok(rows)
61    }
62
63    /// 整页替换式保存标记单元:先删该页旧单元,再插入新单元(事务)
64    #[tracing::instrument(skip_all, level = "debug")]
65    pub async fn replace_page_units(
66        &self,
67        episode_id: i32,
68        page_id: i64,
69        editor_member_id: i32,
70        units: &[EditorUnitInput],
71    ) -> ApiResult<usize> {
72        let txn = self.db.begin().await?;
73        editor_unit::Entity::delete_many()
74            .filter(editor_unit::Column::EpisodeId.eq(episode_id))
75            .filter(editor_unit::Column::PageId.eq(page_id))
76            .exec(&txn)
77            .await?;
78        if !units.is_empty() {
79            let now = chrono::Utc::now().naive_utc();
80            let models: Vec<editor_unit::ActiveModel> = units
81                .iter()
82                .map(|u| editor_unit::ActiveModel {
83                    episode_id: Set(episode_id),
84                    page_id: Set(page_id),
85                    unit_index: Set(u.index),
86                    x_coord: Set(u.x_coord),
87                    y_coord: Set(u.y_coord),
88                    in_bubble: Set(u.in_bubble),
89                    is_proofread: Set(u.is_proofread),
90                    translated_text: Set(u.translated_text.clone()),
91                    translator_id: Set(u.translator_id),
92                    translator_comment: Set(u.translator_comment.clone()),
93                    proofreader_text: Set(u.proofreader_text.clone()),
94                    proofreader_id: Set(u.proofreader_id),
95                    proofreader_comment: Set(u.proofreader_comment.clone()),
96                    last_edited_by: Set(Some(editor_member_id)),
97                    last_edited_at: Set(Some(now)),
98                    ..Default::default()
99                })
100                .collect();
101            editor_unit::Entity::insert_many(models).exec(&txn).await?;
102        }
103        txn.commit().await?;
104        Ok(units.len())
105    }
106
107    /// 删除某话数全部标记单元(force 重建图源时)
108    #[tracing::instrument(skip_all, level = "debug")]
109    pub async fn delete_by_episode(&self, episode_id: i32) -> ApiResult<()> {
110        Self::delete_by_episode_with(&self.db, episode_id).await
111    }
112
113    /// 使用指定连接统计某话数的标记单元数量,供事务内重建保护使用。
114    pub async fn count_by_episode_with<C: ConnectionTrait>(
115        db: &C,
116        episode_id: i32,
117    ) -> ApiResult<u64> {
118        Ok(editor_unit::Entity::find()
119            .filter(editor_unit::Column::EpisodeId.eq(episode_id))
120            .count(db)
121            .await?)
122    }
123
124    /// 使用指定连接删除某话数全部标记单元,供事务内复用。
125    pub async fn delete_by_episode_with<C: ConnectionTrait>(
126        db: &C,
127        episode_id: i32,
128    ) -> ApiResult<()> {
129        editor_unit::Entity::delete_many()
130            .filter(editor_unit::Column::EpisodeId.eq(episode_id))
131            .exec(db)
132            .await?;
133        Ok(())
134    }
135}