tdm_server_rust/service/
task_tracking_service.rs1use crate::{
9 app::AppState,
10 cache::{
11 get_episode_tasks_cached, get_member_task_counts_cached, set_episode_tasks_cached,
12 set_member_task_counts_cached,
13 },
14 common::PageBean,
15 entity::episode::{MangaEpisodeTb, MemberTaskCount, PendingEpisode, PendingMangaTask, TaskTrackingResponse},
16 error::ApiResult,
17 repository::task_tracking_repo::{PendingEpisodeRow, TaskTrackingRepository},
18 utils::{agent_debug, page::slice_rows},
19};
20use std::collections::HashMap;
21
22pub struct TaskTrackingService;
24
25impl TaskTrackingService {
26 #[tracing::instrument(skip_all, level = "debug")]
34 pub async fn get_member_task_count_list(state: &AppState) -> ApiResult<Vec<MemberTaskCount>> {
35 if let Some(cached) = get_member_task_counts_cached(state).await {
36 return Ok(cached);
37 }
38 let repo = TaskTrackingRepository::new(state.db.clone());
39 let rows = repo.list_member_task_counts().await?;
40 let data = TaskTrackingRepository::to_member_task_counts(&rows);
41 set_member_task_counts_cached(state, data.clone()).await;
42 Ok(data)
43 }
44
45 #[tracing::instrument(skip_all, level = "debug")]
53 pub async fn get_all_task(state: &AppState) -> ApiResult<TaskTrackingResponse> {
54 if let Some(cached) = get_episode_tasks_cached(state).await {
55 return Ok(cached);
56 }
57 let repo = TaskTrackingRepository::new(state.db.clone());
58 let episodes = repo.list_unpublished_episodes().await?;
59 let data = TaskTrackingRepository::build_task_response(&episodes);
60 set_episode_tasks_cached(state, data.clone()).await;
61 Ok(data)
62 }
63
64 #[tracing::instrument(skip_all, level = "debug")]
66 pub async fn get_pending_manga_tasks(
67 state: &AppState,
68 page: i32,
69 page_size: i32,
70 manga_tran_name: Option<String>,
71 ) -> ApiResult<PageBean<PendingMangaTask>> {
72 let repo = TaskTrackingRepository::new(state.db.clone());
73 let name_ref = manga_tran_name.as_deref();
74 let (total, episodes) = tokio::try_join!(
75 repo.count_pending_publish_episodes(name_ref),
76 repo.list_pending_publish_episodes(name_ref, page, page_size),
77 )?;
78
79 agent_debug::log(
81 "A",
82 "task_tracking_service.rs:get_pending_manga_tasks",
83 "pending episodes fetched",
84 serde_json::json!({
85 "total": total,
86 "pageCount": episodes.len(),
87 "sampleMangaIds": episodes.iter().take(3).map(|e| e.manga_id).collect::<Vec<_>>()
88 }),
89 );
90 let manga_ids: Vec<i32> = episodes
93 .iter()
94 .map(|e| e.manga_id)
95 .collect::<std::collections::HashSet<_>>()
96 .into_iter()
97 .collect();
98
99 let (latest, next_publish) = repo.map_publish_episode_context(&manga_ids).await?;
100
101 let mut grouped: HashMap<i32, Vec<&PendingEpisodeRow>> = HashMap::new();
102 let mut manga_order: Vec<i32> = Vec::new();
103 for ep in &episodes {
104 if !grouped.contains_key(&ep.manga_id) {
105 manga_order.push(ep.manga_id);
106 }
107 grouped.entry(ep.manga_id).or_default().push(ep);
108 }
109
110 let mut tasks: Vec<PendingMangaTask> = Vec::new();
111 for manga_id in manga_order {
112 let Some(rows) = grouped.get(&manga_id) else {
113 continue;
114 };
115 let first = rows[0];
116 let mut pending_list: Vec<PendingEpisode> = rows
117 .iter()
118 .map(|row| {
119 let status = if row.reviewer_update_time.is_some() {
120 "publisher".to_string()
121 } else {
122 "reviewer".to_string()
123 };
124 let next = next_publish
125 .get(&row.manga_id)
126 .map(|id| *id == row.episode_id)
127 .unwrap_or(false);
128 PendingEpisode {
129 mangaepisodetb: row_to_episode_tb(row),
130 status,
131 next_publish: next,
132 }
133 })
134 .collect();
135 pending_list.sort_by(|a, b| {
136 a.mangaepisodetb
137 .manga_episode
138 .cmp(&b.mangaepisodetb.manga_episode)
139 });
140
141 tasks.push(PendingMangaTask {
142 mangatb: TaskTrackingRepository::row_mangatb(first),
143 newest_manga_episode: latest.get(&manga_id).cloned(),
144 pending_episode_list: pending_list,
145 });
146 }
147
148 tasks.sort_by(|a, b| {
149 a.mangatb
150 .manga_tran_name
151 .cmp(&b.mangatb.manga_tran_name)
152 });
153
154 agent_debug::log(
156 "A",
157 "task_tracking_service.rs:get_pending_manga_tasks",
158 "pending manga tasks built",
159 serde_json::json!({
160 "taskCount": tasks.len(),
161 "firstTaskEpisodes": tasks.first().map(|t| t.pending_episode_list.len())
162 }),
163 );
164 Ok(slice_rows(tasks, total))
167 }
168}
169
170fn row_to_episode_tb(row: &PendingEpisodeRow) -> MangaEpisodeTb {
172 MangaEpisodeTb {
173 id: row.episode_id,
174 manga_id: row.manga_id,
175 manga_episode: row.manga_episode.clone(),
176 manga_episode_name: row.manga_episode_name.clone(),
177 provider_id: row.provider_id,
178 translator_id: row.translator_id,
179 proofreader_id: row.proofreader_id,
180 letterer_id: row.letterer_id,
181 timer_id: row.timer_id,
182 reviewer_id: row.reviewer_id,
183 setup_time: row.setup_time,
184 update_time: row.update_time,
185 translator_file: row.translator_file.clone(),
186 proofreader_file: row.proofreader_file.clone(),
187 timer_file: row.timer_file.clone(),
188 publish_link: row.publish_link.clone(),
189 provider_file_oss_id: row.provider_file_oss_id,
190 translator_file_oss_id: row.translator_file_oss_id,
191 proofreader_file_oss_id: row.proofreader_file_oss_id,
192 letterer_file_oss_id: row.letterer_file_oss_id,
193 timer_file_oss_id: row.timer_file_oss_id,
194 }
195}