Skip to main content

wekan_cli/client/admin/
attachment_settings.rs

1use super::{
2    BatchSize, CpuThreshold, DelayMs, GcsCredentials, MaskedSecret, NonNegativeNumber,
3    SettingNumber, StorageSecret, nullable_object, present, present_object,
4};
5use serde::{Deserialize, Serialize};
6#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
7#[serde(deny_unknown_fields, rename_all = "camelCase")]
8pub struct FilesystemDocument {
9    pub enabled: bool,
10    pub read: bool,
11    pub write: bool,
12    #[serde(default, deserialize_with = "present")]
13    pub path: Option<String>,
14}
15
16#[derive(Clone, Debug, Default, Deserialize, Serialize)]
17#[serde(deny_unknown_fields, rename_all = "camelCase")]
18pub struct FilesystemUpdate {
19    #[serde(
20        default,
21        deserialize_with = "present",
22        skip_serializing_if = "Option::is_none"
23    )]
24    pub enabled: Option<bool>,
25    #[serde(
26        default,
27        deserialize_with = "present",
28        skip_serializing_if = "Option::is_none"
29    )]
30    pub read: Option<bool>,
31    #[serde(
32        default,
33        deserialize_with = "present",
34        skip_serializing_if = "Option::is_none"
35    )]
36    pub write: Option<bool>,
37    #[serde(
38        default,
39        deserialize_with = "present",
40        skip_serializing_if = "Option::is_none"
41    )]
42    pub path: Option<String>,
43}
44
45#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
46#[serde(deny_unknown_fields, rename_all = "camelCase")]
47pub struct GridfsDocument {
48    pub enabled: bool,
49    pub read: bool,
50    pub write: bool,
51}
52
53#[derive(Clone, Debug, Default, Deserialize, Serialize)]
54#[serde(deny_unknown_fields, rename_all = "camelCase")]
55pub struct GridfsUpdate {
56    #[serde(
57        default,
58        deserialize_with = "present",
59        skip_serializing_if = "Option::is_none"
60    )]
61    pub enabled: Option<bool>,
62    #[serde(
63        default,
64        deserialize_with = "present",
65        skip_serializing_if = "Option::is_none"
66    )]
67    pub read: Option<bool>,
68    #[serde(
69        default,
70        deserialize_with = "present",
71        skip_serializing_if = "Option::is_none"
72    )]
73    pub write: Option<bool>,
74}
75
76#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
77#[serde(deny_unknown_fields, rename_all = "camelCase")]
78pub struct S3Document {
79    #[serde(default, deserialize_with = "present")]
80    pub enabled: Option<bool>,
81    #[serde(default, deserialize_with = "present")]
82    pub read: Option<bool>,
83    #[serde(default, deserialize_with = "present")]
84    pub write: Option<bool>,
85    #[serde(default, deserialize_with = "present")]
86    pub endpoint: Option<String>,
87    #[serde(default, deserialize_with = "present")]
88    pub region: Option<String>,
89    #[serde(default, deserialize_with = "present")]
90    pub bucket: Option<String>,
91    #[serde(default, deserialize_with = "present")]
92    pub access_key_id: Option<String>,
93    #[serde(default, deserialize_with = "present")]
94    pub force_path_style: Option<bool>,
95    pub secret_access_key: MaskedSecret,
96    pub secret_access_key_set: bool,
97}
98
99#[derive(Clone, Debug, Default, Deserialize, Serialize)]
100#[serde(deny_unknown_fields, rename_all = "camelCase")]
101pub struct S3Update {
102    #[serde(
103        default,
104        deserialize_with = "present",
105        skip_serializing_if = "Option::is_none"
106    )]
107    pub enabled: Option<bool>,
108    #[serde(
109        default,
110        deserialize_with = "present",
111        skip_serializing_if = "Option::is_none"
112    )]
113    pub read: Option<bool>,
114    #[serde(
115        default,
116        deserialize_with = "present",
117        skip_serializing_if = "Option::is_none"
118    )]
119    pub write: Option<bool>,
120    #[serde(
121        default,
122        deserialize_with = "present",
123        skip_serializing_if = "Option::is_none"
124    )]
125    pub endpoint: Option<String>,
126    #[serde(
127        default,
128        deserialize_with = "present",
129        skip_serializing_if = "Option::is_none"
130    )]
131    pub region: Option<String>,
132    #[serde(
133        default,
134        deserialize_with = "present",
135        skip_serializing_if = "Option::is_none"
136    )]
137    pub bucket: Option<String>,
138    #[serde(
139        default,
140        deserialize_with = "present",
141        skip_serializing_if = "Option::is_none"
142    )]
143    pub access_key_id: Option<String>,
144    #[serde(
145        default,
146        deserialize_with = "present",
147        skip_serializing_if = "Option::is_none"
148    )]
149    pub force_path_style: Option<bool>,
150    #[serde(
151        default,
152        deserialize_with = "present",
153        skip_serializing_if = "Option::is_none"
154    )]
155    pub secret_access_key: Option<StorageSecret>,
156}
157
158#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
159#[serde(deny_unknown_fields, rename_all = "camelCase")]
160pub struct AzureDocument {
161    #[serde(default, deserialize_with = "present")]
162    pub enabled: Option<bool>,
163    #[serde(default, deserialize_with = "present")]
164    pub read: Option<bool>,
165    #[serde(default, deserialize_with = "present")]
166    pub write: Option<bool>,
167    #[serde(default, deserialize_with = "present")]
168    pub account_name: Option<String>,
169    #[serde(default, deserialize_with = "present")]
170    pub bucket: Option<String>,
171    pub account_key: MaskedSecret,
172    pub account_key_set: bool,
173    pub connection_string: MaskedSecret,
174    pub connection_string_set: bool,
175}
176
177#[derive(Clone, Debug, Default, Deserialize, Serialize)]
178#[serde(deny_unknown_fields, rename_all = "camelCase")]
179pub struct AzureUpdate {
180    #[serde(
181        default,
182        deserialize_with = "present",
183        skip_serializing_if = "Option::is_none"
184    )]
185    pub enabled: Option<bool>,
186    #[serde(
187        default,
188        deserialize_with = "present",
189        skip_serializing_if = "Option::is_none"
190    )]
191    pub read: Option<bool>,
192    #[serde(
193        default,
194        deserialize_with = "present",
195        skip_serializing_if = "Option::is_none"
196    )]
197    pub write: Option<bool>,
198    #[serde(
199        default,
200        deserialize_with = "present",
201        skip_serializing_if = "Option::is_none"
202    )]
203    pub account_name: Option<String>,
204    #[serde(
205        default,
206        deserialize_with = "present",
207        skip_serializing_if = "Option::is_none"
208    )]
209    pub bucket: Option<String>,
210    #[serde(
211        default,
212        deserialize_with = "present",
213        skip_serializing_if = "Option::is_none"
214    )]
215    pub account_key: Option<StorageSecret>,
216    #[serde(
217        default,
218        deserialize_with = "present",
219        skip_serializing_if = "Option::is_none"
220    )]
221    pub connection_string: Option<StorageSecret>,
222}
223
224#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
225#[serde(deny_unknown_fields, rename_all = "camelCase")]
226pub struct GcsDocument {
227    #[serde(default, deserialize_with = "present")]
228    pub enabled: Option<bool>,
229    #[serde(default, deserialize_with = "present")]
230    pub read: Option<bool>,
231    #[serde(default, deserialize_with = "present")]
232    pub write: Option<bool>,
233    #[serde(default, deserialize_with = "present")]
234    pub project_id: Option<String>,
235    #[serde(default, deserialize_with = "present")]
236    pub key_filename: Option<String>,
237    #[serde(default, deserialize_with = "present")]
238    pub bucket: Option<String>,
239    pub credentials: MaskedSecret,
240    pub credentials_set: bool,
241}
242
243#[derive(Clone, Debug, Default, Deserialize, Serialize)]
244#[serde(deny_unknown_fields, rename_all = "camelCase")]
245pub struct GcsUpdate {
246    #[serde(
247        default,
248        deserialize_with = "present",
249        skip_serializing_if = "Option::is_none"
250    )]
251    pub enabled: Option<bool>,
252    #[serde(
253        default,
254        deserialize_with = "present",
255        skip_serializing_if = "Option::is_none"
256    )]
257    pub read: Option<bool>,
258    #[serde(
259        default,
260        deserialize_with = "present",
261        skip_serializing_if = "Option::is_none"
262    )]
263    pub write: Option<bool>,
264    #[serde(
265        default,
266        deserialize_with = "present",
267        skip_serializing_if = "Option::is_none"
268    )]
269    pub project_id: Option<String>,
270    #[serde(
271        default,
272        deserialize_with = "present",
273        skip_serializing_if = "Option::is_none"
274    )]
275    pub key_filename: Option<String>,
276    #[serde(
277        default,
278        deserialize_with = "present",
279        skip_serializing_if = "Option::is_none"
280    )]
281    pub bucket: Option<String>,
282    #[serde(
283        default,
284        deserialize_with = "present",
285        skip_serializing_if = "Option::is_none"
286    )]
287    pub credentials: Option<GcsCredentials>,
288}
289
290#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
291#[serde(deny_unknown_fields, rename_all = "camelCase")]
292pub struct UploadDocument {
293    #[serde(default, deserialize_with = "present")]
294    pub max_file_size: Option<SettingNumber>,
295    #[serde(default, deserialize_with = "present")]
296    pub allowed_mime_types: Option<Vec<String>>,
297}
298
299#[derive(Clone, Debug, Default, Deserialize, Serialize)]
300#[serde(deny_unknown_fields, rename_all = "camelCase")]
301pub struct UploadUpdate {
302    #[serde(
303        default,
304        deserialize_with = "present",
305        skip_serializing_if = "Option::is_none"
306    )]
307    pub max_file_size: Option<SettingNumber>,
308    #[serde(
309        default,
310        deserialize_with = "present",
311        skip_serializing_if = "Option::is_none"
312    )]
313    pub allowed_mime_types: Option<Vec<String>>,
314}
315
316#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
317#[serde(deny_unknown_fields, rename_all = "camelCase")]
318pub struct LimitsDocument {
319    #[serde(default, deserialize_with = "present")]
320    pub attachments_upload_max_bytes: Option<NonNegativeNumber>,
321    #[serde(default, deserialize_with = "present")]
322    pub attachments_upload_blocked: Option<bool>,
323    #[serde(default, deserialize_with = "present")]
324    pub avatars_upload_blocked: Option<bool>,
325    #[serde(default, deserialize_with = "present")]
326    pub attachments_download_max_bytes: Option<NonNegativeNumber>,
327    #[serde(default, deserialize_with = "present")]
328    pub attachments_download_blocked: Option<bool>,
329    #[serde(default, deserialize_with = "present")]
330    pub api_upload_max_bytes: Option<NonNegativeNumber>,
331    #[serde(default, deserialize_with = "present")]
332    pub api_upload_blocked: Option<bool>,
333    #[serde(default, deserialize_with = "present")]
334    pub api_download_max_bytes: Option<NonNegativeNumber>,
335    #[serde(default, deserialize_with = "present")]
336    pub api_download_blocked: Option<bool>,
337}
338
339#[derive(Clone, Debug, Default, Deserialize, Serialize)]
340#[serde(deny_unknown_fields, rename_all = "camelCase")]
341pub struct LimitsUpdate {
342    #[serde(
343        default,
344        deserialize_with = "present",
345        skip_serializing_if = "Option::is_none"
346    )]
347    pub attachments_upload_max_bytes: Option<NonNegativeNumber>,
348    #[serde(
349        default,
350        deserialize_with = "present",
351        skip_serializing_if = "Option::is_none"
352    )]
353    pub attachments_upload_blocked: Option<bool>,
354    #[serde(
355        default,
356        deserialize_with = "present",
357        skip_serializing_if = "Option::is_none"
358    )]
359    pub avatars_upload_blocked: Option<bool>,
360    #[serde(
361        default,
362        deserialize_with = "present",
363        skip_serializing_if = "Option::is_none"
364    )]
365    pub attachments_download_max_bytes: Option<NonNegativeNumber>,
366    #[serde(
367        default,
368        deserialize_with = "present",
369        skip_serializing_if = "Option::is_none"
370    )]
371    pub attachments_download_blocked: Option<bool>,
372    #[serde(
373        default,
374        deserialize_with = "present",
375        skip_serializing_if = "Option::is_none"
376    )]
377    pub api_upload_max_bytes: Option<NonNegativeNumber>,
378    #[serde(
379        default,
380        deserialize_with = "present",
381        skip_serializing_if = "Option::is_none"
382    )]
383    pub api_upload_blocked: Option<bool>,
384    #[serde(
385        default,
386        deserialize_with = "present",
387        skip_serializing_if = "Option::is_none"
388    )]
389    pub api_download_max_bytes: Option<NonNegativeNumber>,
390    #[serde(
391        default,
392        deserialize_with = "present",
393        skip_serializing_if = "Option::is_none"
394    )]
395    pub api_download_blocked: Option<bool>,
396}
397
398#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
399#[serde(deny_unknown_fields, rename_all = "camelCase")]
400pub struct MigrationDocument {
401    pub auto_migrate: bool,
402    pub batch_size: BatchSize,
403    pub delay_ms: DelayMs,
404    pub cpu_threshold: CpuThreshold,
405}
406
407#[derive(Clone, Debug, Default, Deserialize, Serialize)]
408#[serde(deny_unknown_fields, rename_all = "camelCase")]
409pub struct MigrationUpdate {
410    #[serde(
411        default,
412        deserialize_with = "present",
413        skip_serializing_if = "Option::is_none"
414    )]
415    pub auto_migrate: Option<bool>,
416    #[serde(
417        default,
418        deserialize_with = "present",
419        skip_serializing_if = "Option::is_none"
420    )]
421    pub batch_size: Option<BatchSize>,
422    #[serde(
423        default,
424        deserialize_with = "present",
425        skip_serializing_if = "Option::is_none"
426    )]
427    pub delay_ms: Option<DelayMs>,
428    #[serde(
429        default,
430        deserialize_with = "present",
431        skip_serializing_if = "Option::is_none"
432    )]
433    pub cpu_threshold: Option<CpuThreshold>,
434}
435
436/// Wekan v12.57 models/attachmentStorageSettings.js WebDAV storage configuration.
437#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
438#[serde(deny_unknown_fields, rename_all = "camelCase")]
439pub struct WebdavDocument {
440    #[serde(default, deserialize_with = "present")]
441    pub enabled: Option<bool>,
442    #[serde(default, deserialize_with = "present")]
443    pub read: Option<bool>,
444    #[serde(default, deserialize_with = "present")]
445    pub write: Option<bool>,
446    #[serde(default, deserialize_with = "present")]
447    pub endpoint: Option<String>,
448    #[serde(default, deserialize_with = "present")]
449    pub username: Option<String>,
450    #[serde(default, deserialize_with = "present")]
451    pub bucket: Option<String>,
452    pub password: MaskedSecret,
453    pub password_set: bool,
454}
455#[derive(Clone, Debug, Default, Deserialize, Serialize)]
456#[serde(deny_unknown_fields, rename_all = "camelCase")]
457pub struct WebdavUpdate {
458    #[serde(
459        default,
460        deserialize_with = "present",
461        skip_serializing_if = "Option::is_none"
462    )]
463    pub enabled: Option<bool>,
464    #[serde(
465        default,
466        deserialize_with = "present",
467        skip_serializing_if = "Option::is_none"
468    )]
469    pub read: Option<bool>,
470    #[serde(
471        default,
472        deserialize_with = "present",
473        skip_serializing_if = "Option::is_none"
474    )]
475    pub write: Option<bool>,
476    #[serde(
477        default,
478        deserialize_with = "present",
479        skip_serializing_if = "Option::is_none"
480    )]
481    pub endpoint: Option<String>,
482    #[serde(
483        default,
484        deserialize_with = "present",
485        skip_serializing_if = "Option::is_none"
486    )]
487    pub username: Option<String>,
488    #[serde(
489        default,
490        deserialize_with = "present",
491        skip_serializing_if = "Option::is_none"
492    )]
493    pub bucket: Option<String>,
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub password: Option<StorageSecret>,
496}
497
498#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
499#[serde(deny_unknown_fields, rename_all = "camelCase")]
500pub struct StorageConfigDocument {
501    #[serde(default, deserialize_with = "present_object")]
502    pub webdav: Option<WebdavDocument>,
503    #[serde(default, deserialize_with = "present_object")]
504    pub filesystem: Option<FilesystemDocument>,
505    #[serde(default, deserialize_with = "present_object")]
506    pub gridfs: Option<GridfsDocument>,
507    #[serde(default, deserialize_with = "present_object")]
508    pub s3: Option<S3Document>,
509    #[serde(default, deserialize_with = "present_object")]
510    pub azure: Option<AzureDocument>,
511    #[serde(default, deserialize_with = "present_object")]
512    pub gcs: Option<GcsDocument>,
513}
514
515#[derive(Clone, Debug, Default, Deserialize, Serialize)]
516#[serde(deny_unknown_fields, rename_all = "camelCase")]
517pub struct StorageConfigUpdate {
518    #[serde(
519        default,
520        deserialize_with = "present_object",
521        skip_serializing_if = "Option::is_none"
522    )]
523    pub webdav: Option<WebdavUpdate>,
524    #[serde(
525        default,
526        deserialize_with = "present_object",
527        skip_serializing_if = "Option::is_none"
528    )]
529    pub filesystem: Option<FilesystemUpdate>,
530    #[serde(
531        default,
532        deserialize_with = "present_object",
533        skip_serializing_if = "Option::is_none"
534    )]
535    pub gridfs: Option<GridfsUpdate>,
536    #[serde(
537        default,
538        deserialize_with = "present_object",
539        skip_serializing_if = "Option::is_none"
540    )]
541    pub s3: Option<S3Update>,
542    #[serde(
543        default,
544        deserialize_with = "present_object",
545        skip_serializing_if = "Option::is_none"
546    )]
547    pub azure: Option<AzureUpdate>,
548    #[serde(
549        default,
550        deserialize_with = "present_object",
551        skip_serializing_if = "Option::is_none"
552    )]
553    pub gcs: Option<GcsUpdate>,
554}
555
556#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
557#[serde(rename_all = "lowercase")]
558pub enum StorageBackend {
559    #[default]
560    Fs,
561    Gridfs,
562    S3,
563    Azure,
564    Gcs,
565    Webdav,
566}
567#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
568#[serde(deny_unknown_fields, rename_all = "camelCase")]
569pub struct AttachmentSettingsDocument {
570    pub default_storage: StorageBackend,
571    #[serde(default, deserialize_with = "nullable_object")]
572    pub storage_config: Option<StorageConfigDocument>,
573    #[serde(default, deserialize_with = "nullable_object")]
574    pub upload_settings: Option<UploadDocument>,
575    #[serde(default, deserialize_with = "nullable_object")]
576    pub limit_settings: Option<LimitsDocument>,
577    #[serde(default, deserialize_with = "nullable_object")]
578    pub migration_settings: Option<MigrationDocument>,
579    #[serde(deserialize_with = "super::date")]
580    pub created_at: String,
581    #[serde(deserialize_with = "super::date")]
582    pub updated_at: String,
583    #[serde(default)]
584    pub created_by: Option<String>,
585    #[serde(default)]
586    pub updated_by: Option<String>,
587    #[serde(rename = "_id")]
588    pub settings_id: String,
589}
590
591#[derive(Clone, Debug, Default, Deserialize, Serialize)]
592#[serde(deny_unknown_fields, rename_all = "camelCase")]
593pub struct UpdateAttachmentSettings {
594    #[serde(
595        default,
596        deserialize_with = "present",
597        skip_serializing_if = "Option::is_none"
598    )]
599    pub default_storage: Option<StorageBackend>,
600    #[serde(
601        default,
602        deserialize_with = "present_object",
603        skip_serializing_if = "Option::is_none"
604    )]
605    pub storage_config: Option<StorageConfigUpdate>,
606    #[serde(
607        default,
608        deserialize_with = "present_object",
609        skip_serializing_if = "Option::is_none"
610    )]
611    pub upload_settings: Option<UploadUpdate>,
612    #[serde(
613        default,
614        deserialize_with = "present_object",
615        skip_serializing_if = "Option::is_none"
616    )]
617    pub limit_settings: Option<LimitsUpdate>,
618    #[serde(
619        default,
620        deserialize_with = "present_object",
621        skip_serializing_if = "Option::is_none"
622    )]
623    pub migration_settings: Option<MigrationUpdate>,
624}
625
626impl std::str::FromStr for StorageBackend {
627    type Err = &'static str;
628    fn from_str(value: &str) -> Result<Self, Self::Err> {
629        match value {
630            "fs" => Ok(Self::Fs),
631            "gridfs" => Ok(Self::Gridfs),
632            "s3" => Ok(Self::S3),
633            "azure" => Ok(Self::Azure),
634            "gcs" => Ok(Self::Gcs),
635            "webdav" => Ok(Self::Webdav),
636            _ => Err("expected fs, gridfs, s3, azure, gcs, or webdav"),
637        }
638    }
639}
640
641impl UpdateAttachmentSettings {
642    pub(crate) fn redaction_secrets(&self) -> Vec<secrecy::SecretString> {
643        let mut secrets = Vec::new();
644        if let Some(config) = &self.storage_config {
645            if let Some(secret) = config
646                .webdav
647                .as_ref()
648                .and_then(|provider| provider.password.as_ref())
649            {
650                secrets.push(secret.0.clone());
651            }
652            if let Some(provider) = &config.s3 {
653                if let Some(secret) = &provider.secret_access_key {
654                    secrets.push(secret.0.clone());
655                }
656            }
657            if let Some(provider) = &config.azure {
658                if let Some(secret) = &provider.account_key {
659                    secrets.push(secret.0.clone());
660                }
661                if let Some(secret) = &provider.connection_string {
662                    secrets.push(secret.0.clone());
663                }
664            }
665            if let Some(provider) = &config.gcs {
666                if let Some(secret) = &provider.credentials {
667                    secrets.extend(secret.redaction_secrets());
668                }
669            }
670        }
671        secrets
672    }
673}