Skip to main content

pickcat_api_collection/dto/
user.rs

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