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#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
437#[serde(deny_unknown_fields, rename_all = "camelCase")]
438pub struct StorageConfigDocument {
439    #[serde(default, deserialize_with = "present_object")]
440    pub filesystem: Option<FilesystemDocument>,
441    #[serde(default, deserialize_with = "present_object")]
442    pub gridfs: Option<GridfsDocument>,
443    #[serde(default, deserialize_with = "present_object")]
444    pub s3: Option<S3Document>,
445    #[serde(default, deserialize_with = "present_object")]
446    pub azure: Option<AzureDocument>,
447    #[serde(default, deserialize_with = "present_object")]
448    pub gcs: Option<GcsDocument>,
449}
450
451#[derive(Clone, Debug, Default, Deserialize, Serialize)]
452#[serde(deny_unknown_fields, rename_all = "camelCase")]
453pub struct StorageConfigUpdate {
454    #[serde(
455        default,
456        deserialize_with = "present_object",
457        skip_serializing_if = "Option::is_none"
458    )]
459    pub filesystem: Option<FilesystemUpdate>,
460    #[serde(
461        default,
462        deserialize_with = "present_object",
463        skip_serializing_if = "Option::is_none"
464    )]
465    pub gridfs: Option<GridfsUpdate>,
466    #[serde(
467        default,
468        deserialize_with = "present_object",
469        skip_serializing_if = "Option::is_none"
470    )]
471    pub s3: Option<S3Update>,
472    #[serde(
473        default,
474        deserialize_with = "present_object",
475        skip_serializing_if = "Option::is_none"
476    )]
477    pub azure: Option<AzureUpdate>,
478    #[serde(
479        default,
480        deserialize_with = "present_object",
481        skip_serializing_if = "Option::is_none"
482    )]
483    pub gcs: Option<GcsUpdate>,
484}
485
486#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize, Eq, PartialEq)]
487#[serde(rename_all = "lowercase")]
488pub enum StorageBackend {
489    #[default]
490    Fs,
491    Gridfs,
492    S3,
493    Azure,
494    Gcs,
495}
496#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
497#[serde(deny_unknown_fields, rename_all = "camelCase")]
498pub struct AttachmentSettingsDocument {
499    pub default_storage: StorageBackend,
500    #[serde(default, deserialize_with = "nullable_object")]
501    pub storage_config: Option<StorageConfigDocument>,
502    #[serde(default, deserialize_with = "nullable_object")]
503    pub upload_settings: Option<UploadDocument>,
504    #[serde(default, deserialize_with = "nullable_object")]
505    pub limit_settings: Option<LimitsDocument>,
506    #[serde(default, deserialize_with = "nullable_object")]
507    pub migration_settings: Option<MigrationDocument>,
508    #[serde(deserialize_with = "super::date")]
509    pub created_at: String,
510    #[serde(deserialize_with = "super::date")]
511    pub updated_at: String,
512    #[serde(default)]
513    pub created_by: Option<String>,
514    #[serde(default)]
515    pub updated_by: Option<String>,
516    #[serde(rename = "_id")]
517    pub settings_id: String,
518}
519
520#[derive(Clone, Debug, Default, Deserialize, Serialize)]
521#[serde(deny_unknown_fields, rename_all = "camelCase")]
522pub struct UpdateAttachmentSettings {
523    #[serde(
524        default,
525        deserialize_with = "present",
526        skip_serializing_if = "Option::is_none"
527    )]
528    pub default_storage: Option<StorageBackend>,
529    #[serde(
530        default,
531        deserialize_with = "present_object",
532        skip_serializing_if = "Option::is_none"
533    )]
534    pub storage_config: Option<StorageConfigUpdate>,
535    #[serde(
536        default,
537        deserialize_with = "present_object",
538        skip_serializing_if = "Option::is_none"
539    )]
540    pub upload_settings: Option<UploadUpdate>,
541    #[serde(
542        default,
543        deserialize_with = "present_object",
544        skip_serializing_if = "Option::is_none"
545    )]
546    pub limit_settings: Option<LimitsUpdate>,
547    #[serde(
548        default,
549        deserialize_with = "present_object",
550        skip_serializing_if = "Option::is_none"
551    )]
552    pub migration_settings: Option<MigrationUpdate>,
553}
554
555impl std::str::FromStr for StorageBackend {
556    type Err = &'static str;
557    fn from_str(value: &str) -> Result<Self, Self::Err> {
558        match value {
559            "fs" => Ok(Self::Fs),
560            "gridfs" => Ok(Self::Gridfs),
561            "s3" => Ok(Self::S3),
562            "azure" => Ok(Self::Azure),
563            "gcs" => Ok(Self::Gcs),
564            _ => Err("expected fs, gridfs, s3, azure, or gcs"),
565        }
566    }
567}
568
569impl UpdateAttachmentSettings {
570    pub(crate) fn redaction_secrets(&self) -> Vec<secrecy::SecretString> {
571        let mut secrets = Vec::new();
572        if let Some(config) = &self.storage_config {
573            if let Some(provider) = &config.s3 {
574                if let Some(secret) = &provider.secret_access_key {
575                    secrets.push(secret.0.clone());
576                }
577            }
578            if let Some(provider) = &config.azure {
579                if let Some(secret) = &provider.account_key {
580                    secrets.push(secret.0.clone());
581                }
582                if let Some(secret) = &provider.connection_string {
583                    secrets.push(secret.0.clone());
584                }
585            }
586            if let Some(provider) = &config.gcs {
587                if let Some(secret) = &provider.credentials {
588                    secrets.extend(secret.redaction_secrets());
589                }
590            }
591        }
592        secrets
593    }
594}