files_sdk/automation/
remote_servers.rs

1//! Remote Server operations
2//!
3//! Remote Servers represent connections to external storage providers like S3, Azure,
4//! FTP/SFTP servers, and more for syncing or mounting.
5
6use crate::{FilesClient, PaginationInfo, Result};
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9
10/// A Remote Server entity
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct RemoteServerEntity {
13    /// Remote server ID
14    #[serde(skip_serializing_if = "Option::is_none")]
15    pub id: Option<i64>,
16
17    /// Server type (e.g., s3, azure, ftp, sftp)
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub server_type: Option<String>,
20
21    /// Server name
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub name: Option<String>,
24
25    /// Authentication method
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub authentication_method: Option<String>,
28
29    /// Hostname (for FTP/SFTP)
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub hostname: Option<String>,
32
33    /// Port (for FTP/SFTP)
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub port: Option<i64>,
36
37    /// Username
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub username: Option<String>,
40
41    /// Remote home path
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub remote_home_path: Option<String>,
44
45    /// Use SSL
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub ssl: Option<String>,
48
49    /// Max connections
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub max_connections: Option<i64>,
52
53    /// Pin to site region
54    #[serde(skip_serializing_if = "Option::is_none")]
55    pub pin_to_site_region: Option<bool>,
56
57    /// Pinned region
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub pinned_region: Option<String>,
60
61    /// S3 bucket name
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub s3_bucket: Option<String>,
64
65    /// S3 region
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub s3_region: Option<String>,
68
69    /// AWS access key
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub aws_access_key: Option<String>,
72
73    /// Server host key (SSH)
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub server_host_key: Option<String>,
76
77    /// Server certificate
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub server_certificate: Option<String>,
80
81    /// Azure Blob storage account
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub azure_blob_storage_account: Option<String>,
84
85    /// Azure Blob storage container
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub azure_blob_storage_container: Option<String>,
88
89    /// Azure Blob storage DNS suffix
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub azure_blob_storage_dns_suffix: Option<String>,
92
93    /// Azure Blob hierarchical namespace
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub azure_blob_storage_hierarchical_namespace: Option<bool>,
96
97    /// Azure Files storage account
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub azure_files_storage_account: Option<String>,
100
101    /// Azure Files storage share name
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub azure_files_storage_share_name: Option<String>,
104
105    /// Azure Files storage DNS suffix
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub azure_files_storage_dns_suffix: Option<String>,
108
109    /// Backblaze B2 bucket
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub backblaze_b2_bucket: Option<String>,
112
113    /// Backblaze B2 S3 endpoint
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub backblaze_b2_s3_endpoint: Option<String>,
116
117    /// Wasabi bucket
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub wasabi_bucket: Option<String>,
120
121    /// Wasabi region
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub wasabi_region: Option<String>,
124
125    /// Wasabi access key
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub wasabi_access_key: Option<String>,
128
129    /// Google Cloud Storage bucket
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub google_cloud_storage_bucket: Option<String>,
132
133    /// Google Cloud Storage project ID
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub google_cloud_storage_project_id: Option<String>,
136
137    /// S3-compatible access key
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub s3_compatible_access_key: Option<String>,
140
141    /// S3-compatible bucket
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub s3_compatible_bucket: Option<String>,
144
145    /// S3-compatible endpoint
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub s3_compatible_endpoint: Option<String>,
148
149    /// S3-compatible region
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub s3_compatible_region: Option<String>,
152
153    /// Files Agent API token
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub files_agent_api_token: Option<String>,
156
157    /// Files Agent root
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub files_agent_root: Option<String>,
160
161    /// Files Agent permission set
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub files_agent_permission_set: Option<String>,
164
165    /// Files Agent version
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub files_agent_version: Option<String>,
168
169    /// Filebase bucket
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub filebase_bucket: Option<String>,
172
173    /// Filebase access key
174    #[serde(skip_serializing_if = "Option::is_none")]
175    pub filebase_access_key: Option<String>,
176
177    /// Cloudflare bucket
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub cloudflare_bucket: Option<String>,
180
181    /// Cloudflare access key
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub cloudflare_access_key: Option<String>,
184
185    /// Cloudflare endpoint
186    #[serde(skip_serializing_if = "Option::is_none")]
187    pub cloudflare_endpoint: Option<String>,
188
189    /// Dropbox teams
190    #[serde(skip_serializing_if = "Option::is_none")]
191    pub dropbox_teams: Option<bool>,
192
193    /// Linode bucket
194    #[serde(skip_serializing_if = "Option::is_none")]
195    pub linode_bucket: Option<String>,
196
197    /// Linode access key
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub linode_access_key: Option<String>,
200
201    /// Linode region
202    #[serde(skip_serializing_if = "Option::is_none")]
203    pub linode_region: Option<String>,
204
205    /// OneDrive account type
206    #[serde(skip_serializing_if = "Option::is_none")]
207    pub one_drive_account_type: Option<String>,
208
209    /// Server disabled
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub disabled: Option<bool>,
212
213    /// Supports versioning
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub supports_versioning: Option<bool>,
216
217    /// Enable dedicated IPs
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub enable_dedicated_ips: Option<bool>,
220
221    /// Auth account name
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub auth_account_name: Option<String>,
224
225    /// Auth status
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub auth_status: Option<String>,
228
229    /// Google Cloud Storage S3-compatible access key
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub google_cloud_storage_s3_compatible_access_key: Option<String>,
232}
233
234/// Handler for remote server operations
235pub struct RemoteServerHandler {
236    client: FilesClient,
237}
238
239impl RemoteServerHandler {
240    /// Create a new remote server handler
241    pub fn new(client: FilesClient) -> Self {
242        Self { client }
243    }
244
245    /// List remote servers
246    ///
247    /// # Arguments
248    /// * `cursor` - Pagination cursor
249    /// * `per_page` - Results per page
250    ///
251    /// # Returns
252    /// Tuple of (remote_servers, pagination_info)
253    ///
254    /// # Example
255    /// ```no_run
256    /// use files_sdk::{FilesClient, RemoteServerHandler};
257    ///
258    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
259    /// let client = FilesClient::builder().api_key("key").build()?;
260    /// let handler = RemoteServerHandler::new(client);
261    /// let (servers, _) = handler.list(None, None).await?;
262    /// # Ok(())
263    /// # }
264    /// ```
265    pub async fn list(
266        &self,
267        cursor: Option<&str>,
268        per_page: Option<i64>,
269    ) -> Result<(Vec<RemoteServerEntity>, PaginationInfo)> {
270        let mut params = vec![];
271        if let Some(c) = cursor {
272            params.push(("cursor", c.to_string()));
273        }
274        if let Some(pp) = per_page {
275            params.push(("per_page", pp.to_string()));
276        }
277
278        let query = if params.is_empty() {
279            String::new()
280        } else {
281            format!(
282                "?{}",
283                params
284                    .iter()
285                    .map(|(k, v)| format!("{}={}", k, v))
286                    .collect::<Vec<_>>()
287                    .join("&")
288            )
289        };
290
291        let response = self
292            .client
293            .get_raw(&format!("/remote_servers{}", query))
294            .await?;
295        let servers: Vec<RemoteServerEntity> = serde_json::from_value(response)?;
296
297        let pagination = PaginationInfo {
298            cursor_next: None,
299            cursor_prev: None,
300        };
301
302        Ok((servers, pagination))
303    }
304
305    /// Get a specific remote server
306    ///
307    /// # Arguments
308    /// * `id` - Remote server ID
309    pub async fn get(&self, id: i64) -> Result<RemoteServerEntity> {
310        let response = self
311            .client
312            .get_raw(&format!("/remote_servers/{}", id))
313            .await?;
314        Ok(serde_json::from_value(response)?)
315    }
316
317    /// Get remote server configuration file
318    ///
319    /// # Arguments
320    /// * `id` - Remote server ID
321    pub async fn get_configuration_file(&self, id: i64) -> Result<serde_json::Value> {
322        let response = self
323            .client
324            .get_raw(&format!("/remote_servers/{}/configuration_file", id))
325            .await?;
326        Ok(response)
327    }
328
329    /// Create a new remote server
330    ///
331    /// # Arguments
332    /// * `name` - Server name (required)
333    /// * `server_type` - Server type (required)
334    /// * `hostname` - Hostname (for FTP/SFTP)
335    /// * `username` - Username
336    /// * `port` - Port
337    /// * `s3_bucket` - S3 bucket name
338    /// * `s3_region` - S3 region
339    ///
340    /// # Returns
341    /// The created remote server
342    #[allow(clippy::too_many_arguments)]
343    pub async fn create(
344        &self,
345        name: &str,
346        server_type: &str,
347        hostname: Option<&str>,
348        username: Option<&str>,
349        port: Option<i64>,
350        s3_bucket: Option<&str>,
351        s3_region: Option<&str>,
352    ) -> Result<RemoteServerEntity> {
353        let mut request_body = json!({
354            "name": name,
355            "server_type": server_type,
356        });
357
358        if let Some(h) = hostname {
359            request_body["hostname"] = json!(h);
360        }
361        if let Some(u) = username {
362            request_body["username"] = json!(u);
363        }
364        if let Some(p) = port {
365            request_body["port"] = json!(p);
366        }
367        if let Some(b) = s3_bucket {
368            request_body["s3_bucket"] = json!(b);
369        }
370        if let Some(r) = s3_region {
371            request_body["s3_region"] = json!(r);
372        }
373
374        let response = self
375            .client
376            .post_raw("/remote_servers", request_body)
377            .await?;
378        Ok(serde_json::from_value(response)?)
379    }
380
381    /// Update a remote server
382    ///
383    /// # Arguments
384    /// * `id` - Remote server ID
385    /// * `name` - New server name
386    /// * `hostname` - New hostname
387    /// * `port` - New port
388    /// * `disabled` - Disable the server
389    ///
390    /// # Returns
391    /// The updated remote server
392    #[allow(clippy::too_many_arguments)]
393    pub async fn update(
394        &self,
395        id: i64,
396        name: Option<&str>,
397        hostname: Option<&str>,
398        port: Option<i64>,
399        disabled: Option<bool>,
400    ) -> Result<RemoteServerEntity> {
401        let mut request_body = json!({});
402
403        if let Some(n) = name {
404            request_body["name"] = json!(n);
405        }
406        if let Some(h) = hostname {
407            request_body["hostname"] = json!(h);
408        }
409        if let Some(p) = port {
410            request_body["port"] = json!(p);
411        }
412        if let Some(d) = disabled {
413            request_body["disabled"] = json!(d);
414        }
415
416        let response = self
417            .client
418            .patch_raw(&format!("/remote_servers/{}", id), request_body)
419            .await?;
420        Ok(serde_json::from_value(response)?)
421    }
422
423    /// Delete a remote server
424    ///
425    /// # Arguments
426    /// * `id` - Remote server ID
427    pub async fn delete(&self, id: i64) -> Result<()> {
428        self.client
429            .delete_raw(&format!("/remote_servers/{}", id))
430            .await?;
431        Ok(())
432    }
433}
434
435#[cfg(test)]
436mod tests {
437    use super::*;
438
439    #[test]
440    fn test_handler_creation() {
441        let client = FilesClient::builder().api_key("test-key").build().unwrap();
442        let _handler = RemoteServerHandler::new(client);
443    }
444}