1use crate::{FilesClient, PaginationInfo, Result};
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(rename_all = "snake_case")]
13pub enum BundlePermission {
14 Read,
16 Write,
18 ReadWrite,
20 Full,
22 None,
24 PreviewOnly,
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct BundleEntity {
31 #[serde(skip_serializing_if = "Option::is_none")]
33 pub id: Option<i64>,
34
35 #[serde(skip_serializing_if = "Option::is_none")]
37 pub code: Option<String>,
38
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub url: Option<String>,
42
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub description: Option<String>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
49 pub note: Option<String>,
50
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub password_protected: Option<bool>,
54
55 #[serde(skip_serializing_if = "Option::is_none")]
57 pub permissions: Option<String>,
58
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub preview_only: Option<bool>,
62
63 #[serde(skip_serializing_if = "Option::is_none")]
65 pub require_registration: Option<bool>,
66
67 #[serde(skip_serializing_if = "Option::is_none")]
69 pub require_share_recipient: Option<bool>,
70
71 #[serde(skip_serializing_if = "Option::is_none")]
73 pub require_logout: Option<bool>,
74
75 #[serde(skip_serializing_if = "Option::is_none")]
77 pub clickwrap_body: Option<String>,
78
79 #[serde(skip_serializing_if = "Option::is_none")]
81 pub clickwrap_id: Option<i64>,
82
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub skip_name: Option<bool>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
89 pub skip_email: Option<bool>,
90
91 #[serde(skip_serializing_if = "Option::is_none")]
93 pub skip_company: Option<bool>,
94
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub expires_at: Option<String>,
98
99 #[serde(skip_serializing_if = "Option::is_none")]
101 pub start_access_on_date: Option<String>,
102
103 #[serde(skip_serializing_if = "Option::is_none")]
105 pub created_at: Option<String>,
106
107 #[serde(skip_serializing_if = "Option::is_none")]
109 pub dont_separate_submissions_by_folder: Option<bool>,
110
111 #[serde(skip_serializing_if = "Option::is_none")]
113 pub max_uses: Option<i64>,
114
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub path_template: Option<String>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub path_template_time_zone: Option<String>,
122
123 #[serde(skip_serializing_if = "Option::is_none")]
125 pub send_email_receipt_to_uploader: Option<bool>,
126
127 #[serde(skip_serializing_if = "Option::is_none")]
129 pub snapshot_id: Option<i64>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
133 pub user_id: Option<i64>,
134
135 #[serde(skip_serializing_if = "Option::is_none")]
137 pub username: Option<String>,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
141 pub inbox_id: Option<i64>,
142
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub has_inbox: Option<bool>,
146
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub dont_allow_folders_in_uploads: Option<bool>,
150
151 #[serde(skip_serializing_if = "Option::is_none")]
153 pub paths: Option<Vec<String>>,
154
155 #[serde(skip_serializing_if = "Option::is_none")]
157 pub color_left: Option<String>,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub color_link: Option<String>,
162
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub color_text: Option<String>,
166
167 #[serde(skip_serializing_if = "Option::is_none")]
169 pub color_top: Option<String>,
170
171 #[serde(skip_serializing_if = "Option::is_none")]
173 pub color_top_text: Option<String>,
174}
175
176pub struct BundleHandler {
178 client: FilesClient,
179}
180
181impl BundleHandler {
182 pub fn new(client: FilesClient) -> Self {
184 Self { client }
185 }
186
187 pub async fn list(
209 &self,
210 user_id: Option<i64>,
211 cursor: Option<&str>,
212 per_page: Option<i64>,
213 ) -> Result<(Vec<BundleEntity>, PaginationInfo)> {
214 let mut params = vec![];
215 if let Some(uid) = user_id {
216 params.push(("user_id", uid.to_string()));
217 }
218 if let Some(c) = cursor {
219 params.push(("cursor", c.to_string()));
220 }
221 if let Some(pp) = per_page {
222 params.push(("per_page", pp.to_string()));
223 }
224
225 let query = if params.is_empty() {
226 String::new()
227 } else {
228 format!(
229 "?{}",
230 params
231 .iter()
232 .map(|(k, v)| format!("{}={}", k, v))
233 .collect::<Vec<_>>()
234 .join("&")
235 )
236 };
237
238 let response = self.client.get_raw(&format!("/bundles{}", query)).await?;
239 let bundles: Vec<BundleEntity> = serde_json::from_value(response)?;
240
241 let pagination = PaginationInfo {
242 cursor_next: None,
243 cursor_prev: None,
244 };
245
246 Ok((bundles, pagination))
247 }
248
249 pub async fn get(&self, id: i64) -> Result<BundleEntity> {
257 let response = self.client.get_raw(&format!("/bundles/{}", id)).await?;
258 Ok(serde_json::from_value(response)?)
259 }
260
261 #[allow(clippy::too_many_arguments)]
277 pub async fn create(
278 &self,
279 paths: Vec<String>,
280 password: Option<&str>,
281 expires_at: Option<&str>,
282 max_uses: Option<i64>,
283 description: Option<&str>,
284 note: Option<&str>,
285 code: Option<&str>,
286 require_registration: Option<bool>,
287 permissions: Option<&str>,
288 ) -> Result<BundleEntity> {
289 let mut body = json!({
290 "paths": paths,
291 });
292
293 if let Some(p) = password {
294 body["password"] = json!(p);
295 }
296 if let Some(e) = expires_at {
297 body["expires_at"] = json!(e);
298 }
299 if let Some(m) = max_uses {
300 body["max_uses"] = json!(m);
301 }
302 if let Some(d) = description {
303 body["description"] = json!(d);
304 }
305 if let Some(n) = note {
306 body["note"] = json!(n);
307 }
308 if let Some(c) = code {
309 body["code"] = json!(c);
310 }
311 if let Some(r) = require_registration {
312 body["require_registration"] = json!(r);
313 }
314 if let Some(perm) = permissions {
315 body["permissions"] = json!(perm);
316 }
317
318 let response = self.client.post_raw("/bundles", body).await?;
319 Ok(serde_json::from_value(response)?)
320 }
321
322 #[allow(clippy::too_many_arguments)]
335 pub async fn update(
336 &self,
337 id: i64,
338 password: Option<&str>,
339 expires_at: Option<&str>,
340 max_uses: Option<i64>,
341 description: Option<&str>,
342 note: Option<&str>,
343 ) -> Result<BundleEntity> {
344 let mut body = json!({});
345
346 if let Some(p) = password {
347 body["password"] = json!(p);
348 }
349 if let Some(e) = expires_at {
350 body["expires_at"] = json!(e);
351 }
352 if let Some(m) = max_uses {
353 body["max_uses"] = json!(m);
354 }
355 if let Some(d) = description {
356 body["description"] = json!(d);
357 }
358 if let Some(n) = note {
359 body["note"] = json!(n);
360 }
361
362 let response = self
363 .client
364 .patch_raw(&format!("/bundles/{}", id), body)
365 .await?;
366 Ok(serde_json::from_value(response)?)
367 }
368
369 pub async fn delete(&self, id: i64) -> Result<()> {
374 self.client.delete_raw(&format!("/bundles/{}", id)).await?;
375 Ok(())
376 }
377
378 pub async fn share(&self, id: i64, to: Vec<String>, note: Option<&str>) -> Result<()> {
388 let mut body = json!({
389 "to": to,
390 });
391
392 if let Some(n) = note {
393 body["note"] = json!(n);
394 }
395
396 self.client
397 .post_raw(&format!("/bundles/{}/share", id), body)
398 .await?;
399 Ok(())
400 }
401}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 #[test]
408 fn test_handler_creation() {
409 let client = FilesClient::builder().api_key("test-key").build().unwrap();
410 let _handler = BundleHandler::new(client);
411 }
412}