Skip to main content

tdm_server_rust/entity/
member.rs

1//! 组员相关实体 (Member Entities)
2//!
3//! 定义组员管理的核心数据结构:
4//! - 组员信息 ([`Member`])、列表 VO ([`MemberListVo`])、详情 VO ([`MemberInfoVo`])
5//! - 登录/注册/密码重置请求体
6//! - 岗位 ([`Post`]) 与常驻漫画 ([`StationedManga`])
7//! - 话数接稿/交稿记录 ([`MemberEpisode`], [`MemberEpisodeVo`])
8
9use chrono::{DateTime, Utc};
10use serde::{Deserialize, Serialize};
11
12/// 岗位 (Post)
13///
14/// 表示组员的职责角色,如翻译、校对、嵌字等。
15/// 对应 Java `Post` 类。
16///
17/// ## 序列化
18///
19/// JSON 格式: `{"post": 1}`,其中 `1` 对应 `PostEnum` 常量值。
20#[derive(Debug, Clone, Serialize, Deserialize, Default)]
21pub struct Post {
22    /// 岗位 ID
23    pub post: i32,
24}
25
26/// 组员 (Member)
27///
28/// 核心组员实体,映射 `t_member` 表。
29/// 同时用于数据库读写和部分 API 响应。
30///
31/// ## 字段说明
32///
33/// 部分字段(如 `password`、`station_id`)在序列化时会根据上下文跳过,
34/// 避免敏感信息泄露或减少冗余。
35///
36/// ## 与 Java 的映射
37///
38/// 对应 Java `Member` 类,字段名采用 camelCase。
39/// `#[serde(alias = "Id")]` 兼容 Java 端大写 `Id` 字段。
40#[derive(Debug, Clone, Serialize, Deserialize, Default)]
41#[serde(rename_all = "camelCase")]
42pub struct Member {
43    /// 组员 ID
44    #[serde(alias = "Id")]
45    pub id: i32,
46    /// 用户名
47    pub username: Option<String>,
48    /// 密码
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub password: Option<String>,
51    /// 职阶
52    pub intern: i16,
53    /// 邮箱
54    pub email: Option<String>,
55    /// 头像对象键或完整 URL
56    pub avatar_url: Option<String>,
57    /// 注册时间
58    pub registration_time: Option<DateTime<Utc>>,
59    /// 最近交稿时间
60    pub last_submit_time: Option<DateTime<Utc>>,
61    /// 岗位列表
62    #[serde(default)]
63    pub posts: Vec<Post>,
64    /// 岗位 ID 列表
65    #[serde(default)]
66    pub post_ids: Vec<i32>,
67    /// 常驻计数
68    #[serde(default)]
69    pub station_count: Option<i32>,
70    /// 常驻记录 ID
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub station_id: Option<i32>,
73    /// 常驻岗位
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub post: Option<i32>,
76    /// 常驻状态
77    #[serde(skip_serializing_if = "Option::is_none")]
78    pub status: Option<i16>,
79    /// 漫画 ID(常驻审批列表)
80    #[serde(skip_serializing_if = "Option::is_none")]
81    pub manga_id: Option<i32>,
82    /// 漫画译名(常驻审批列表)
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub manga_name: Option<String>,
85}
86
87impl Member {
88    /// 是否拥有可用的审稿管理权限。
89    ///
90    /// 退休成员即使因历史数据残留审稿岗位,也不得继续获得管理权限。
91    pub fn has_reviewer_permission(&self) -> bool {
92        self.intern != crate::entity::enums::MemberInternEnum::RETIRED
93            && self
94                .post_ids
95                .contains(&crate::entity::enums::PostEnum::REVIEWER)
96    }
97}
98
99/// 组员列表 VO(对齐 Java MemberListVO)
100#[derive(Debug, Clone, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct MemberListVo {
103    /// 用户名
104    pub username: Option<String>,
105    /// 组员 ID
106    pub id: i32,
107    /// 岗位列表
108    pub posts: Vec<Post>,
109    /// 职阶
110    pub intern: i16,
111    /// 最近交稿时间
112    pub last_submit_time: Option<DateTime<Utc>>,
113    /// 注册时间
114    pub registration_time: Option<DateTime<Utc>>,
115    /// 邮箱
116    pub email: Option<String>,
117    /// 头像对象键或完整 URL
118    pub avatar_url: Option<String>,
119    /// 常驻计数
120    pub station_count: Option<i32>,
121}
122
123/// 组员详情 VO(对齐 Java MemberInfoVO)
124#[derive(Debug, Clone, Serialize, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub struct MemberInfoVo {
127    /// 组员 ID
128    pub id: i32,
129    /// 用户名
130    pub username: Option<String>,
131    /// 岗位列表
132    pub posts: Vec<Post>,
133    /// 岗位 ID 列表
134    pub post_ids: Vec<i32>,
135    /// 职阶
136    pub intern: i16,
137    /// 最近交稿时间
138    pub last_submit_time: Option<DateTime<Utc>>,
139    /// 注册时间
140    pub registration_time: Option<DateTime<Utc>>,
141    /// 邮箱
142    pub email: Option<String>,
143    /// 头像对象键或完整 URL
144    pub avatar_url: Option<String>,
145}
146
147/// 组员缓存
148#[derive(Debug, Clone, Serialize, Deserialize)]
149#[serde(rename_all = "camelCase")]
150pub struct MemberCache {
151    /// 组员 ID
152    #[serde(alias = "Id")]
153    pub id: i32,
154    /// 用户名
155    pub username: Option<String>,
156    /// 职阶
157    pub intern: i16,
158    /// 邮箱
159    pub email: Option<String>,
160    /// 头像对象键或完整 URL
161    pub avatar_url: Option<String>,
162    /// 岗位 ID 列表
163    pub posts: Vec<i32>,
164}
165
166/// 登录请求体 (Login Request)
167///
168/// 对应 `POST /api/login` 的 JSON body:
169///
170/// ```json
171/// {"username": "gum979", "password": "123456"}
172/// ```
173#[derive(Debug, Deserialize)]
174pub struct MemberLoginRequest {
175    /// 用户名或 QQ 邮箱
176    pub username: String,
177    /// 密码
178    pub password: String,
179}
180
181/// 注册请求
182#[derive(Debug, Deserialize)]
183pub struct Reg {
184    /// ID
185    pub id: Option<i32>,
186    /// 用户名
187    pub username: String,
188    /// 邮箱
189    pub email: String,
190    /// 密码
191    pub password: String,
192    /// 邀请码
193    pub invitationcode: i32,
194}
195
196/// 邀请码
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct InvitationCode {
199    /// ID
200    pub id: Option<i32>,
201    /// 邀请码
202    pub code: i32,
203}
204
205/// 重置密码
206#[derive(Debug, Deserialize)]
207pub struct ResetPassword {
208    /// 组员 ID
209    pub id: i32,
210    /// 旧密码
211    pub old_pwd: String,
212    /// 新密码
213    pub new_pwd: String,
214}
215
216/// 组员接稿/交稿记录 (Member-Episode Assignment)
217///
218/// 记录组员对某一话的岗位分配及时间线:
219/// - `my_setup_time` — 接稿(认领)时间
220/// - `my_update_time` — 交稿(完成)时间
221///
222/// `my_name` 为岗位标识名,如 `"translator"`、`"proofreader"` 等。
223#[derive(Debug, Clone, Deserialize, Serialize)]
224#[serde(rename_all = "camelCase")]
225pub struct MemberEpisode {
226    /// 组员 ID
227    pub member_id: i32,
228    /// 话数 ID
229    pub episode_id: i32,
230    /// 岗位名 translator/proofreader 等
231    pub my_name: String,
232    /// 接稿时间
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub my_setup_time: Option<DateTime<Utc>>,
235    /// 交稿时间
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub my_update_time: Option<DateTime<Utc>>,
238}
239
240/// 新增组员请求
241#[derive(Debug, Deserialize)]
242#[serde(rename_all = "camelCase")]
243pub struct MemberAddRequest {
244    /// 用户名
245    pub username: String,
246    /// 密码
247    pub password: String,
248    /// 邮箱
249    pub email: String,
250    /// 是否在实习期,1是,0否
251    pub intern: i16,
252    /// 岗位 ID 列表
253    pub post_ids: Vec<i32>,
254}
255
256/// 更新组员请求(对齐 Java MemberUpdateRequest)
257#[derive(Debug, Deserialize)]
258#[serde(rename_all = "camelCase")]
259pub struct MemberUpdateRequest {
260    /// 组员 ID
261    pub id: i32,
262    /// 用户名
263    pub username: Option<String>,
264    /// 密码
265    pub password: Option<String>,
266    /// 邮箱
267    pub email: Option<String>,
268    /// 头像对象键或完整 URL
269    pub avatar_url: Option<String>,
270    /// 职阶
271    pub intern: Option<i16>,
272    /// 岗位 ID 列表
273    pub post_ids: Option<Vec<i32>>,
274    /// 岗位字符串列表
275    pub string_posts: Option<Vec<String>>,
276}
277
278/// 组员自助更新(对齐 Java MemberSelfUpdateRequest)
279#[derive(Debug, Deserialize)]
280#[serde(rename_all = "camelCase")]
281pub struct MemberSelfUpdateRequest {
282    /// 组员 ID
283    pub id: i32,
284    /// 用户名
285    pub username: Option<String>,
286    /// 邮箱
287    pub email: Option<String>,
288    /// 头像对象键或完整 URL
289    pub avatar_url: Option<String>,
290}
291
292/// 组员话数视图 (Member Episode View Object)
293///
294/// 用于展示组员参与的所有话数及在各岗位上的进度。
295/// 包含每个岗位的负责人、接稿时间、交稿时间和文件信息。
296///
297/// 对应 Java `MemberEpisodeVO`。
298#[derive(Debug, Clone, Serialize, Deserialize, Default)]
299#[serde(rename_all = "camelCase")]
300pub struct MemberEpisodeVo {
301    /// 话数 ID
302    pub id: i32,
303    /// 漫画 ID
304    pub manga_id: i32,
305    /// 漫画译名
306    pub manga_name: Option<String>,
307    /// 话数标签
308    pub manga_episode: Option<String>,
309    /// 话数名
310    pub manga_episode_name: Option<String>,
311    /// 设立时间
312    pub setup_time: Option<DateTime<Utc>>,
313    /// 更新时间
314    pub update_time: Option<DateTime<Utc>>,
315    /// 图源 ID
316    pub provider_id: Option<i32>,
317    /// 图源组员名
318    pub provider_name: Option<String>,
319    /// 图源接稿时间
320    pub provider_setup_time: Option<DateTime<Utc>>,
321    /// 图源交稿时间
322    pub provider_update_time: Option<DateTime<Utc>>,
323    /// 翻译 ID
324    pub translator_id: Option<i32>,
325    /// 翻译组员名
326    pub translator_name: Option<String>,
327    /// 翻译接稿时间
328    pub translator_setup_time: Option<DateTime<Utc>>,
329    /// 翻译交稿时间
330    pub translator_update_time: Option<DateTime<Utc>>,
331    /// 校对 ID
332    pub proofreader_id: Option<i32>,
333    /// 校对组员名
334    pub proofreader_name: Option<String>,
335    /// 校对接稿时间
336    pub proofreader_setup_time: Option<DateTime<Utc>>,
337    /// 校对交稿时间
338    pub proofreader_update_time: Option<DateTime<Utc>>,
339    /// 嵌字 ID
340    pub letterer_id: Option<i32>,
341    /// 嵌字组员名
342    pub letterer_name: Option<String>,
343    /// 嵌字接稿时间
344    pub letterer_setup_time: Option<DateTime<Utc>>,
345    /// 嵌字交稿时间
346    pub letterer_update_time: Option<DateTime<Utc>>,
347    /// 时轴 ID
348    pub timer_id: Option<i32>,
349    /// 时轴组员名
350    pub timer_name: Option<String>,
351    /// 时轴接稿时间
352    pub timer_setup_time: Option<DateTime<Utc>>,
353    /// 时轴交稿时间
354    pub timer_update_time: Option<DateTime<Utc>>,
355    /// 审稿 ID
356    pub reviewer_id: Option<i32>,
357    /// 审稿组员名
358    pub reviewer_name: Option<String>,
359    /// 审稿接稿时间
360    pub reviewer_setup_time: Option<DateTime<Utc>>,
361    /// 审稿交稿时间
362    pub reviewer_update_time: Option<DateTime<Utc>>,
363    /// 图源 OSS ID
364    pub provider_file_oss_id: Option<i32>,
365    /// 图源文件名
366    pub provider_filename: Option<String>,
367    /// 翻译稿 OSS ID
368    pub translator_file_oss_id: Option<i32>,
369    /// 翻译稿文件名
370    pub translator_filename: Option<String>,
371    /// 校对稿 OSS ID
372    pub proofreader_file_oss_id: Option<i32>,
373    /// 校对稿文件名
374    pub proofreader_filename: Option<String>,
375    /// 嵌字稿 OSS ID
376    pub letterer_file_oss_id: Option<i32>,
377    /// 嵌字稿文件名
378    pub letterer_filename: Option<String>,
379    /// 时轴 OSS ID
380    pub timer_file_oss_id: Option<i32>,
381    /// 时轴文件名
382    pub timer_filename: Option<String>,
383    /// 翻译文件
384    pub translator_file: Option<String>,
385    /// 校对文件
386    pub proofreader_file: Option<String>,
387    /// 时轴文件
388    pub timer_file: Option<String>,
389    /// 发布链接,非空表示该话已发布
390    pub publish_link: Option<String>,
391    /// 工作台列表排序/展示用:当前组员在做岗位中最新的接稿时间
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub workbench_time: Option<DateTime<Utc>>,
394}
395
396/// 工作台漫画任务分组。
397#[derive(Debug, Clone, Serialize, Deserialize, Default)]
398#[serde(rename_all = "camelCase")]
399pub struct MemberWorkbenchTaskGroup {
400    /// 漫画 ID
401    pub manga_id: i32,
402    /// 漫画名
403    pub manga_name: String,
404    /// 当前筛选条件下该漫画的全部任务
405    pub tasks: Vec<MemberEpisodeVo>,
406}
407
408/// 工作台岗位计数
409#[derive(Debug, Clone, Serialize, Deserialize, Default)]
410#[serde(rename_all = "camelCase")]
411pub struct MemberWorkbenchRoleCounts {
412    /// 翻译在做话数
413    pub translator: i32,
414    /// 校对在做话数
415    pub proofreader: i32,
416    /// 嵌字在做话数
417    pub letterer: i32,
418    /// 时轴在做话数
419    pub timer: i32,
420    /// 审稿在做话数
421    pub reviewer: i32,
422}
423
424/// 工作台总览(统计卡 + Tab 计数,无需拉全量话数)
425#[derive(Debug, Clone, Serialize, Deserialize, Default)]
426#[serde(rename_all = "camelCase")]
427pub struct MemberWorkbenchOverview {
428    /// 待交稿:本人已接稿、未交稿且上游已完成
429    pub pending: i32,
430    /// 进行中:任一岗位已接稿(含已交)
431    pub working: i32,
432    /// 超期:在做且接稿超过 60 天
433    pub overdue: i32,
434    /// 可下载:在做且存在上级稿件
435    pub downloadable: i32,
436    /// 已完成:任一岗位已交稿
437    pub completed: i32,
438    /// 常驻漫画数
439    pub stationed: i32,
440    /// 「全部」Tab 就绪在做话数
441    pub all_count: i32,
442    /// 计划视图话数:本人已接稿但上游未完成
443    pub plan_count: i32,
444    /// 进行视图话数:本人已接稿且上游已完成
445    pub doing_count: i32,
446    /// 进行视图各岗位在做计数
447    pub role_counts: MemberWorkbenchRoleCounts,
448    /// 计划视图各岗位在做计数
449    pub plan_role_counts: MemberWorkbenchRoleCounts,
450}
451
452/// 常驻漫画(对齐 Java StationedManga extends Manga)
453#[derive(Debug, Clone, Serialize, Deserialize)]
454#[serde(rename_all = "camelCase")]
455pub struct StationedManga {
456    /// 漫画信息(扁平序列化,对齐 Java Manga 字段)
457    #[serde(flatten)]
458    pub manga: crate::entity::manga::MangaResponse,
459    /// 常驻岗位 ID 列表
460    pub posts: Vec<i32>,
461}
462
463impl From<Member> for MemberListVo {
464    fn from(m: Member) -> Self {
465        Self {
466            username: m.username,
467            id: m.id,
468            posts: m.posts,
469            intern: m.intern,
470            last_submit_time: m.last_submit_time,
471            registration_time: m.registration_time,
472            email: m.email,
473            avatar_url: m.avatar_url,
474            station_count: m.station_count,
475        }
476    }
477}
478
479impl From<Member> for MemberInfoVo {
480    fn from(m: Member) -> Self {
481        Self {
482            id: m.id,
483            username: m.username,
484            posts: m.posts,
485            post_ids: m.post_ids,
486            intern: m.intern,
487            last_submit_time: m.last_submit_time,
488            registration_time: m.registration_time,
489            email: m.email,
490            avatar_url: m.avatar_url,
491        }
492    }
493}