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