dco3/nodes/models/
mod.rs

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