Skip to main content

pickcat_api_collection/dto/
user.rs

1//! 用户章节及跨章节共享的 DTO。
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6#[serde(rename_all = "camelCase")]
7pub struct GetUserInformationDTO {
8    pub id: String,
9    pub username: String,
10    pub avatar: UserAvatarDTO,
11    pub bio: Option<String>,
12    pub region: Option<String>,
13    pub show_following_list: bool,
14    pub show_followers_list: bool,
15    pub created_at: String,
16    pub level: UserLevelDTO,
17    pub stats: UserStatsDTO,
18    pub viewer_state: UserViewerStateDTO,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct UserAvatarDTO {
23    #[serde(rename = "type")]
24    pub avatar_type: String,
25    pub id: String,
26    pub url: String,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct UserLevelDTO {
31    pub current: u64,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct UserStatsDTO {
36    pub followers: u64,
37    pub following: u64,
38    pub topics: u64,
39    pub replies: u64,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43#[serde(rename_all = "camelCase")]
44pub struct UserViewerStateDTO {
45    pub following: bool,
46    pub can_follow: bool,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct GetUserEmailDTO {
52    pub email: String,
53    pub verified_at: String,
54}
55
56/// 主题/楼层列表项里的作者:只含 `id`/`username`/`avatar`/`displayedBadge`,
57/// 与完整的 `GetUserInformationDTO` 不同。
58#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(rename_all = "camelCase")]
60pub struct TopicAuthorDTO {
61    pub id: String,
62    pub username: String,
63    pub avatar: UserAvatarDTO,
64    pub displayed_badge: Option<DisplayedBadgeDTO>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct UserSubjectInfoDTO {
70    pub id: String,
71    pub title: String,
72    pub kind: String,
73    pub excerpt: String,
74    pub author: TopicAuthorDTO,
75    pub tags: Vec<UserSubjectTagDTO>,
76    pub reply_count: u64,
77    pub view_count: u64,
78    pub like_count: u64,
79    pub bookmark_count: u64,
80    /// 仅 `kind == "QUESTION"` 时存在,结构未文档化。
81    pub question_state: Option<serde_json::Value>,
82    pub closed_at: Option<String>,
83    pub pinned: bool,
84    pub pinned_globally: bool,
85    pub pinned_tag_id: Option<String>,
86    pub pinned_at: Option<String>,
87    pub pinned_until: Option<String>,
88    pub created_at: String,
89    pub edited_at: Option<String>,
90    pub last_activity_at: String,
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct UserSubjectTagDTO {
95    pub id: String,
96    pub slug: String,
97    pub name: String,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct UserSubjectsDTO {
103    pub items: Vec<UserSubjectInfoDTO>,
104    pub page_info: UserSubjectsPageInfoDTO,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct UserSubjectsPageInfoDTO {
110    pub has_next_page: bool,
111    pub next_cursor: Option<String>,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct UserRepliesDTO {
117    pub items: Vec<UserReplyDTO>,
118    pub page_info: UserRepliesPageInfoDTO,
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
122#[serde(rename_all = "camelCase")]
123pub struct UserReplyDTO {
124    pub id: String,
125    pub topic_id: String,
126    pub post_number: u64,
127    pub reply_to_post_number: Option<u64>,
128    pub deleted: bool,
129    pub children: Vec<UserReplyDTO>,
130    pub current_revision: u64,
131    pub like_count: u64,
132    pub pinned: bool,
133    pub cooked_html: String,
134    pub author: TopicAuthorDTO,
135    pub created_at: String,
136    pub edited_at: Option<String>,
137    pub viewer_capabilities: UserReplyViewerCapabilitiesDTO,
138    pub viewer_state: UserReplyViewerStateDTO,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct UserRepliesPageInfoDTO {
144    pub has_next_page: bool,
145    pub next_cursor: Option<String>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct UserReplyViewerCapabilitiesDTO {}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct UserReplyViewerStateDTO {}
153
154// ---------------------------------------------------------------------------
155// 通用游标分页
156// ---------------------------------------------------------------------------
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
159#[serde(rename_all = "camelCase")]
160pub struct PageInfoDTO {
161    pub has_next_page: bool,
162    pub next_cursor: Option<String>,
163}
164
165/// `{ "items": [...], "pageInfo": {...} }` 形式的通用游标分页响应。
166#[derive(Debug, Clone, Serialize, Deserialize)]
167#[serde(rename_all = "camelCase")]
168pub struct CursorListDTO<T> {
169    pub items: Vec<T>,
170    pub page_info: PageInfoDTO,
171}
172
173// ---------------------------------------------------------------------------
174// PATCH /users/{userId}
175// ---------------------------------------------------------------------------
176
177/// 只提交需要变更的字段;`None` 的字段不会出现在请求体里。
178#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct UpdateUserProfileDTO {
181    #[serde(skip_serializing_if = "Option::is_none")]
182    pub bio: Option<String>,
183}
184
185// ---------------------------------------------------------------------------
186// GET /users/{userId}/featured-topics
187// ---------------------------------------------------------------------------
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct UserFeaturedTopicsDTO {
191    pub items: Vec<UserSubjectInfoDTO>,
192}
193
194// ---------------------------------------------------------------------------
195// 徽章
196// ---------------------------------------------------------------------------
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
199#[serde(rename_all = "camelCase")]
200pub struct BadgeImageDTO {
201    pub file_id: String,
202    pub url: String,
203    pub width: u64,
204    pub height: u64,
205}
206
207/// `GET /users/{userId}/badges` 中的完整徽章详情。
208#[derive(Debug, Clone, Serialize, Deserialize)]
209#[serde(rename_all = "camelCase")]
210pub struct BadgeDTO {
211    pub id: String,
212    pub slug: String,
213    pub name: String,
214    pub description: String,
215    pub icon: String,
216    pub version: u64,
217    pub image: BadgeImageDTO,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
221#[serde(rename_all = "camelCase")]
222pub struct UserBadgeDTO {
223    pub user_id: String,
224    pub badge_id: String,
225    pub granted_at: String,
226    pub revision: String,
227    pub badge: BadgeDTO,
228}
229
230#[derive(Debug, Clone, Serialize, Deserialize)]
231pub struct UserBadgesDTO {
232    pub items: Vec<UserBadgeDTO>,
233}
234
235/// 批量/展示接口里返回的精简徽章对象。
236#[derive(Debug, Clone, Serialize, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct DisplayedBadgeDTO {
239    pub id: String,
240    pub name: String,
241    pub description: String,
242    pub image: BadgeImageDTO,
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
246#[serde(rename_all = "camelCase")]
247pub struct BadgeDisplayDTO {
248    pub mode: String,
249    pub badge_id: Option<String>,
250    pub displayed_badge: Option<DisplayedBadgeDTO>,
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
254#[serde(rename_all = "camelCase")]
255pub struct UserBadgeDisplayItemDTO {
256    pub user_id: String,
257    pub displayed_badge: Option<DisplayedBadgeDTO>,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct UserBadgeDisplaysDTO {
262    pub items: Vec<UserBadgeDisplayItemDTO>,
263}
264
265// ---------------------------------------------------------------------------
266// 用户动态
267// ---------------------------------------------------------------------------
268
269#[derive(Debug, Clone, Serialize, Deserialize)]
270#[serde(rename_all = "camelCase")]
271pub struct ProfileActivityDTO {
272    pub id: String,
273    /// `POSTED` / `LIKED` / `LEVEL_UP`
274    pub kind: String,
275    pub occurred_at: String,
276    pub title: Option<String>,
277    pub level: Option<u64>,
278}
279
280// ---------------------------------------------------------------------------
281// 贡献日历
282// ---------------------------------------------------------------------------
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
285#[serde(rename_all = "camelCase")]
286pub struct LevelContributionDayDTO {
287    pub date: String,
288    pub ability: u64,
289    pub responsibility: u64,
290    pub care: u64,
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize)]
294#[serde(rename_all = "camelCase")]
295pub struct LevelContributionsDTO {
296    pub year: u64,
297    pub time_zone: String,
298    pub days: Vec<LevelContributionDayDTO>,
299    pub updating: bool,
300    pub calculated_at: String,
301}
302
303// ---------------------------------------------------------------------------
304// 等级进度
305// ---------------------------------------------------------------------------
306
307#[derive(Debug, Clone, Serialize, Deserialize)]
308pub struct LevelScoresDTO {
309    pub ability: u64,
310    pub responsibility: u64,
311    pub care: u64,
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
315#[serde(rename_all = "camelCase")]
316pub struct LevelFamiliarityDTO {
317    pub tracking: bool,
318    pub familiarity_started_at: String,
319    pub days_since_entrance_exam: u64,
320    pub valid_visit_days: u64,
321    pub topics_entered: u64,
322    pub posts_read: u64,
323    pub effective_reading_seconds: u64,
324}
325
326#[derive(Debug, Clone, Serialize, Deserialize)]
327pub struct LevelRequirementDTO {
328    pub current: u64,
329    pub required: u64,
330    pub met: bool,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct NextLevelScoresDTO {
335    pub ability: LevelRequirementDTO,
336    pub responsibility: LevelRequirementDTO,
337    pub care: LevelRequirementDTO,
338}
339
340#[derive(Debug, Clone, Serialize, Deserialize)]
341#[serde(rename_all = "camelCase")]
342pub struct NextLevelFamiliarityDTO {
343    pub days_since_entrance_exam: LevelRequirementDTO,
344    pub valid_visit_days: LevelRequirementDTO,
345    pub topics_entered: LevelRequirementDTO,
346    pub posts_read: LevelRequirementDTO,
347    pub effective_reading_seconds: LevelRequirementDTO,
348}
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
351pub struct HardRequirementDTO {
352    pub key: String,
353    pub met: bool,
354}
355
356#[derive(Debug, Clone, Serialize, Deserialize)]
357#[serde(rename_all = "camelCase")]
358pub struct NextLevelDTO {
359    pub level: u64,
360    pub admission: String,
361    pub scores: NextLevelScoresDTO,
362    pub familiarity: NextLevelFamiliarityDTO,
363    pub hard_requirements: Vec<HardRequirementDTO>,
364    pub blocked_by_promotion_ceiling: bool,
365    pub eligible: bool,
366}
367
368#[derive(Debug, Clone, Serialize, Deserialize)]
369pub struct QuotaDTO {
370    pub action: String,
371    pub limit: u64,
372    pub used: u64,
373    pub remaining: u64,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
377#[serde(rename_all = "camelCase")]
378pub struct LevelProgressDTO {
379    pub current_level: u64,
380    /// 文档中观测为 `null`,类型未验证。
381    pub promotion_ceiling: Option<serde_json::Value>,
382    pub scores: LevelScoresDTO,
383    pub familiarity: LevelFamiliarityDTO,
384    pub next_level: Option<NextLevelDTO>,
385    pub lv4_candidate: bool,
386    pub quotas: Vec<QuotaDTO>,
387    pub updating: bool,
388    pub calculated_at: String,
389}
390
391// ---------------------------------------------------------------------------
392// 配额 / 上限
393// ---------------------------------------------------------------------------
394
395#[derive(Debug, Clone, Serialize, Deserialize)]
396#[serde(rename_all = "camelCase")]
397pub struct ContentLengthLimitDTO {
398    pub level: u64,
399    pub topic_max_length: u64,
400    pub reply_max_length: u64,
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize)]
404#[serde(rename_all = "camelCase")]
405pub struct TopicCollectionUsageDTO {
406    pub current_level: u64,
407    pub limit_count: u64,
408    pub used_count: u64,
409    pub remaining_count: u64,
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
413#[serde(rename_all = "camelCase")]
414pub struct FileStorageDTO {
415    pub used_bytes: u64,
416    pub limit_bytes: u64,
417    pub remaining_bytes: u64,
418}
419
420// ---------------------------------------------------------------------------
421// 预设头像
422// ---------------------------------------------------------------------------
423
424#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct AvatarPresetDTO {
426    #[serde(rename = "type")]
427    pub avatar_type: String,
428    pub id: String,
429    pub url: String,
430}
431
432#[derive(Debug, Clone, Serialize, Deserialize)]
433pub struct AvatarPresetsDTO {
434    pub items: Vec<AvatarPresetDTO>,
435}