Skip to main content

tdm_server_rust/web/
manga_benefit_controller.rs

1//! 漫画特典接口 (Manga Benefit Controller)
2//!
3//! 特典图片的增删改查。
4//! 对应 Java MangaBenefitController。
5
6use crate::{
7    app::AppState,
8    common::result::ResultBody,
9    entity::manga::{MangaBenefitDto, VolumeVo},
10    error::{ApiResult, AppError},
11    service::manga_benefit_service::MangaBenefitService,
12};
13use axum::{
14    body::Bytes,
15    extract::{Multipart, Path, State},
16    routing::{get, post},
17    Router,
18};
19
20/// 特典路由(挂载于 `/api/manga/benefits`)
21pub fn routes() -> Router<AppState> {
22    Router::new()
23        .route("/", post(add_manga_benefit).put(update_manga_benefit))
24        .route("/:id", get(get_manga_benefits).delete(delete_manga_benefit))
25}
26
27/// 按漫画 ID 获取特典列表
28#[tracing::instrument(skip_all, level = "info")]
29pub async fn get_manga_benefits(
30    State(state): State<AppState>,
31    Path(manga_id): Path<i32>,
32) -> ApiResult<ResultBody<Vec<VolumeVo>>> {
33    let data = MangaBenefitService::get_manga_benefits_by_manga_id(&state, manga_id).await?;
34    Ok(ResultBody::success_data(data))
35}
36
37/// 新增特典
38#[tracing::instrument(skip_all, level = "info")]
39pub async fn add_manga_benefit(
40    State(state): State<AppState>,
41    mut multipart: Multipart,
42) -> ApiResult<ResultBody<()>> {
43    let (file, dto) = parse_benefit_multipart(&mut multipart).await?;
44    MangaBenefitService::add_manga_benefit(&state, file, dto).await?;
45    Ok(ResultBody::success())
46}
47
48/// 更新特典
49#[tracing::instrument(skip_all, level = "info")]
50pub async fn update_manga_benefit(
51    State(state): State<AppState>,
52    mut multipart: Multipart,
53) -> ApiResult<ResultBody<()>> {
54    let (file, dto) = parse_benefit_multipart(&mut multipart).await?;
55    MangaBenefitService::update_manga_benefit(&state, file, dto).await?;
56    Ok(ResultBody::success())
57}
58
59/// 删除特典
60#[tracing::instrument(skip_all, level = "info")]
61pub async fn delete_manga_benefit(
62    State(state): State<AppState>,
63    Path(id): Path<i32>,
64) -> ApiResult<ResultBody<()>> {
65    MangaBenefitService::delete_manga_benefit_by_id(&state, id).await?;
66    Ok(ResultBody::success())
67}
68
69#[tracing::instrument(skip_all, level = "info")]
70async fn parse_benefit_multipart(
71    multipart: &mut Multipart,
72) -> ApiResult<(Option<Bytes>, MangaBenefitDto)> {
73    let mut file = None;
74    let mut dto = MangaBenefitDto {
75        id: None,
76        manga_id: 0,
77        volume_number: None,
78        volume_title: None,
79        store_name: None,
80        benefit_name: None,
81        benefit_tag: None,
82        img_url: None,
83        r#type: None,
84        publish_time: None,
85    };
86    while let Some(field) = multipart
87        .next_field()
88        .await
89        .map_err(|e| AppError::Internal(e.to_string()))?
90    {
91        match field.name() {
92            Some("file") => {
93                file = Some(
94                    field
95                        .bytes()
96                        .await
97                        .map_err(|e| AppError::Internal(e.to_string()))?,
98                );
99            }
100            Some("mangaBenefit") => {
101                let raw = field
102                    .bytes()
103                    .await
104                    .map_err(|e| AppError::Internal(e.to_string()))?;
105                dto = serde_json::from_slice(&raw)
106                    .map_err(|e| AppError::business(format!("mangaBenefit 解析失败: {e}")))?;
107            }
108            _ => {}
109        }
110    }
111    Ok((file, dto))
112}