Skip to main content

tdm_server_rust/web/
episode_controller.rs

1//! 话数管理接口 (Episode Controller)
2//!
3//! 话数 CRUD、发布链接、上传页、统计。
4//! 对应 Java EpisodeController。
5
6use crate::utils::query_deserialize::{de_i32, de_opt_string, de_page, de_page_size};
7use crate::{
8    app::AppState,
9    common::result::{json_bytes_response, ResultBody},
10    common::AppJson,
11    entity::episode::{
12        EpisodeDetailVo, EpisodeEditDto, EpisodeType, NewestEpisodeVo, PublishLinkRequest,
13        Statistics,
14    },
15    error::ApiResult,
16    middleware::AuthMember,
17    service::episode_service::EpisodeService,
18};
19use axum::{
20    extract::{Extension, Path, Query, State},
21    response::Response,
22    routing::{get, put},
23    Router,
24};
25use chrono::{DateTime, Utc};
26use serde::Deserialize;
27
28/// 话数分页参数
29#[derive(Debug, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct EpisodePageQuery {
32    /// 页码
33    #[serde(default, deserialize_with = "de_page")]
34    pub page: i32,
35    /// 每页条数
36    #[serde(default, deserialize_with = "de_page_size")]
37    pub page_size: i32,
38    /// 漫画 ID
39    #[serde(deserialize_with = "de_i32")]
40    pub id: i32,
41    /// 话数分类,缺省仅查询正篇。
42    #[serde(default)]
43    pub episode_type: EpisodeType,
44}
45
46/// 上传稿件分页参数
47#[derive(Debug, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct UploadPageQuery {
50    /// 页码
51    #[serde(default, deserialize_with = "de_page")]
52    pub page: i32,
53    /// 每页条数
54    #[serde(default, deserialize_with = "de_page_size")]
55    pub page_size: i32,
56    /// 漫画译名
57    #[serde(default, deserialize_with = "de_opt_string")]
58    pub manga_tran_name: Option<String>,
59    /// 组员名
60    #[serde(default, deserialize_with = "de_opt_string")]
61    pub username: Option<String>,
62}
63
64/// 任意时间段统计参数
65#[derive(Debug, Deserialize)]
66pub struct StatisticsAnyQuery {
67    /// 起始时间
68    pub start: String,
69    /// 结束时间
70    pub end: String,
71}
72
73/// 组员统计参数
74#[derive(Debug, Deserialize)]
75pub struct MemberStatisticsQuery {
76    /// 起始时间
77    pub start: DateTime<Utc>,
78    /// 结束时间
79    pub end: DateTime<Utc>,
80}
81
82/// 话数路由(挂载于 `/api/episodes`)
83pub fn routes() -> Router<AppState> {
84    Router::new()
85        .route(
86            "/",
87            get(page_episode)
88                .post(add_episode)
89                .put(update_manga_episode),
90        )
91        .route("/manga/:mangaId", get(list_episode_by_manga_id))
92        .route("/publishLinks", put(update_publish_links))
93        .route("/publishLink", put(update_publish_link))
94        .route("/:id", get(get_manga_episode_by_id).delete(delete_episode))
95        .route("/newest/:id", get(get_newest_manga_episode_by_id))
96        .route("/download", get(get_uploaded_submit))
97        .route("/statisticsAny", get(statistics_any))
98        .route("/statistics", get(statistics))
99        .route("/memberStatistics", get(member_statistics))
100        .route("/rollback/:episodeId/:workflowType", put(rollback_episode))
101}
102
103/// 按漫画 ID 列出话数
104#[tracing::instrument(skip_all, level = "info")]
105pub async fn list_episode_by_manga_id(
106    State(state): State<AppState>,
107    Path(manga_id): Path<i32>,
108) -> ApiResult<Response> {
109    let json = EpisodeService::select_list_by_manga_id_json(&state, manga_id).await?;
110    Ok(json_bytes_response(json))
111}
112
113/// 批量更新发布链接
114#[tracing::instrument(skip_all, level = "info")]
115pub async fn update_publish_links(
116    State(state): State<AppState>,
117    AppJson(body): AppJson<Vec<PublishLinkRequest>>,
118) -> ApiResult<ResultBody<()>> {
119    EpisodeService::update_manga_episodes(&state, body).await?;
120    Ok(ResultBody::success())
121}
122
123/// 更新单条发布链接
124#[tracing::instrument(skip_all, level = "info")]
125pub async fn update_publish_link(
126    State(state): State<AppState>,
127    AppJson(body): AppJson<PublishLinkRequest>,
128) -> ApiResult<ResultBody<()>> {
129    EpisodeService::update_publish_link(&state, body.id, body.publish_link).await?;
130    Ok(ResultBody::success())
131}
132
133/// 分页查询话数
134#[tracing::instrument(skip_all, level = "info")]
135pub async fn page_episode(
136    State(state): State<AppState>,
137    Query(q): Query<EpisodePageQuery>,
138) -> ApiResult<Response> {
139    let json = EpisodeService::page_episode_json(
140        &state,
141        q.page,
142        q.page_size,
143        q.id,
144        q.episode_type,
145    )
146    .await?;
147    Ok(json_bytes_response(json))
148}
149
150/// 删除话数
151#[tracing::instrument(skip_all, level = "info")]
152pub async fn delete_episode(
153    State(state): State<AppState>,
154    Path(id): Path<i32>,
155) -> ApiResult<ResultBody<()>> {
156    EpisodeService::delete_episode(&state, id).await?;
157    Ok(ResultBody::success())
158}
159
160/// 新增话数
161#[tracing::instrument(skip_all, level = "info")]
162pub async fn add_episode(
163    State(state): State<AppState>,
164    AppJson(body): AppJson<EpisodeEditDto>,
165) -> ApiResult<ResultBody<()>> {
166    EpisodeService::add_episodes(&state, body).await?;
167    Ok(ResultBody::success())
168}
169
170/// 按 ID 查询话数
171#[tracing::instrument(skip_all, level = "info")]
172pub async fn get_manga_episode_by_id(
173    State(state): State<AppState>,
174    Path(id): Path<i32>,
175) -> ApiResult<ResultBody<EpisodeDetailVo>> {
176    let data = EpisodeService::get_manga_episode_by_id(&state, id).await?;
177    Ok(ResultBody::success_data(data))
178}
179
180/// 查询最新话
181#[tracing::instrument(skip_all, level = "info")]
182pub async fn get_newest_manga_episode_by_id(
183    State(state): State<AppState>,
184    Path(id): Path<i32>,
185) -> ApiResult<ResultBody<NewestEpisodeVo>> {
186    let data = EpisodeService::get_newest_manga_episode_by_id(&state, id).await?;
187    Ok(ResultBody::success_data(data))
188}
189
190/// 更新话数
191#[tracing::instrument(skip_all, level = "info")]
192pub async fn update_manga_episode(
193    State(state): State<AppState>,
194    AppJson(body): AppJson<EpisodeEditDto>,
195) -> ApiResult<ResultBody<()>> {
196    EpisodeService::update_manga_episode(&state, body).await?;
197    Ok(ResultBody::success())
198}
199
200/// 分页查询上传稿件
201#[tracing::instrument(skip_all, level = "info")]
202pub async fn get_uploaded_submit(
203    State(state): State<AppState>,
204    Query(q): Query<UploadPageQuery>,
205) -> ApiResult<Response> {
206    let json = EpisodeService::get_uploaded_submit_json(
207        &state,
208        q.page,
209        q.page_size,
210        q.manga_tran_name,
211        q.username,
212    )
213    .await?;
214    Ok(json_bytes_response(json))
215}
216
217/// 任意时间段统计
218#[tracing::instrument(skip_all, level = "info")]
219pub async fn statistics_any(
220    State(state): State<AppState>,
221    Query(q): Query<StatisticsAnyQuery>,
222) -> ApiResult<ResultBody<Statistics>> {
223    let data = EpisodeService::get_statistics(&state, &q.start, &q.end).await?;
224    Ok(ResultBody::success_data(data))
225}
226
227/// 预设统计(缓存命中时直出预序列化 JSON)
228#[tracing::instrument(skip_all, level = "info")]
229pub async fn statistics(State(state): State<AppState>) -> ApiResult<Response> {
230    let json = EpisodeService::get_statistics_list_json(&state).await?;
231    Ok(json_bytes_response(json))
232}
233
234/// 组员完成统计(缓存命中时直出预序列化 JSON)
235#[tracing::instrument(skip_all, level = "info")]
236pub async fn member_statistics(
237    State(state): State<AppState>,
238    Query(q): Query<MemberStatisticsQuery>,
239) -> ApiResult<Response> {
240    let json = EpisodeService::get_member_statistics_json(&state, q.start, q.end).await?;
241    Ok(json_bytes_response(json))
242}
243
244/// 回退流程
245#[tracing::instrument(skip_all, level = "info")]
246pub async fn rollback_episode(
247    State(state): State<AppState>,
248    Extension(AuthMember(member)): Extension<AuthMember>,
249    Path((episode_id, workflow_type)): Path<(i32, String)>,
250) -> ApiResult<ResultBody<()>> {
251    EpisodeService::rollback_episode(&state, episode_id, &workflow_type, member.map(|m| m.id))
252        .await?;
253    Ok(ResultBody::success())
254}