Skip to main content

cloudreve_api/api/v3/
models.rs

1//! Common data models for the Cloudreve API v3
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6/// API response wrapper for v3 API
7#[derive(Debug, Serialize, Deserialize)]
8pub struct ApiResponse<T> {
9    pub code: i32,
10    pub msg: String,
11    pub data: Option<T>,
12}
13
14/// User information for v3 API
15#[derive(Debug, Serialize, Deserialize, Clone)]
16pub struct User {
17    pub id: String,
18    pub user_name: String,
19    pub nickname: String,
20    #[serde(default)]
21    pub status: i32,
22    #[serde(default)]
23    pub avatar: String,
24    pub created_at: String,
25    #[serde(default)]
26    pub preferred_theme: String,
27    #[serde(default)]
28    pub anonymous: bool,
29    pub group: UserGroup,
30    #[serde(default)]
31    pub tags: Vec<String>,
32}
33
34/// User group information for v3 API
35#[derive(Debug, Serialize, Deserialize, Clone)]
36pub struct UserGroup {
37    #[serde(default)]
38    pub id: i32,
39    #[serde(default)]
40    pub name: String,
41    #[serde(default)]
42    pub allow_share: bool,
43    #[serde(default)]
44    pub allow_remote_download: bool,
45    #[serde(default)]
46    pub allow_archive_download: bool,
47    #[serde(default)]
48    pub share_download: bool,
49    #[serde(default)]
50    pub compress: bool,
51    #[serde(default)]
52    pub webdav: bool,
53    #[serde(default)]
54    pub source_batch: i32,
55    #[serde(default)]
56    pub advance_delete: bool,
57    #[serde(default)]
58    pub allow_web_dav_proxy: bool,
59}
60
61/// File or folder object for v3 API
62#[derive(Debug, Serialize, Deserialize, Clone)]
63pub struct Object {
64    pub id: String,
65    pub name: String,
66    pub path: String,
67    pub thumb: bool,
68    pub size: i64,
69    #[serde(rename = "type")]
70    pub object_type: String,
71    pub date: String,
72    pub create_date: String,
73    pub source_enabled: bool,
74}
75
76/// Storage policy information for v3 API
77#[derive(Debug, Serialize, Deserialize, Clone, Default)]
78pub struct Policy {
79    #[serde(default)]
80    pub id: String,
81    #[serde(default)]
82    pub name: String,
83    #[serde(rename = "type")]
84    #[serde(default)]
85    pub policy_type: String,
86    #[serde(default)]
87    pub max_size: i64,
88    #[serde(default)]
89    pub file_type: Option<Vec<String>>,
90}
91
92/// File or folder property for v3 API
93#[derive(Debug, Serialize, Deserialize)]
94pub struct Property {
95    pub created_at: String,
96    pub updated_at: String,
97    pub policy: String,
98    pub size: i64,
99    pub child_folder_num: i32,
100    pub child_file_num: i32,
101    pub path: String,
102    pub query_date: String,
103}
104
105/// Directory listing response for v3 API
106#[derive(Debug, Serialize, Deserialize)]
107pub struct DirectoryList {
108    #[serde(default)]
109    pub parent: String,
110    #[serde(default)]
111    pub objects: Vec<Object>,
112    #[serde(default)]
113    pub policy: Policy,
114}
115
116/// Upload session information for v3 API
117#[derive(Debug, Serialize, Deserialize)]
118pub struct UploadSession {
119    #[serde(rename = "sessionID")]
120    pub session_id: String,
121    #[serde(rename = "chunkSize")]
122    pub chunk_size: i64,
123    pub expires: i64,
124}
125
126/// Upload file request for v3 API
127#[derive(Debug, Serialize)]
128pub struct UploadFileRequest<'a> {
129    pub path: &'a str,
130    pub size: i64,
131    pub name: &'a str,
132    pub policy_id: &'a str,
133    /// 原文件修改时间,**Unix 毫秒**(服务端走 time.UnixMilli)。0 表示不设置,
134    /// 由服务端用上传时刻。
135    pub last_modified: i64,
136    pub mime_type: &'a str,
137}
138
139/// Download file response for v3 API
140#[derive(Debug, Serialize, Deserialize)]
141pub struct DownloadUrl {
142    pub url: String,
143}
144
145/// File source information for v3 API
146#[derive(Debug, Deserialize)]
147pub struct FileSource {
148    pub url: String,
149    pub name: String,
150    pub parent: i64,
151}
152
153/// `/user/setting` 的返回:用户偏好,**没有容量字段**。
154///
155/// 别和 `/user/storage` 的 [`StorageInfo`] 混了——早先 `get_user_settings()` 就是
156/// 拿 StorageInfo 去解 `/user/setting`,每次调用都稳定报 missing field `used`。
157#[derive(Debug, Serialize, Deserialize, Default)]
158#[serde(default)]
159pub struct UserSetting {
160    pub uid: i64,
161    pub homepage: bool,
162    pub two_factor: bool,
163    pub prefer_theme: String,
164    pub themes: String,
165    pub authn: Vec<WebAuthnCredential>,
166}
167
168/// `/user/setting` 里 `authn` 数组的元素(服务端 serializer.WebAuthnCredentials)。
169#[derive(Debug, Serialize, Deserialize, Default)]
170#[serde(default)]
171pub struct WebAuthnCredential {
172    /// 服务端是 []byte,JSON 里表现为 base64 字符串。
173    pub id: String,
174    pub fingerprint: String,
175}
176
177/// Storage information for v3 API
178#[derive(Debug, Serialize, Deserialize)]
179pub struct StorageInfo {
180    pub used: i64,
181    pub free: i64,
182    pub total: i64,
183}
184
185/// Share link information for v3 API
186#[derive(Debug, Deserialize, Default)]
187pub struct Share {
188    #[serde(default)]
189    pub key: String,
190    #[serde(default)]
191    pub password: String,
192    #[serde(default)]
193    pub is_dir: bool,
194    #[serde(default)]
195    pub downloads: i64,
196    #[serde(default)]
197    pub remain_downloads: i64,
198    #[serde(default)]
199    pub expire: i64,
200    #[serde(default)]
201    pub preview: bool,
202    #[serde(default)]
203    pub owner_id: String,
204    #[serde(default)]
205    pub owner_nick: String,
206    #[serde(default)]
207    pub created_at: String,
208}
209
210/// Share request for v3 API
211#[derive(Debug, Serialize)]
212pub struct ShareRequest {
213    pub id: String,
214    pub is_dir: bool,
215    pub password: String,
216    pub downloads: i32,
217    pub expire: i32,
218    pub preview: bool,
219}
220
221/// Site configuration for v3 API
222#[derive(Debug, Clone, Serialize, Deserialize, Default)]
223pub struct SiteConfig {
224    #[serde(default)]
225    pub title: String,
226    #[serde(default)]
227    pub login_captcha: bool,
228    #[serde(default)]
229    pub reg_captcha: bool,
230    #[serde(default)]
231    pub forget_captcha: bool,
232    #[serde(default)]
233    pub email_active: bool,
234    #[serde(default)]
235    pub themes: String,
236    #[serde(default)]
237    pub default_theme: String,
238    #[serde(default)]
239    pub home_view_method: String,
240    #[serde(default)]
241    pub share_view_method: String,
242    #[serde(default)]
243    pub authn: bool,
244    #[serde(default)]
245    pub user: Option<User>,
246    #[serde(default)]
247    pub captcha_recaptcha_key: String,
248    #[serde(default)]
249    pub captcha_type: String,
250    #[serde(default)]
251    pub tcaptcha_captcha_app_id: String,
252    #[serde(default)]
253    pub register_enabled: bool,
254    #[serde(default)]
255    pub app_promotion: bool,
256    #[serde(default)]
257    pub wopi_exts: Option<Value>,
258    #[serde(default)]
259    pub direct_link_batch_size: i64,
260}
261
262/// Login request for v3 API
263#[derive(Debug, Serialize)]
264pub struct LoginRequest<'a> {
265    #[serde(rename = "userName")]
266    pub user_name: &'a str,
267    #[serde(rename = "Password")]
268    pub password: &'a str,
269    #[serde(rename = "captchaCode")]
270    pub captcha_code: Option<&'a str>,
271}
272
273/// CAPTCHA response for v3 API
274#[derive(Debug, Deserialize)]
275pub struct CaptchaResponse {
276    pub image: String,
277    pub ticket: String,
278}
279
280/// OTP login request for v3 API
281#[derive(Debug, Serialize)]
282pub struct OtpLoginRequest {
283    pub code: String,
284}
285
286/// Create directory request for v3 API
287#[derive(Debug, Serialize)]
288pub struct CreateDirectoryRequest<'a> {
289    pub path: &'a str,
290}
291
292/// Create file request for v3 API
293#[derive(Debug, Serialize)]
294pub struct CreateFileRequest<'a> {
295    pub path: &'a str,
296}
297
298/// File source request for v3 API
299#[derive(Debug, Serialize)]
300pub struct FileSourceRequest {
301    pub items: Vec<String>,
302}
303
304/// Rename object request for v3 API
305#[derive(Debug, Serialize)]
306pub struct RenameObjectRequest<'a> {
307    pub action: &'a str,
308    pub src: SourceItems<'a>,
309    pub new_name: &'a str,
310}
311
312/// Source items for object operations
313#[derive(Debug, Serialize)]
314pub struct SourceItems<'a> {
315    pub dirs: Vec<&'a str>,
316    pub items: Vec<&'a str>,
317}
318
319/// Move object request for v3 API
320#[derive(Debug, Serialize)]
321pub struct MoveObjectRequest<'a> {
322    pub action: &'a str,
323    pub src_dir: &'a str,
324    pub src: SourceItems<'a>,
325    pub dst: &'a str,
326}
327
328/// Copy object request for v3 API
329#[derive(Debug, Serialize)]
330pub struct CopyObjectRequest<'a> {
331    pub src_dir: &'a str,
332    pub src: SourceItems<'a>,
333    pub dst: &'a str,
334}
335
336/// Delete object request for v3 API
337#[derive(Debug, Serialize)]
338pub struct DeleteObjectRequest<'a> {
339    pub items: Vec<&'a str>,
340    pub dirs: Vec<&'a str>,
341    pub force: bool,
342    pub unlink: bool,
343}
344
345/// Object property request for v3 API
346pub struct ObjectPropertyRequest<'a> {
347    pub id: &'a str,
348    pub is_folder: Option<bool>,
349    pub trace_root: Option<bool>,
350}
351
352/// Aria2 download progress info as reported by aria2c JSON-RPC, surfaced verbatim by
353/// Cloudreve v3 inside the `info` field of `/aria2/downloading` items. All fields are
354/// strings because aria2 itself emits them that way; consumers typically parse on demand.
355#[derive(Debug, Serialize, Deserialize, Clone, Default)]
356pub struct Aria2DownloadInfo {
357    #[serde(default)]
358    pub gid: String,
359    #[serde(default)]
360    pub status: String,
361    #[serde(default, rename = "totalLength")]
362    pub total_length: String,
363    #[serde(default, rename = "completedLength")]
364    pub completed_length: String,
365    #[serde(default, rename = "uploadLength")]
366    pub upload_length: String,
367    #[serde(default)]
368    pub bitfield: String,
369    #[serde(default, rename = "downloadSpeed")]
370    pub download_speed: String,
371    #[serde(default, rename = "uploadSpeed")]
372    pub upload_speed: String,
373    #[serde(default, rename = "infoHash")]
374    pub info_hash: String,
375    #[serde(default, rename = "numSeeders")]
376    pub num_seeders: String,
377    #[serde(default)]
378    pub seeder: String,
379    #[serde(default, rename = "pieceLength")]
380    pub piece_length: String,
381    #[serde(default, rename = "numPieces")]
382    pub num_pieces: String,
383    #[serde(default)]
384    pub connections: String,
385    #[serde(default, rename = "errorCode")]
386    pub error_code: String,
387    #[serde(default, rename = "errorMessage")]
388    pub error_message: String,
389    #[serde(default, rename = "belongsTo")]
390    pub belongs_to: String,
391    #[serde(default)]
392    pub dir: String,
393    #[serde(default)]
394    pub files: Vec<Aria2File>,
395    #[serde(default)]
396    pub bittorrent: Option<serde_json::Value>,
397}
398
399/// One source URI inside an aria2 file entry. Cloudreve v3 echoes aria2's own shape.
400#[derive(Debug, Serialize, Deserialize, Clone, Default)]
401pub struct Aria2FileUri {
402    #[serde(default)]
403    pub uri: String,
404    #[serde(default)]
405    pub status: String,
406}
407
408/// One file inside an aria2 task. For multi-file torrents Cloudreve v3 may expose more
409/// than one entry; for plain HTTP downloads there is exactly one.
410#[derive(Debug, Serialize, Deserialize, Clone, Default)]
411pub struct Aria2File {
412    #[serde(default)]
413    pub index: String,
414    #[serde(default)]
415    pub path: String,
416    #[serde(default)]
417    pub length: String,
418    #[serde(default, rename = "completedLength")]
419    pub completed_length: String,
420    /// `selected` arrives as a JSON string ("true"/"false") from aria2c — Cloudreve passes
421    /// it through unchanged — but in `/aria2/finished` Cloudreve sometimes coerces it to a
422    /// real bool. Accept either form.
423    #[serde(default, deserialize_with = "deserialize_loose_bool")]
424    pub selected: bool,
425    #[serde(default)]
426    pub uris: Vec<Aria2FileUri>,
427}
428
429fn deserialize_loose_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
430where
431    D: serde::Deserializer<'de>,
432{
433    use serde::de::{Error, Unexpected};
434    let value = Option::<serde_json::Value>::deserialize(deserializer)?;
435    match value {
436        None | Some(serde_json::Value::Null) => Ok(false),
437        Some(serde_json::Value::Bool(b)) => Ok(b),
438        Some(serde_json::Value::String(s)) => match s.trim().to_ascii_lowercase().as_str() {
439            "true" | "1" | "yes" => Ok(true),
440            "false" | "0" | "no" | "" => Ok(false),
441            other => Err(D::Error::invalid_value(
442                Unexpected::Str(other),
443                &"a boolean or boolean-like string",
444            )),
445        },
446        Some(serde_json::Value::Number(n)) => Ok(n.as_i64().map(|x| x != 0).unwrap_or(false)),
447        Some(other) => Err(D::Error::invalid_type(
448            Unexpected::Other(&other.to_string()),
449            &"a boolean",
450        )),
451    }
452}
453
454/// In-flight aria2 task as returned by `GET /aria2/downloading`.
455///
456/// Cloudreve v3 returns rich progress here — `info` mirrors aria2's JSON-RPC payload,
457/// while the top-level fields are pre-computed summaries the web UI uses for the row.
458#[derive(Debug, Serialize, Deserialize, Clone, Default)]
459pub struct Aria2DownloadingTask {
460    #[serde(default)]
461    pub update: String,
462    #[serde(default)]
463    pub interval: i64,
464    #[serde(default)]
465    pub name: String,
466    /// Aria2 status as Cloudreve's internal int (see Cloudreve `aria2` constants:
467    /// 0 ready, 1 downloading, 2 paused, 3 error, 4 complete, 5 canceled).
468    #[serde(default)]
469    pub status: i32,
470    #[serde(default)]
471    pub dst: String,
472    #[serde(default)]
473    pub total: i64,
474    #[serde(default)]
475    pub downloaded: i64,
476    #[serde(default)]
477    pub speed: i64,
478    #[serde(default)]
479    pub info: Aria2DownloadInfo,
480    #[serde(default)]
481    pub node: String,
482}
483
484/// Finished aria2 task as returned by `GET /aria2/finished`.
485///
486/// Cloudreve v3 emits a flattened representation here — there is no nested aria2 `info`,
487/// instead the task lifecycle (`task_status`, `task_error`) and per-file completion are
488/// surfaced directly.
489#[derive(Debug, Serialize, Deserialize, Clone, Default)]
490pub struct Aria2FinishedTask {
491    #[serde(default)]
492    pub name: String,
493    #[serde(default)]
494    pub gid: String,
495    #[serde(default)]
496    pub status: i32,
497    #[serde(default)]
498    pub dst: String,
499    /// Legacy aria2-side error string. Prefer `task_error` for new code.
500    #[serde(default)]
501    pub error: String,
502    #[serde(default)]
503    pub total: i64,
504    #[serde(default)]
505    pub files: Vec<Aria2File>,
506    /// Cloudreve task lifecycle status (matches the Aria2 status int set).
507    #[serde(default)]
508    pub task_status: i32,
509    #[serde(default)]
510    pub task_error: String,
511    /// ISO-8601 creation timestamp.
512    #[serde(default)]
513    pub create: String,
514    /// ISO-8601 last-update timestamp.
515    #[serde(default)]
516    pub update: String,
517    #[serde(default)]
518    pub node: String,
519}
520
521/// Aria2 create download request for v3 API
522#[derive(Debug, Serialize)]
523pub struct Aria2CreateRequest<'a> {
524    pub dst: &'a str,
525    pub url: Vec<&'a str>,
526}
527
528/// One entry of the user's task queue (`/user/setting/tasks`). Distinct from aria2 tasks:
529/// covers compression, decompression, recycle, transfer, etc.
530#[derive(Debug, Serialize, Deserialize, Clone, Default)]
531pub struct UserTask {
532    #[serde(default)]
533    pub id: String,
534    #[serde(default)]
535    pub name: String,
536    /// Cloudreve's `task.Status` int (0 queued, 1 processing, 2 error, 3 canceled, 4 complete).
537    #[serde(default)]
538    pub status: i32,
539    /// Cloudreve's `task.Type` int (see Cloudreve `models/task`).
540    #[serde(default, rename = "type")]
541    pub task_type: i32,
542    #[serde(default)]
543    pub create_date: String,
544    #[serde(default)]
545    pub progress: i64,
546    /// JSON-encoded error payload (Cloudreve's own format: `{"msg":"...","error":"..."}`).
547    #[serde(default)]
548    pub error: String,
549}
550
551/// Paginated response for `/user/setting/tasks`.
552#[derive(Debug, Serialize, Deserialize, Clone, Default)]
553pub struct UserTaskList {
554    #[serde(default)]
555    pub tasks: Vec<UserTask>,
556    #[serde(default)]
557    pub total: i64,
558}
559
560/// WebDAV account information for v3 API
561#[derive(Debug, Deserialize)]
562pub struct WebdavAccount {
563    #[serde(rename = "ID")]
564    pub id: i32,
565    #[serde(rename = "Name")]
566    pub name: String,
567    #[serde(rename = "Root")]
568    pub uri: String,
569    #[serde(rename = "Password")]
570    pub password: String,
571    #[serde(rename = "CreatedAt")]
572    pub created_at: String,
573}