Skip to main content

tdm_server_rust/web/
member_controller.rs

1//! 组员管理接口 (Member Controller)
2//!
3//! 组员 CRUD、岗位管理、密码重置、常驻申请。
4//! 对应 Java MemberController。
5
6use crate::utils::query_deserialize::{
7    de_opt_i16, de_opt_i32, de_opt_string, de_page, de_page_size,
8};
9use crate::{
10    app::AppState,
11    common::result::{json_bytes_response, ResultBody},
12    common::AppJson,
13    entity::member::{
14        MemberAddRequest, MemberEpisode, MemberInfoVo, MemberSelfUpdateRequest,
15        MemberUpdateRequest, ResetPassword,
16    },
17    error::{ApiResult, AppError},
18    middleware::AuthMember,
19    service::member_service::MemberService,
20};
21use axum::{
22    extract::{Extension, Path, Query, State},
23    response::Response,
24    routing::{get, patch, put},
25    Router,
26};
27use serde::Deserialize;
28
29/// 组员分页查询参数
30#[derive(Debug, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct MemberPageQuery {
33    /// 页码
34    #[serde(default, deserialize_with = "de_page")]
35    pub page: i32,
36    /// 每页条数
37    #[serde(default, deserialize_with = "de_page_size")]
38    pub page_size: i32,
39    /// 用户名
40    #[serde(default, deserialize_with = "de_opt_string")]
41    pub username: Option<String>,
42    /// 岗位
43    #[serde(default, deserialize_with = "de_opt_i16")]
44    pub post: Option<i16>,
45    /// 职阶
46    #[serde(default, deserialize_with = "de_opt_i16")]
47    pub intern: Option<i16>,
48    /// 邮箱
49    #[serde(default, deserialize_with = "de_opt_string")]
50    pub email: Option<String>,
51}
52
53/// 组员话数分页参数
54#[derive(Debug, Deserialize)]
55#[serde(rename_all = "camelCase")]
56pub struct MemberEpisodePageQuery {
57    /// 页码
58    #[serde(default, deserialize_with = "de_page")]
59    pub page: i32,
60    /// 每页条数
61    #[serde(default, deserialize_with = "de_page_size")]
62    pub page_size: i32,
63    /// 组员 ID
64    #[serde(default, deserialize_with = "de_opt_i32")]
65    pub id: Option<i32>,
66}
67
68/// 工作台任务分页参数
69#[derive(Debug, Deserialize)]
70#[serde(rename_all = "camelCase")]
71pub struct MemberWorkbenchTasksQuery {
72    /// 页码
73    #[serde(default, deserialize_with = "de_page")]
74    pub page: i32,
75    /// 每页条数
76    #[serde(default, deserialize_with = "de_page_size")]
77    pub page_size: i32,
78    /// Tab:all / translator / proofreader / letterer / timer / reviewer
79    #[serde(default, deserialize_with = "de_opt_string")]
80    pub tab: Option<String>,
81    /// View:all / plan / doing
82    #[serde(default, deserialize_with = "de_opt_string")]
83    pub view: Option<String>,
84    /// 关键词(漫画名 / 话序 / 单话名)
85    #[serde(default, deserialize_with = "de_opt_string")]
86    pub keyword: Option<String>,
87}
88
89/// 组员路由(挂载于 `/api/members`)
90pub fn routes() -> Router<AppState> {
91    Router::new()
92        .route("/", get(page_member).post(new_member).put(update_member))
93        .route("/all", get(get_member_list))
94        .route("/self", put(update_member_self))
95        .route("/updatepwd", patch(update_member_password))
96        .route("/memberepisode", get(page_episode))
97        .route("/workbench/:id/overview", get(workbench_overview))
98        .route("/workbench/:id/tasks", get(workbench_tasks))
99        .route("/takeEpisode", patch(take_episode))
100        .route("/submitEpisode", patch(submit_episode))
101        .route("/stationedMangas/:id", get(find_stationed_mangas))
102        .route("/:id", get(get_member_by_id).delete(delete_member))
103}
104
105/// 分页查询组员
106#[tracing::instrument(skip_all, level = "info")]
107pub async fn page_member(
108    State(state): State<AppState>,
109    Query(q): Query<MemberPageQuery>,
110) -> ApiResult<Response> {
111    let json = MemberService::page_json(
112        &state,
113        q.page,
114        q.page_size,
115        q.username,
116        q.post,
117        q.intern,
118        q.email,
119    )
120    .await?;
121    Ok(json_bytes_response(json))
122}
123
124/// 全量组员列表(缓存命中时直出预序列化 JSON)
125#[tracing::instrument(skip_all, level = "info")]
126pub async fn get_member_list(State(state): State<AppState>) -> ApiResult<Response> {
127    let json = MemberService::get_member_list_json(&state).await?;
128    Ok(json_bytes_response(json))
129}
130
131/// 批量删除组员
132#[tracing::instrument(skip_all, level = "info")]
133pub async fn delete_member(
134    State(state): State<AppState>,
135    Path(id): Path<String>,
136) -> ApiResult<ResultBody<()>> {
137    let id_list: Vec<i32> = id
138        .split(',')
139        .filter_map(|s| s.trim().parse().ok())
140        .collect();
141    MemberService::delete(&state, id_list).await?;
142    Ok(ResultBody::success())
143}
144
145/// 新增组员
146#[tracing::instrument(skip_all, level = "info")]
147pub async fn new_member(
148    State(state): State<AppState>,
149    AppJson(body): AppJson<MemberAddRequest>,
150) -> ApiResult<ResultBody<()>> {
151    MemberService::new_member(&state, body).await?;
152    Ok(ResultBody::success())
153}
154
155/// 按 ID 查询组员
156#[tracing::instrument(skip_all, level = "info")]
157pub async fn get_member_by_id(
158    State(state): State<AppState>,
159    Path(id): Path<String>,
160) -> ApiResult<ResultBody<MemberInfoVo>> {
161    let id: i32 = id
162        .parse()
163        .map_err(|_| crate::error::AppError::business("无效的组员 ID 喵"))?;
164    let m = MemberService::get_member_by_id(&state, id).await?;
165    Ok(ResultBody::success_data(MemberInfoVo::from(m)))
166}
167
168/// 更新组员
169#[tracing::instrument(skip_all, level = "info")]
170pub async fn update_member(
171    State(state): State<AppState>,
172    AppJson(body): AppJson<MemberUpdateRequest>,
173) -> ApiResult<ResultBody<()>> {
174    MemberService::update_member(&state, body).await?;
175    Ok(ResultBody::success())
176}
177
178/// 组员自助更新
179#[tracing::instrument(skip_all, level = "info")]
180pub async fn update_member_self(
181    State(state): State<AppState>,
182    AppJson(body): AppJson<MemberSelfUpdateRequest>,
183) -> ApiResult<ResultBody<()>> {
184    MemberService::update_member_self(&state, body).await?;
185    Ok(ResultBody::success())
186}
187
188/// 查询常驻漫画
189#[tracing::instrument(skip_all, level = "info")]
190pub async fn find_stationed_mangas(
191    State(state): State<AppState>,
192    Path(id): Path<String>,
193) -> ApiResult<Response> {
194    let id: i32 = id
195        .parse()
196        .map_err(|_| AppError::business("无效的组员 ID 喵"))?;
197    let json = MemberService::find_stationed_mangas_json(&state, id).await?;
198    Ok(json_bytes_response(json))
199}
200
201/// 修改密码
202#[tracing::instrument(skip_all, level = "info")]
203pub async fn update_member_password(
204    State(state): State<AppState>,
205    AppJson(body): AppJson<ResetPassword>,
206) -> ApiResult<ResultBody<()>> {
207    MemberService::update_member_password(&state, body).await?;
208    Ok(ResultBody::success())
209}
210
211/// 分页查询组员话数(缓存命中时直出预序列化 JSON)
212#[tracing::instrument(skip_all, level = "info")]
213pub async fn page_episode(
214    State(state): State<AppState>,
215    Query(q): Query<MemberEpisodePageQuery>,
216) -> ApiResult<Response> {
217    let id = q.id.ok_or_else(|| AppError::business("无效的组员 ID 喵"))?;
218    let json = MemberService::page_episode_json(&state, q.page, q.page_size, id).await?;
219    Ok(json_bytes_response(json))
220}
221
222/// 工作台总览(统计卡 + Tab 计数)
223#[tracing::instrument(skip_all, level = "info")]
224pub async fn workbench_overview(
225    State(state): State<AppState>,
226    Path(id): Path<String>,
227) -> ApiResult<Response> {
228    let id: i32 = id
229        .parse()
230        .map_err(|_| AppError::business("无效的组员 ID 喵"))?;
231    let json = MemberService::get_workbench_overview_json(&state, id).await?;
232    Ok(json_bytes_response(json))
233}
234
235/// 工作台在做任务分页
236#[tracing::instrument(skip_all, level = "info")]
237pub async fn workbench_tasks(
238    State(state): State<AppState>,
239    Path(id): Path<String>,
240    Query(q): Query<MemberWorkbenchTasksQuery>,
241) -> ApiResult<Response> {
242    let id: i32 = id
243        .parse()
244        .map_err(|_| AppError::business("无效的组员 ID 喵"))?;
245    let json = MemberService::page_workbench_tasks_json(
246        &state,
247        id,
248        q.tab.as_deref(),
249        q.view.as_deref(),
250        q.keyword.as_deref(),
251        q.page,
252        q.page_size,
253    )
254    .await?;
255    Ok(json_bytes_response(json))
256}
257
258/// 接稿
259#[tracing::instrument(skip_all, level = "info")]
260pub async fn take_episode(
261    State(state): State<AppState>,
262    Extension(AuthMember(member)): Extension<AuthMember>,
263    AppJson(body): AppJson<MemberEpisode>,
264) -> ApiResult<ResultBody<()>> {
265    MemberService::take_episode(&state, body, member).await?;
266    Ok(ResultBody::success())
267}
268
269/// 交稿
270#[tracing::instrument(skip_all, level = "info")]
271pub async fn submit_episode(
272    State(state): State<AppState>,
273    Extension(AuthMember(_member)): Extension<AuthMember>,
274    AppJson(body): AppJson<MemberEpisode>,
275) -> ApiResult<ResultBody<()>> {
276    MemberService::submit_episode(&state, body).await?;
277    Ok(ResultBody::success())
278}