Skip to main content

dco3/nodes/models/
mod.rs

1pub(crate) mod crypto;
2pub mod filters;
3pub mod sorts;
4
5pub(crate) use crypto::*;
6use dco3_crypto::DracoonCrypto;
7use dco3_crypto::DracoonRSACrypto;
8use dco3_crypto::PlainUserKeyPairContainer;
9use dco3_derive::FromResponse;
10pub use filters::*;
11pub use sorts::*;
12use tracing::{debug, error, warn};
13
14use std::fmt::Debug;
15use std::fmt::Display;
16use std::fmt::Formatter;
17use std::sync::Arc;
18use std::sync::Mutex;
19
20use crate::client::DracoonClient;
21use crate::{
22    client::{errors::DracoonClientError, models::DracoonErrorResponse},
23    models::{ObjectExpiration, Range, RangedItems},
24    utils::parse_body,
25    utils::FromResponse,
26};
27
28use async_trait::async_trait;
29use chrono::{DateTime, Utc};
30use dco3_crypto::FileKey;
31use dco3_crypto::PublicKeyContainer;
32use reqwest::{Response, StatusCode};
33use serde::{Deserialize, Serialize};
34
35use super::rooms::models::NodePermissionsBuilder;
36
37#[derive(Clone)]
38pub struct NodesEndpoint<S> {
39    client: Arc<DracoonClient<S>>,
40    state: std::marker::PhantomData<S>,
41}
42
43impl<S> NodesEndpoint<S> {
44    pub fn new(client: Arc<DracoonClient<S>>) -> Self {
45        Self {
46            client,
47            state: std::marker::PhantomData,
48        }
49    }
50
51    pub fn client(&self) -> &Arc<DracoonClient<S>> {
52        &self.client
53    }
54}
55
56/// A callback function that is called after each chunk is processed (download)
57pub type DownloadProgressCallback = Box<dyn FnMut(u64, u64) + Send + Sync>;
58
59/// A callback function that is called after each chunk is processed (upload)
60pub type UploadProgressCallback = Box<dyn FnMut(u64, u64) + Send + Sync>;
61
62/// A callback function (thread-safe) that can be cloned and called from multiple threads (upload)
63pub struct CloneableUploadProgressCallback(Arc<Mutex<UploadProgressCallback>>);
64
65impl Clone for CloneableUploadProgressCallback {
66    fn clone(&self) -> Self {
67        Self(self.0.clone())
68    }
69}
70
71impl CloneableUploadProgressCallback {
72    pub fn new<F>(callback: F) -> Self
73    where
74        F: 'static + FnMut(u64, u64) + Send + Sync,
75    {
76        Self(Arc::new(Mutex::new(Box::new(callback))))
77    }
78
79    pub fn call(&self, bytes_read: u64, total_size: u64) {
80        (self.0.lock().unwrap())(bytes_read, total_size);
81    }
82}
83
84/// file meta information (name, size, timestamp creation, timestamp modification)
85#[derive(Debug, Clone)]
86pub struct FileMeta {
87    pub name: String,
88    pub size: u64,
89    pub timestamp_creation: Option<DateTime<Utc>>,
90    pub timestamp_modification: Option<DateTime<Utc>>,
91}
92
93#[derive(Default)]
94pub struct FileMetaBuilder {
95    name: String,
96    size: u64,
97    timestamp_creation: Option<DateTime<Utc>>,
98    timestamp_modification: Option<DateTime<Utc>>,
99}
100
101impl FileMeta {
102    pub fn builder(name: impl Into<String>, size: u64) -> FileMetaBuilder {
103        FileMetaBuilder::new(name, size)
104    }
105}
106
107impl FileMetaBuilder {
108    pub fn new(name: impl Into<String>, size: u64) -> Self {
109        Self {
110            name: name.into(),
111            size,
112            timestamp_creation: None,
113            timestamp_modification: None,
114        }
115    }
116
117    pub fn with_timestamp_creation(mut self, timestamp_creation: DateTime<Utc>) -> Self {
118        self.timestamp_creation = Some(timestamp_creation);
119        self
120    }
121
122    pub fn with_timestamp_modification(mut self, timestamp_modification: DateTime<Utc>) -> Self {
123        self.timestamp_modification = Some(timestamp_modification);
124        self
125    }
126
127    pub fn build(self) -> FileMeta {
128        FileMeta {
129            name: self.name,
130            size: self.size,
131            timestamp_creation: self.timestamp_creation,
132            timestamp_modification: self.timestamp_modification,
133        }
134    }
135}
136
137/// upload options (expiration, classification, keep share links, resolution strategy)
138#[derive(Debug, Clone)]
139pub struct UploadOptions {
140    pub expiration: Option<ObjectExpiration>,
141    pub classification: Option<u8>,
142    pub keep_share_links: Option<bool>,
143    pub resolution_strategy: Option<ResolutionStrategy>,
144    pub file_meta: FileMeta,
145}
146
147impl UploadOptions {
148    pub fn builder(file_meta: FileMeta) -> UploadOptionsBuilder {
149        UploadOptionsBuilder::new(file_meta)
150    }
151}
152
153pub struct UploadOptionsBuilder {
154    file_meta: FileMeta,
155    expiration: Option<ObjectExpiration>,
156    classification: Option<u8>,
157    keep_share_links: Option<bool>,
158    resolution_strategy: Option<ResolutionStrategy>,
159}
160
161impl UploadOptionsBuilder {
162    pub fn new(file_meta: FileMeta) -> Self {
163        Self {
164            expiration: None,
165            classification: None,
166            keep_share_links: None,
167            resolution_strategy: None,
168            file_meta,
169        }
170    }
171
172    pub fn with_expiration(mut self, expiration: impl Into<ObjectExpiration>) -> Self {
173        self.expiration = Some(expiration.into());
174        self
175    }
176
177    pub fn with_classification(mut self, classification: u8) -> Self {
178        self.classification = Some(classification);
179        self
180    }
181
182    pub fn with_keep_share_links(mut self, keep_share_links: bool) -> Self {
183        self.keep_share_links = Some(keep_share_links);
184        self
185    }
186
187    pub fn with_resolution_strategy(mut self, resolution_strategy: ResolutionStrategy) -> Self {
188        self.resolution_strategy = Some(resolution_strategy);
189        self
190    }
191
192    pub fn build(self) -> UploadOptions {
193        UploadOptions {
194            expiration: self.expiration,
195            classification: self.classification,
196            keep_share_links: self.keep_share_links,
197            resolution_strategy: self.resolution_strategy,
198            file_meta: self.file_meta,
199        }
200    }
201}
202
203/// A list of nodes in DRACOON - GET /nodes
204pub type NodeList = RangedItems<Node>;
205
206impl NodeList {
207    pub fn get_files(&self) -> Vec<Node> {
208        self.items
209            .iter()
210            .filter(|node| node.node_type == NodeType::File)
211            .cloned()
212            .collect()
213    }
214
215    pub fn get_folders(&self) -> Vec<Node> {
216        self.items
217            .iter()
218            .filter(|node| node.node_type == NodeType::Folder)
219            .cloned()
220            .collect()
221    }
222
223    pub fn get_rooms(&self) -> Vec<Node> {
224        self.items
225            .iter()
226            .filter(|node| node.node_type == NodeType::Room)
227            .cloned()
228            .collect()
229    }
230}
231
232/// A node in DRACOON - GET /nodes/{nodeId}
233#[derive(Debug, Deserialize, Clone, FromResponse)]
234#[serde(rename_all = "camelCase")]
235pub struct Node {
236    pub id: u64,
237    pub reference_id: Option<u64>,
238    #[serde(rename = "type")]
239    pub node_type: NodeType,
240    pub name: String,
241    pub timestamp_creation: Option<DateTime<Utc>>,
242    pub timestamp_modification: Option<DateTime<Utc>>,
243    pub parent_id: Option<u64>,
244    pub parent_path: Option<String>,
245    pub created_at: Option<DateTime<Utc>>,
246    pub created_by: Option<UserInfo>,
247    pub updated_at: Option<DateTime<Utc>>,
248    pub updated_by: Option<UserInfo>,
249    pub expire_at: Option<DateTime<Utc>>,
250    pub hash: Option<String>,
251    pub file_type: Option<String>,
252    pub media_type: Option<String>,
253    pub size: Option<u64>,
254    pub classification: Option<u64>,
255    pub notes: Option<String>,
256    pub permissions: Option<NodePermissions>,
257    pub inherit_permissions: Option<bool>,
258    pub is_encrypted: Option<bool>,
259    pub encryption_info: Option<EncryptionInfo>,
260    pub cnt_deleted_versions: Option<u64>,
261    pub cnt_comments: Option<u64>,
262    pub cnt_upload_shares: Option<u64>,
263    pub cnt_download_shares: Option<u64>,
264    pub recycle_bin_retention_period: Option<u64>,
265    pub has_activities_log: Option<bool>,
266    pub quota: Option<u64>,
267    pub is_favorite: Option<bool>,
268    pub branch_version: Option<u64>,
269    pub media_token: Option<String>,
270    pub is_browsable: Option<bool>,
271    pub cnt_rooms: Option<u64>,
272    pub cnt_folders: Option<u64>,
273    pub cnt_files: Option<u64>,
274    pub auth_parent_id: Option<u64>,
275}
276
277#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
278pub enum NodeType {
279    #[serde(rename = "room")]
280    Room,
281    #[serde(rename = "folder")]
282    Folder,
283    #[serde(rename = "file")]
284    File,
285}
286
287impl From<NodeType> for String {
288    fn from(node_type: NodeType) -> Self {
289        match node_type {
290            NodeType::Room => "room".to_string(),
291            NodeType::Folder => "folder".to_string(),
292            NodeType::File => "file".to_string(),
293        }
294    }
295}
296
297impl From<&NodeType> for String {
298    fn from(node_type: &NodeType) -> Self {
299        match node_type {
300            NodeType::Room => "room".to_string(),
301            NodeType::Folder => "folder".to_string(),
302            NodeType::File => "file".to_string(),
303        }
304    }
305}
306
307/// DRACOOON node permissions
308#[derive(Debug, Serialize, Deserialize, Clone)]
309#[serde(rename_all = "camelCase")]
310#[allow(clippy::struct_excessive_bools)]
311pub struct NodePermissions {
312    pub manage: bool,
313    pub read: bool,
314    pub create: bool,
315    pub change: bool,
316    pub delete: bool,
317    pub manage_download_share: bool,
318    pub manage_upload_share: bool,
319    pub read_recycle_bin: bool,
320    pub restore_recycle_bin: bool,
321    pub delete_recycle_bin: bool,
322}
323
324impl NodePermissions {
325    pub fn builder() -> NodePermissionsBuilder {
326        NodePermissionsBuilder::new()
327    }
328
329    pub fn new_with_edit_permissions() -> Self {
330        Self {
331            manage: false,
332            read: true,
333            create: true,
334            change: true,
335            delete: true,
336            manage_download_share: true,
337            manage_upload_share: true,
338            read_recycle_bin: true,
339            restore_recycle_bin: true,
340            delete_recycle_bin: false,
341        }
342    }
343
344    pub fn new_with_read_permissions() -> Self {
345        Self {
346            manage: false,
347            read: true,
348            create: false,
349            change: false,
350            delete: false,
351            manage_download_share: false,
352            manage_upload_share: false,
353            read_recycle_bin: false,
354            restore_recycle_bin: false,
355            delete_recycle_bin: false,
356        }
357    }
358
359    pub fn new_with_manage_permissions() -> Self {
360        Self {
361            manage: true,
362            read: true,
363            create: true,
364            change: true,
365            delete: true,
366            manage_download_share: true,
367            manage_upload_share: true,
368            read_recycle_bin: true,
369            restore_recycle_bin: true,
370            delete_recycle_bin: true,
371        }
372    }
373}
374
375impl Display for NodePermissions {
376    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
377        let mapping = [
378            (self.manage, 'm'),
379            (self.read, 'r'),
380            (self.create, 'w'),
381            (self.change, 'c'),
382            (self.delete, 'd'),
383            (self.manage_download_share, 'm'),
384            (self.manage_upload_share, 'm'),
385            (self.read_recycle_bin, 'r'),
386            (self.restore_recycle_bin, 'r'),
387            (self.delete_recycle_bin, 'd'),
388        ];
389
390        let mut perms = String::with_capacity(mapping.len());
391
392        for (i, &(flag, ch)) in mapping.iter().enumerate() {
393            perms.push(if flag { ch } else { '-' });
394
395            // Add a dash after the "delete" permission
396            if i == 4 {
397                perms.push('-');
398            }
399        }
400
401        formatter.write_str(&perms)?;
402
403        Ok(())
404    }
405}
406
407/// DRACOOON encryption info (rescue keys)
408#[derive(Debug, Serialize, Deserialize, Clone)]
409#[serde(rename_all = "camelCase")]
410pub struct EncryptionInfo {
411    user_key_state: String,
412    room_key_state: String,
413    data_space_key_state: String,
414}
415
416/// DRACOON user info on nodes (`created_by`, `updated_by`)
417#[derive(Debug, Deserialize, Clone)]
418#[serde(rename_all = "camelCase")]
419pub struct UserInfo {
420    pub id: i64,
421    pub user_type: UserType,
422    pub user_name: Option<String>,
423    pub avatar_uuid: String,
424    pub first_name: Option<String>,
425    pub last_name: Option<String>,
426    pub email: Option<String>,
427}
428
429#[derive(Deserialize, Debug, Clone, PartialEq)]
430pub enum UserType {
431    #[serde(rename = "internal")]
432    Internal,
433    #[serde(rename = "external")]
434    External,
435    #[serde(rename = "system")]
436    System,
437    #[serde(rename = "deleted")]
438    Deleted,
439}
440
441#[async_trait]
442impl FromResponse for NodeList {
443    /// transforms a response into a NodeList
444    async fn from_response(res: Response) -> Result<Self, DracoonClientError> {
445        parse_body::<Self, DracoonErrorResponse>(res).await
446    }
447}
448
449/// Response for download url of a node - POST /nodes/files/{nodeId}/download
450#[derive(Serialize, Deserialize, Debug, FromResponse)]
451#[serde(rename_all = "camelCase")]
452pub struct DownloadUrlResponse {
453    pub download_url: String,
454}
455
456/// Error response for S3 requests (XML)
457#[derive(Debug, Deserialize, PartialEq, Clone)]
458#[serde(rename_all = "PascalCase")]
459pub struct S3XmlError {
460    code: Option<String>,
461    request_id: Option<String>,
462    host_id: Option<String>,
463    message: Option<String>,
464    argument_name: Option<String>,
465}
466
467/// Error response for S3 requests
468#[derive(Debug, PartialEq, Clone)]
469pub enum S3ErrorKind {
470    Xml(S3XmlError),
471    Protocol { code: &'static str, message: String },
472}
473
474/// Error response for S3 requests
475#[derive(Debug, PartialEq, Clone)]
476pub struct S3ErrorResponse {
477    pub status: StatusCode,
478    pub kind: S3ErrorKind,
479}
480
481impl Display for S3ErrorResponse {
482    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
483        match &self.kind {
484            S3ErrorKind::Xml(error) => write!(
485                f,
486                "Error: {} ({})",
487                error
488                    .message
489                    .as_ref()
490                    .unwrap_or(&String::from("Unknown S3 error")),
491                self.status,
492            ),
493            S3ErrorKind::Protocol { code, message } => {
494                write!(f, "Error: {message} [{code}] ({})", self.status)
495            }
496        }
497    }
498}
499
500impl S3ErrorResponse {
501    /// transforms a `S3XmlError` into a `S3ErrorResponse`
502    pub fn from_xml_error(status: StatusCode, error: S3XmlError) -> Self {
503        Self {
504            status,
505            kind: S3ErrorKind::Xml(error),
506        }
507    }
508
509    /// builds an S3/upload protocol error when the upstream response shape is invalid
510    pub fn protocol(status: StatusCode, code: &'static str, message: impl Into<String>) -> Self {
511        Self {
512            status,
513            kind: S3ErrorKind::Protocol {
514                code,
515                message: message.into(),
516            },
517        }
518    }
519}
520
521#[async_trait]
522impl FromResponse for FileKey {
523    /// transforms a response into a `FileKey`
524    async fn from_response(res: Response) -> Result<Self, DracoonClientError> {
525        parse_body::<Self, DracoonErrorResponse>(res).await
526    }
527}
528
529#[derive(Debug, Deserialize, FromResponse)]
530#[serde(rename_all = "camelCase")]
531pub struct CreateFileUploadResponse {
532    pub upload_url: String,
533    pub upload_id: String,
534    pub token: String,
535}
536
537#[derive(Debug, Deserialize, Clone)]
538#[serde(rename_all = "camelCase")]
539pub struct PresignedUrl {
540    pub url: String,
541    pub part_number: u32,
542}
543
544#[derive(Debug, Deserialize, FromResponse, Clone)]
545#[serde(rename_all = "camelCase")]
546pub struct PresignedUrlList {
547    pub urls: Vec<PresignedUrl>,
548}
549
550#[derive(Debug, Deserialize, FromResponse, Clone)]
551#[serde(rename_all = "camelCase")]
552pub struct S3FileUploadStatus {
553    pub status: S3UploadStatus,
554    pub node: Option<Node>,
555    pub error_details: Option<DracoonErrorResponse>,
556}
557
558#[derive(Debug, Deserialize, PartialEq, Clone)]
559pub enum S3UploadStatus {
560    #[serde(rename = "transfer")]
561    Transfer,
562    #[serde(rename = "finishing")]
563    Finishing,
564    #[serde(rename = "done")]
565    Done,
566    #[serde(rename = "error")]
567    Error,
568}
569
570#[derive(Debug, Serialize, Clone)]
571#[serde(rename_all = "camelCase")]
572#[allow(non_snake_case)]
573pub struct CreateFileUploadRequest {
574    parent_id: u64,
575    name: String,
576    #[serde(skip_serializing_if = "Option::is_none")]
577    size: Option<u64>,
578    #[serde(skip_serializing_if = "Option::is_none")]
579    classification: Option<u8>,
580    #[serde(skip_serializing_if = "Option::is_none")]
581    expiration: Option<ObjectExpiration>,
582    #[serde(skip_serializing_if = "Option::is_none")]
583    direct_S3_upload: Option<bool>,
584    #[serde(skip_serializing_if = "Option::is_none")]
585    timestamp_creation: Option<String>,
586    #[serde(skip_serializing_if = "Option::is_none")]
587    timestamp_modification: Option<String>,
588}
589
590impl CreateFileUploadRequest {
591    pub fn builder(parent_id: u64, name: String) -> CreateFileUploadRequestBuilder {
592        CreateFileUploadRequestBuilder {
593            parent_id,
594            name,
595            size: None,
596            classification: None,
597            expiration: None,
598            direct_s3_upload: Some(true),
599            timestamp_creation: None,
600            timestamp_modification: None,
601        }
602    }
603
604    pub fn from_upload_options(
605        parent_id: u64,
606        upload_options: &UploadOptions,
607        is_s3_upload: Option<bool>,
608    ) -> Self {
609        let req = Self::builder(parent_id, upload_options.file_meta.name.clone())
610            .with_size(upload_options.file_meta.size)
611            .with_classification(upload_options.classification.unwrap_or(1))
612            .with_expiration(
613                upload_options
614                    .expiration
615                    .clone()
616                    .unwrap_or_default()
617                    .clone(),
618            );
619        let req = if let Some(timestamp_creation) = upload_options.file_meta.timestamp_creation {
620            req.with_timestamp_creation(timestamp_creation)
621        } else {
622            req
623        };
624
625        let mut req =
626            if let Some(timestamp_modification) = upload_options.file_meta.timestamp_modification {
627                req.with_timestamp_modification(timestamp_modification)
628            } else {
629                req
630            };
631
632        req.direct_s3_upload = is_s3_upload;
633
634        req.build()
635    }
636}
637
638pub struct CreateFileUploadRequestBuilder {
639    parent_id: u64,
640    name: String,
641    size: Option<u64>,
642    classification: Option<u8>,
643    expiration: Option<ObjectExpiration>,
644    direct_s3_upload: Option<bool>,
645    timestamp_creation: Option<String>,
646    timestamp_modification: Option<String>,
647}
648
649impl CreateFileUploadRequestBuilder {
650    pub fn with_size(mut self, size: u64) -> Self {
651        self.size = Some(size);
652        self
653    }
654
655    pub fn with_classification(mut self, classification: u8) -> Self {
656        self.classification = Some(classification);
657        self
658    }
659
660    pub fn with_expiration(mut self, expiration: impl Into<ObjectExpiration>) -> Self {
661        self.expiration = Some(expiration.into());
662        self
663    }
664    pub fn with_timestamp_creation(mut self, timestamp_creation: DateTime<Utc>) -> Self {
665        self.timestamp_creation = Some(timestamp_creation.to_rfc3339());
666        self
667    }
668    pub fn with_timestamp_modification(mut self, timestamp_modification: DateTime<Utc>) -> Self {
669        self.timestamp_modification = Some(timestamp_modification.to_rfc3339());
670        self
671    }
672
673    pub fn with_direct_s3_upload(mut self, direct_s3_upload: bool) -> Self {
674        if !direct_s3_upload {
675            self.direct_s3_upload = None;
676        } else {
677            self.direct_s3_upload = Some(direct_s3_upload);
678        }
679        self
680    }
681
682    pub fn build(self) -> CreateFileUploadRequest {
683        CreateFileUploadRequest {
684            parent_id: self.parent_id,
685            name: self.name,
686            size: self.size,
687            classification: self.classification,
688            expiration: self.expiration,
689            direct_S3_upload: self.direct_s3_upload,
690            timestamp_creation: self.timestamp_creation,
691            timestamp_modification: self.timestamp_modification,
692        }
693    }
694}
695
696#[derive(Debug, Serialize, Clone)]
697#[serde(rename_all = "camelCase")]
698pub struct GeneratePresignedUrlsRequest {
699    size: u64,
700    first_part_number: u32,
701    last_part_number: u32,
702}
703
704impl GeneratePresignedUrlsRequest {
705    pub fn new(size: u64, first_part_number: u32, last_part_number: u32) -> Self {
706        Self {
707            size,
708            first_part_number,
709            last_part_number,
710        }
711    }
712}
713
714#[derive(Debug, Serialize, Clone)]
715#[serde(rename_all = "camelCase")]
716pub struct CompleteS3FileUploadRequest {
717    parts: Vec<S3FileUploadPart>,
718    #[serde(skip_serializing_if = "Option::is_none")]
719    resolution_strategy: Option<ResolutionStrategy>,
720    #[serde(skip_serializing_if = "Option::is_none")]
721    file_name: Option<String>,
722    #[serde(skip_serializing_if = "Option::is_none")]
723    keep_share_links: Option<bool>,
724    #[serde(skip_serializing_if = "Option::is_none")]
725    file_key: Option<FileKey>,
726}
727
728pub struct CompleteS3FileUploadRequestBuilder {
729    parts: Vec<S3FileUploadPart>,
730    resolution_strategy: Option<ResolutionStrategy>,
731    file_name: Option<String>,
732    keep_share_links: Option<bool>,
733    file_key: Option<FileKey>,
734}
735
736impl CompleteS3FileUploadRequest {
737    pub fn builder(parts: Vec<S3FileUploadPart>) -> CompleteS3FileUploadRequestBuilder {
738        CompleteS3FileUploadRequestBuilder {
739            parts,
740            resolution_strategy: None,
741            file_name: None,
742            keep_share_links: None,
743            file_key: None,
744        }
745    }
746}
747
748impl CompleteS3FileUploadRequestBuilder {
749    pub fn with_resolution_strategy(mut self, resolution_strategy: ResolutionStrategy) -> Self {
750        self.resolution_strategy = Some(resolution_strategy);
751        self
752    }
753
754    pub fn with_file_name(mut self, file_name: String) -> Self {
755        self.file_name = Some(file_name);
756        self
757    }
758
759    pub fn with_keep_share_links(mut self, keep_share_links: bool) -> Self {
760        self.keep_share_links = Some(keep_share_links);
761        self
762    }
763
764    pub fn with_file_key(mut self, file_key: FileKey) -> Self {
765        self.file_key = Some(file_key);
766        self
767    }
768
769    pub fn build(self) -> CompleteS3FileUploadRequest {
770        CompleteS3FileUploadRequest {
771            parts: self.parts,
772            resolution_strategy: self.resolution_strategy,
773            file_name: self.file_name,
774            keep_share_links: self.keep_share_links,
775            file_key: self.file_key,
776        }
777    }
778}
779
780#[derive(Debug, Serialize, Clone)]
781#[serde(rename_all = "camelCase")]
782pub struct CompleteUploadRequest {
783    #[serde(skip_serializing_if = "Option::is_none")]
784    resolution_strategy: Option<ResolutionStrategy>,
785    #[serde(skip_serializing_if = "Option::is_none")]
786    file_name: Option<String>,
787    #[serde(skip_serializing_if = "Option::is_none")]
788    keep_share_links: Option<bool>,
789    #[serde(skip_serializing_if = "Option::is_none")]
790    file_key: Option<FileKey>,
791}
792
793pub struct CompleteUploadRequestBuilder {
794    resolution_strategy: Option<ResolutionStrategy>,
795    file_name: Option<String>,
796    keep_share_links: Option<bool>,
797    file_key: Option<FileKey>,
798}
799
800impl CompleteUploadRequest {
801    pub fn builder() -> CompleteUploadRequestBuilder {
802        CompleteUploadRequestBuilder {
803            resolution_strategy: None,
804            file_name: None,
805            keep_share_links: None,
806            file_key: None,
807        }
808    }
809}
810
811impl CompleteUploadRequestBuilder {
812    pub fn with_resolution_strategy(mut self, resolution_strategy: ResolutionStrategy) -> Self {
813        self.resolution_strategy = Some(resolution_strategy);
814        self
815    }
816
817    pub fn with_file_name(mut self, file_name: String) -> Self {
818        self.file_name = Some(file_name);
819        self
820    }
821
822    pub fn with_keep_share_links(mut self, keep_share_links: bool) -> Self {
823        self.keep_share_links = Some(keep_share_links);
824        self
825    }
826
827    pub fn with_file_key(mut self, file_key: FileKey) -> Self {
828        self.file_key = Some(file_key);
829        self
830    }
831
832    pub fn build(self) -> CompleteUploadRequest {
833        CompleteUploadRequest {
834            resolution_strategy: self.resolution_strategy,
835            file_name: self.file_name,
836            keep_share_links: self.keep_share_links,
837            file_key: self.file_key,
838        }
839    }
840}
841
842#[derive(Debug, Serialize, Clone, Default)]
843pub enum ResolutionStrategy {
844    #[default]
845    #[serde(rename = "autorename")]
846    AutoRename,
847    #[serde(rename = "overwrite")]
848    Overwrite,
849    #[serde(rename = "fail")]
850    Fail,
851}
852
853#[derive(Debug, Serialize, Clone)]
854#[serde(rename_all = "camelCase")]
855pub struct S3FileUploadPart {
856    part_number: u32,
857    part_etag: String,
858}
859
860impl S3FileUploadPart {
861    pub fn new(part_number: u32, part_etag: String) -> Self {
862        Self {
863            part_number,
864            part_etag,
865        }
866    }
867}
868
869#[derive(Debug, Serialize, Clone)]
870#[serde(rename_all = "camelCase")]
871pub struct DeleteNodesRequest {
872    node_ids: Vec<u64>,
873}
874
875impl From<Vec<u64>> for DeleteNodesRequest {
876    fn from(node_ids: Vec<u64>) -> Self {
877        Self { node_ids }
878    }
879}
880
881#[derive(Debug, Serialize, Clone)]
882#[serde(rename_all = "camelCase")]
883pub struct TransferNodesRequest {
884    items: Vec<TransferNode>,
885    #[serde(skip_serializing_if = "Option::is_none")]
886    resolution_strategy: Option<ResolutionStrategy>,
887    #[serde(skip_serializing_if = "Option::is_none")]
888    keep_share_links: Option<bool>,
889}
890
891#[derive(Debug, Serialize, Clone)]
892#[serde(rename_all = "camelCase")]
893pub struct TransferNode {
894    id: u64,
895    name: Option<String>,
896    timestamp_creation: Option<String>,
897    timestamp_modification: Option<String>,
898}
899
900impl From<u64> for TransferNode {
901    fn from(node_id: u64) -> Self {
902        Self {
903            id: node_id,
904            name: None,
905            timestamp_creation: None,
906            timestamp_modification: None,
907        }
908    }
909}
910
911impl From<Vec<u64>> for TransferNodesRequest {
912    fn from(node_ids: Vec<u64>) -> Self {
913        Self {
914            items: node_ids.into_iter().map(std::convert::Into::into).collect(),
915            resolution_strategy: None,
916            keep_share_links: None,
917        }
918    }
919}
920
921pub struct TransferNodesRequestBuilder {
922    items: Vec<TransferNode>,
923    resolution_strategy: Option<ResolutionStrategy>,
924    keep_share_links: Option<bool>,
925}
926
927impl TransferNodesRequest {
928    pub fn builder(items: Vec<TransferNode>) -> TransferNodesRequestBuilder {
929        TransferNodesRequestBuilder {
930            items,
931            resolution_strategy: None,
932            keep_share_links: None,
933        }
934    }
935
936    pub fn new_from_ids(node_ids: Vec<u64>) -> TransferNodesRequestBuilder {
937        TransferNodesRequestBuilder {
938            items: node_ids.into_iter().map(std::convert::Into::into).collect(),
939            resolution_strategy: None,
940            keep_share_links: None,
941        }
942    }
943
944    pub fn with_resolution_strategy(mut self, resolution_strategy: ResolutionStrategy) -> Self {
945        self.resolution_strategy = Some(resolution_strategy);
946        self
947    }
948
949    pub fn with_keep_share_links(mut self, keep_share_links: bool) -> Self {
950        self.keep_share_links = Some(keep_share_links);
951        self
952    }
953
954    pub fn build(self) -> TransferNodesRequest {
955        TransferNodesRequest {
956            items: self.items,
957            resolution_strategy: self.resolution_strategy,
958            keep_share_links: self.keep_share_links,
959        }
960    }
961}
962
963pub struct TransferNodeBuilder {
964    id: u64,
965    name: Option<String>,
966    timestamp_creation: Option<String>,
967    timestamp_modification: Option<String>,
968}
969
970impl TransferNode {
971    pub fn builder(id: u64) -> TransferNodeBuilder {
972        TransferNodeBuilder {
973            id,
974            name: None,
975            timestamp_creation: None,
976            timestamp_modification: None,
977        }
978    }
979
980    pub fn with_name(mut self, name: String) -> Self {
981        self.name = Some(name);
982        self
983    }
984
985    pub fn with_timestamp_creation(mut self, timestamp_creation: String) -> Self {
986        self.timestamp_creation = Some(timestamp_creation);
987        self
988    }
989
990    pub fn with_timestamp_modification(mut self, timestamp_modification: String) -> Self {
991        self.timestamp_modification = Some(timestamp_modification);
992        self
993    }
994
995    pub fn build(self) -> TransferNode {
996        TransferNode {
997            id: self.id,
998            name: self.name,
999            timestamp_creation: self.timestamp_creation,
1000            timestamp_modification: self.timestamp_modification,
1001        }
1002    }
1003}
1004
1005#[derive(Debug, Serialize, Clone)]
1006#[serde(rename_all = "camelCase")]
1007pub struct CreateFolderRequest {
1008    name: String,
1009    parent_id: u64,
1010    #[serde(skip_serializing_if = "Option::is_none")]
1011    notes: Option<String>,
1012    #[serde(skip_serializing_if = "Option::is_none")]
1013    timestamp_creation: Option<String>,
1014    #[serde(skip_serializing_if = "Option::is_none")]
1015    timestamp_modification: Option<String>,
1016    #[serde(skip_serializing_if = "Option::is_none")]
1017    classification: Option<u8>,
1018}
1019
1020pub struct CreateFolderRequestBuilder {
1021    name: String,
1022    parent_id: u64,
1023    notes: Option<String>,
1024    timestamp_creation: Option<String>,
1025    timestamp_modification: Option<String>,
1026    classification: Option<u8>,
1027}
1028
1029impl CreateFolderRequest {
1030    pub fn builder(name: impl Into<String>, parent_id: u64) -> CreateFolderRequestBuilder {
1031        CreateFolderRequestBuilder {
1032            name: name.into(),
1033            parent_id,
1034            notes: None,
1035            timestamp_creation: None,
1036            timestamp_modification: None,
1037            classification: None,
1038        }
1039    }
1040}
1041
1042impl CreateFolderRequestBuilder {
1043    pub fn with_notes(mut self, notes: impl Into<String>) -> Self {
1044        self.notes = Some(notes.into());
1045        self
1046    }
1047
1048    pub fn with_timestamp_creation(mut self, timestamp_creation: impl Into<String>) -> Self {
1049        self.timestamp_creation = Some(timestamp_creation.into());
1050        self
1051    }
1052
1053    pub fn with_timestamp_modification(
1054        mut self,
1055        timestamp_modification: impl Into<String>,
1056    ) -> Self {
1057        self.timestamp_modification = Some(timestamp_modification.into());
1058        self
1059    }
1060
1061    pub fn with_classification(mut self, classification: u8) -> Self {
1062        self.classification = Some(classification);
1063        self
1064    }
1065
1066    pub fn build(self) -> CreateFolderRequest {
1067        CreateFolderRequest {
1068            name: self.name,
1069            parent_id: self.parent_id,
1070            notes: self.notes,
1071            timestamp_creation: self.timestamp_creation,
1072            timestamp_modification: self.timestamp_modification,
1073            classification: self.classification,
1074        }
1075    }
1076}
1077
1078#[derive(Debug, Serialize, Clone)]
1079#[serde(rename_all = "camelCase")]
1080pub struct UpdateFolderRequest {
1081    #[serde(skip_serializing_if = "Option::is_none")]
1082    name: Option<String>,
1083    #[serde(skip_serializing_if = "Option::is_none")]
1084    notes: Option<String>,
1085    #[serde(skip_serializing_if = "Option::is_none")]
1086    timestamp_creation: Option<String>,
1087    #[serde(skip_serializing_if = "Option::is_none")]
1088    timestamp_modification: Option<String>,
1089    #[serde(skip_serializing_if = "Option::is_none")]
1090    classification: Option<u8>,
1091}
1092
1093pub struct UpdateFolderRequestBuilder {
1094    name: Option<String>,
1095    notes: Option<String>,
1096    timestamp_creation: Option<String>,
1097    timestamp_modification: Option<String>,
1098    classification: Option<u8>,
1099}
1100
1101impl UpdateFolderRequest {
1102    pub fn builder() -> UpdateFolderRequestBuilder {
1103        UpdateFolderRequestBuilder {
1104            name: None,
1105            notes: None,
1106            timestamp_creation: None,
1107            timestamp_modification: None,
1108            classification: None,
1109        }
1110    }
1111}
1112
1113impl UpdateFolderRequestBuilder {
1114    pub fn with_name(mut self, name: impl Into<String>) -> Self {
1115        self.name = Some(name.into());
1116        self
1117    }
1118
1119    pub fn with_notes(mut self, notes: impl Into<String>) -> Self {
1120        self.notes = Some(notes.into());
1121        self
1122    }
1123
1124    pub fn with_timestamp_creation(mut self, timestamp_creation: impl Into<String>) -> Self {
1125        self.timestamp_creation = Some(timestamp_creation.into());
1126        self
1127    }
1128
1129    pub fn with_timestamp_modification(
1130        mut self,
1131        timestamp_modification: impl Into<String>,
1132    ) -> Self {
1133        self.timestamp_modification = Some(timestamp_modification.into());
1134        self
1135    }
1136
1137    pub fn with_classification(mut self, classification: u8) -> Self {
1138        self.classification = Some(classification);
1139        self
1140    }
1141
1142    pub fn build(self) -> UpdateFolderRequest {
1143        UpdateFolderRequest {
1144            name: self.name,
1145            notes: self.notes,
1146            timestamp_creation: self.timestamp_creation,
1147            timestamp_modification: self.timestamp_modification,
1148            classification: self.classification,
1149        }
1150    }
1151}
1152
1153#[derive(Debug, Deserialize, Clone)]
1154#[serde(rename_all = "camelCase")]
1155pub struct UserIdFileItem {
1156    pub user_id: u64,
1157    pub file_id: u64,
1158}
1159
1160#[derive(Debug, Deserialize, Clone)]
1161#[serde(rename_all = "camelCase")]
1162pub struct UserUserPublicKey {
1163    pub id: u64,
1164    pub public_key_container: PublicKeyContainer,
1165}
1166
1167#[derive(Debug, Deserialize, Clone)]
1168#[serde(rename_all = "camelCase")]
1169pub struct FileFileKeys {
1170    pub id: u64,
1171    pub file_key_container: FileKey,
1172}
1173
1174#[derive(Debug, Deserialize, FromResponse, Clone)]
1175#[serde(rename_all = "camelCase")]
1176pub struct MissingKeysResponse {
1177    pub range: Option<Range>,
1178    pub items: Vec<UserIdFileItem>,
1179    pub users: Vec<UserUserPublicKey>,
1180    pub files: Vec<FileFileKeys>,
1181}
1182
1183#[derive(Debug, Serialize, Default, Clone)]
1184#[serde(rename_all = "camelCase")]
1185pub struct UserFileKeySetBatchRequest {
1186    items: Vec<UserFileKeySetRequest>,
1187}
1188
1189impl UserFileKeySetBatchRequest {
1190    pub fn new() -> Self {
1191        UserFileKeySetBatchRequest { items: Vec::new() }
1192    }
1193
1194    pub fn add(&mut self, user_id: u64, file_id: u64, file_key: FileKey) {
1195        self.items
1196            .push(UserFileKeySetRequest::new(user_id, file_id, file_key));
1197    }
1198
1199    pub fn is_empty(&self) -> bool {
1200        self.items.is_empty()
1201    }
1202
1203    pub fn try_new_from_missing_keys(
1204        missing_keys: MissingKeysResponse,
1205        keypair: &PlainUserKeyPairContainer,
1206    ) -> Result<Self, DracoonClientError> {
1207        let mut reqs = Vec::new();
1208
1209        for item in missing_keys.items {
1210            let file_id = item.file_id;
1211            let user_id = item.user_id;
1212            let public_key = missing_keys
1213                .users
1214                .iter()
1215                .find(|u| u.id == user_id)
1216                .ok_or_else(|| {
1217                    error!("User not found in response: {}", user_id);
1218                    DracoonClientError::Unknown
1219                })?
1220                .public_key_container
1221                .clone();
1222            let file_key = missing_keys
1223                .files
1224                .iter()
1225                .find(|f| f.id == file_id)
1226                .ok_or_else(|| {
1227                    error!("File not found in response: {}", file_id);
1228                    DracoonClientError::Unknown
1229                })?
1230                .file_key_container
1231                .clone();
1232
1233            let plain_file_key = match DracoonCrypto::decrypt_file_key(file_key, keypair.clone()) {
1234                Ok(plain_file_key) => plain_file_key,
1235                Err(err) => {
1236                    warn!(
1237                        user_id,
1238                        file_id,
1239                        error = ?err,
1240                        "Skipping missing-key redistribution because file key decryption failed",
1241                    );
1242                    continue;
1243                }
1244            };
1245            let file_key = match DracoonCrypto::encrypt_file_key(plain_file_key, public_key) {
1246                Ok(file_key) => file_key,
1247                Err(err) => {
1248                    warn!(
1249                        user_id,
1250                        file_id,
1251                        error = ?err,
1252                        "Skipping missing-key redistribution because file key encryption failed",
1253                    );
1254                    continue;
1255                }
1256            };
1257
1258            reqs.push(UserFileKeySetRequest::new(user_id, file_id, file_key));
1259        }
1260
1261        debug!("Built {} key requests", reqs.len());
1262
1263        Ok(reqs.into())
1264    }
1265}
1266
1267impl From<Vec<UserFileKeySetRequest>> for UserFileKeySetBatchRequest {
1268    fn from(items: Vec<UserFileKeySetRequest>) -> Self {
1269        UserFileKeySetBatchRequest { items }
1270    }
1271}
1272
1273#[derive(Debug, Serialize, Clone)]
1274#[serde(rename_all = "camelCase")]
1275pub struct UserFileKeySetRequest {
1276    user_id: u64,
1277    file_id: u64,
1278    file_key: FileKey,
1279}
1280
1281impl UserFileKeySetRequest {
1282    pub fn new(user_id: u64, file_id: u64, file_key: FileKey) -> Self {
1283        UserFileKeySetRequest {
1284            user_id,
1285            file_id,
1286            file_key,
1287        }
1288    }
1289}
1290
1291#[derive(Debug, Clone)]
1292pub enum UseKey {
1293    RoomRescueKey,
1294    SystemRescueKey,
1295    PreviousUserKey,
1296    PreviousRoomRescueKey,
1297    PreviousSystemRescueKey,
1298}
1299
1300impl From<UseKey> for String {
1301    fn from(use_key: UseKey) -> Self {
1302        match use_key {
1303            UseKey::RoomRescueKey => "room_rescue_key".to_string(),
1304            UseKey::SystemRescueKey => "system_rescue_key".to_string(),
1305            UseKey::PreviousUserKey => "previous_user_key".to_string(),
1306            UseKey::PreviousRoomRescueKey => "previous_room_rescue_key".to_string(),
1307            UseKey::PreviousSystemRescueKey => "previous_system_rescue_key".to_string(),
1308        }
1309    }
1310}