1
/*
2
 * This file is part of mailpot
3
 *
4
 * Copyright 2020 - Manos Pitsidianakis
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU Affero General Public License as
8
 * published by the Free Software Foundation, either version 3 of the
9
 * License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Affero General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Affero General Public License
17
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18
 */
19

            
20
//! Database models: [`MailingList`], [`ListOwner`], [`ListSubscription`],
21
//! [`PostPolicy`], [`SubscriptionPolicy`] and [`Post`].
22

            
23
use super::*;
24
pub mod changesets;
25

            
26
use std::borrow::Cow;
27

            
28
use melib::email::Address;
29

            
30
/// A database entry and its primary key. Derefs to its inner type.
31
///
32
/// # Example
33
///
34
/// ```rust,no_run
35
/// # use mailpot::{*, models::*};
36
/// # fn foo(db: &Connection) {
37
/// let val: Option<DbVal<MailingList>> = db.list(5).unwrap();
38
/// if let Some(list) = val {
39
///     assert_eq!(list.pk(), 5);
40
/// }
41
/// # }
42
/// ```
43
296
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
44
#[serde(transparent)]
45
139
pub struct DbVal<T: Send + Sync>(pub T, #[serde(skip)] pub i64);
46

            
47
impl<T: Send + Sync> DbVal<T> {
48
    /// Primary key.
49
    #[inline(always)]
50
60
    pub fn pk(&self) -> i64 {
51
60
        self.1
52
120
    }
53

            
54
    /// Unwrap inner value.
55
    #[inline(always)]
56
12
    pub fn into_inner(self) -> T {
57
12
        self.0
58
24
    }
59
}
60

            
61
impl<T> std::borrow::Borrow<T> for DbVal<T>
62
where
63
    T: Send + Sync + Sized,
64
{
65
    fn borrow(&self) -> &T {
66
        &self.0
67
    }
68
}
69

            
70
impl<T> std::ops::Deref for DbVal<T>
71
where
72
    T: Send + Sync,
73
{
74
    type Target = T;
75
617
    fn deref(&self) -> &T {
76
63
        &self.0
77
680
    }
78
}
79

            
80
impl<T> std::ops::DerefMut for DbVal<T>
81
where
82
    T: Send + Sync,
83
{
84
1
    fn deref_mut(&mut self) -> &mut Self::Target {
85
        &mut self.0
86
1
    }
87
}
88

            
89
impl<T> std::fmt::Display for DbVal<T>
90
where
91
    T: std::fmt::Display + Send + Sync,
92
{
93
24
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
94
24
        write!(fmt, "{}", self.0)
95
24
    }
96
}
97

            
98
/// A mailing list entry.
99
216
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
100
pub struct MailingList {
101
    /// Database primary key.
102
75
    pub pk: i64,
103
    /// Mailing list name.
104
39
    pub name: String,
105
    /// Mailing list ID (what appears in the subject tag, e.g. `[mailing-list]
106
    /// New post!`).
107
75
    pub id: String,
108
    /// Mailing list e-mail address.
109
75
    pub address: String,
110
    /// Discussion topics.
111
75
    pub topics: Vec<String>,
112
    /// Mailing list description.
113
75
    pub description: Option<String>,
114
    /// Mailing list archive URL.
115
75
    pub archive_url: Option<String>,
116
}
117

            
118
impl std::fmt::Display for MailingList {
119
22
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
120
22
        if let Some(description) = self.description.as_ref() {
121
            write!(
122
                fmt,
123
                "[#{} {}] {} <{}>: {}",
124
                self.pk, self.id, self.name, self.address, description
125
            )
126
        } else {
127
22
            write!(
128
                fmt,
129
                "[#{} {}] {} <{}>",
130
                self.pk, self.id, self.name, self.address
131
            )
132
        }
133
22
    }
134
}
135

            
136
impl MailingList {
137
    /// Mailing list display name.
138
    ///
139
    /// # Example
140
    ///
141
    /// ```rust
142
    /// # fn main() -> mailpot::Result<()> {
143
    #[doc = include_str!("./doctests/db_setup.rs.inc")]
144
    /// assert_eq!(
145
    ///     &list.display_name(),
146
    ///     "\"foobar chat\" <foo-chat@example.com>"
147
    /// );
148
    /// # Ok(())
149
    /// # }
150
11
    pub fn display_name(&self) -> String {
151
11
        format!("\"{}\" <{}>", self.name, self.address)
152
11
    }
153

            
154
    #[inline]
155
    /// Request subaddress.
156
    ///
157
    /// # Example
158
    ///
159
    /// ```rust
160
    /// # fn main() -> mailpot::Result<()> {
161
    #[doc = include_str!("./doctests/db_setup.rs.inc")]
162
    /// assert_eq!(&list.request_subaddr(), "foo-chat+request@example.com");
163
    /// # Ok(())
164
    /// # }
165
55
    pub fn request_subaddr(&self) -> String {
166
55
        let p = self.address.split('@').collect::<Vec<&str>>();
167
55
        format!("{}+request@{}", p[0], p[1])
168
55
    }
169

            
170
    /// Value of `List-Id` header.
171
    ///
172
    /// See RFC2919 Section 3: <https://www.rfc-editor.org/rfc/rfc2919>
173
    ///
174
    /// # Example
175
    ///
176
    /// ```rust
177
    /// # fn main() -> mailpot::Result<()> {
178
    #[doc = include_str!("./doctests/db_setup.rs.inc")]
179
    /// assert_eq!(
180
    ///     &list.id_header(),
181
    ///     "Hello world, from foo-chat list <foo-chat.example.com>");
182
    /// # Ok(())
183
    /// # }
184
31
    pub fn id_header(&self) -> String {
185
31
        let p = self.address.split('@').collect::<Vec<&str>>();
186
93
        format!(
187
            "{}{}<{}.{}>",
188
31
            self.description.as_deref().unwrap_or(""),
189
32
            self.description.as_ref().map(|_| " ").unwrap_or(""),
190
            self.id,
191
31
            p[1]
192
        )
193
31
    }
194

            
195
    /// Value of `List-Help` header.
196
    ///
197
    /// See RFC2369 Section 3.1: <https://www.rfc-editor.org/rfc/rfc2369#section-3.1>
198
    ///
199
    /// # Example
200
    ///
201
    /// ```rust
202
    /// # fn main() -> mailpot::Result<()> {
203
    #[doc = include_str!("./doctests/db_setup.rs.inc")]
204
    /// assert_eq!(
205
    ///     &list.help_header().unwrap(),
206
    ///     "<mailto:foo-chat+request@example.com?subject=help>"
207
    /// );
208
    /// # Ok(())
209
    /// # }
210
31
    pub fn help_header(&self) -> Option<String> {
211
31
        Some(format!("<mailto:{}?subject=help>", self.request_subaddr()))
212
31
    }
213

            
214
    /// Value of `List-Post` header.
215
    ///
216
    /// See RFC2369 Section 3.4: <https://www.rfc-editor.org/rfc/rfc2369#section-3.4>
217
    ///
218
    /// # Example
219
    ///
220
    /// ```rust
221
    /// # fn main() -> mailpot::Result<()> {
222
    #[doc = include_str!("./doctests/db_setup.rs.inc")]
223
    /// assert_eq!(&list.post_header(None).unwrap(), "NO");
224
    /// assert_eq!(
225
    ///     &list.post_header(Some(&post_policy)).unwrap(),
226
    ///     "<mailto:foo-chat@example.com>"
227
    /// );
228
    /// # Ok(())
229
    /// # }
230
32
    pub fn post_header(&self, policy: Option<&PostPolicy>) -> Option<String> {
231
64
        Some(policy.map_or_else(
232
3
            || "NO".to_string(),
233
61
            |p| {
234
29
                if p.announce_only {
235
1
                    "NO".to_string()
236
                } else {
237
28
                    format!("<mailto:{}>", self.address)
238
                }
239
29
            },
240
        ))
241
32
    }
242

            
243
    /// Value of `List-Unsubscribe` header.
244
    ///
245
    /// See RFC2369 Section 3.2: <https://www.rfc-editor.org/rfc/rfc2369#section-3.2>
246
    ///
247
    /// # Example
248
    ///
249
    /// ```rust
250
    /// # fn main() -> mailpot::Result<()> {
251
    #[doc = include_str!("./doctests/db_setup.rs.inc")]
252
    /// assert_eq!(
253
    ///     &list.unsubscribe_header(Some(&sub_policy)).unwrap(),
254
    ///     "<mailto:foo-chat+request@example.com?subject=unsubscribe>"
255
    /// );
256
    /// # Ok(())
257
    /// # }
258
27
    pub fn unsubscribe_header(&self, policy: Option<&SubscriptionPolicy>) -> Option<String> {
259
54
        policy.map_or_else(
260
26
            || None,
261
28
            |_| {
262
1
                Some(format!(
263
                    "<mailto:{}?subject=unsubscribe>",
264
1
                    self.request_subaddr()
265
                ))
266
1
            },
267
        )
268
27
    }
269

            
270
    /// Value of `List-Subscribe` header.
271
    ///
272
    /// See RFC2369 Section 3.3: <https://www.rfc-editor.org/rfc/rfc2369#section-3.3>
273
    ///
274
    /// # Example
275
    ///
276
    /// ```rust
277
    /// # fn main() -> mailpot::Result<()> {
278
    #[doc = include_str!("./doctests/db_setup.rs.inc")]
279
    /// assert_eq!(
280
    ///     &list.subscribe_header(Some(&sub_policy)).unwrap(),
281
    ///     "<mailto:foo-chat+request@example.com?subject=subscribe>",
282
    /// );
283
    /// # Ok(())
284
    /// # }
285
    /// ```
286
27
    pub fn subscribe_header(&self, policy: Option<&SubscriptionPolicy>) -> Option<String> {
287
54
        policy.map_or_else(
288
26
            || None,
289
28
            |_| {
290
1
                Some(format!(
291
                    "<mailto:{}?subject=subscribe>",
292
1
                    self.request_subaddr()
293
                ))
294
1
            },
295
        )
296
27
    }
297

            
298
    /// Value of `List-Archive` header.
299
    ///
300
    /// See RFC2369 Section 3.6: <https://www.rfc-editor.org/rfc/rfc2369#section-3.6>
301
    ///
302
    /// # Example
303
    ///
304
    /// ```rust
305
    /// # fn main() -> mailpot::Result<()> {
306
    #[doc = include_str!("./doctests/db_setup.rs.inc")]
307
    /// assert_eq!(
308
    ///     &list.archive_header().unwrap(),
309
    ///     "<https://lists.example.com>"
310
    /// );
311
    /// # Ok(())
312
    /// # }
313
    /// ```
314
27
    pub fn archive_header(&self) -> Option<String> {
315
28
        self.archive_url.as_ref().map(|url| format!("<{}>", url))
316
27
    }
317

            
318
    /// List address as a [`melib::Address`]
319
38
    pub fn address(&self) -> Address {
320
38
        Address::new(Some(self.name.clone()), self.address.clone())
321
38
    }
322

            
323
    /// List unsubscribe action as a [`MailtoAddress`].
324
2
    pub fn unsubscription_mailto(&self) -> MailtoAddress {
325
2
        MailtoAddress {
326
2
            address: self.request_subaddr(),
327
2
            subject: Some("unsubscribe".to_string()),
328
        }
329
2
    }
330

            
331
    /// List subscribe action as a [`MailtoAddress`].
332
3
    pub fn subscription_mailto(&self) -> MailtoAddress {
333
3
        MailtoAddress {
334
3
            address: self.request_subaddr(),
335
3
            subject: Some("subscribe".to_string()),
336
        }
337
3
    }
338

            
339
    /// List owner as a [`MailtoAddress`].
340
2
    pub fn owner_mailto(&self) -> MailtoAddress {
341
2
        let p = self.address.split('@').collect::<Vec<&str>>();
342
2
        MailtoAddress {
343
2
            address: format!("{}+owner@{}", p[0], p[1]),
344
2
            subject: None,
345
        }
346
2
    }
347

            
348
    /// List archive url value.
349
    pub fn archive_url(&self) -> Option<&str> {
350
        self.archive_url.as_deref()
351
    }
352

            
353
    /// Insert all available list headers.
354
17
    pub fn insert_headers(
355
        &self,
356
        draft: &mut melib::Draft,
357
        post_policy: Option<&PostPolicy>,
358
        subscription_policy: Option<&SubscriptionPolicy>,
359
    ) {
360
119
        for (hdr, val) in [
361
17
            ("List-Id", Some(self.id_header())),
362
17
            ("List-Help", self.help_header()),
363
17
            ("List-Post", self.post_header(post_policy)),
364
17
            (
365
                "List-Unsubscribe",
366
17
                self.unsubscribe_header(subscription_policy),
367
            ),
368
17
            ("List-Subscribe", self.subscribe_header(subscription_policy)),
369
17
            ("List-Archive", self.archive_header()),
370
        ] {
371
204
            if let Some(val) = val {
372
102
                draft
373
                    .headers
374
102
                    .insert(melib::HeaderName::try_from(hdr).unwrap(), val);
375
            }
376
102
        }
377
17
    }
378

            
379
    /// Generate help e-mail body containing information on how to subscribe,
380
    /// unsubscribe, post and how to contact the list owners.
381
1
    pub fn generate_help_email(
382
        &self,
383
        post_policy: Option<&PostPolicy>,
384
        subscription_policy: Option<&SubscriptionPolicy>,
385
    ) -> String {
386
4
        format!(
387
            "Help for {list_name}\n\n{subscribe}\n\n{post}\n\nTo contact the list owners, send an \
388
             e-mail to {contact}\n",
389
            list_name = self.name,
390
1
            subscribe = subscription_policy.map_or(
391
1
                Cow::Borrowed("This list is not open to subscriptions."),
392
1
                |p| if p.open {
393
                    Cow::Owned(format!(
394
                        "Anyone can subscribe without restrictions. Send an e-mail to {} with the \
395
                         subject `subscribe`.",
396
                        self.request_subaddr(),
397
                    ))
398
                } else if p.manual {
399
                    Cow::Borrowed(
400
                        "The list owners must manually add you to the list of subscriptions.",
401
                    )
402
                } else if p.request {
403
                    Cow::Owned(format!(
404
                        "Anyone can request to subscribe. Send an e-mail to {} with the subject \
405
                         `subscribe` and a confirmation will be sent to you when your request is \
406
                         approved.",
407
                        self.request_subaddr(),
408
                    ))
409
                } else {
410
                    Cow::Borrowed("Please contact the list owners for details on how to subscribe.")
411
                }
412
            ),
413
2
            post = post_policy.map_or(Cow::Borrowed("This list does not allow posting."), |p| {
414
1
                if p.announce_only {
415
                    Cow::Borrowed(
416
                        "This list is announce only, which means that you can only receive posts \
417
                         from the list owners.",
418
                    )
419
1
                } else if p.subscription_only {
420
                    Cow::Owned(format!(
421
                        "Only list subscriptions can post to this list. Send your post to {}",
422
                        self.address
423
                    ))
424
1
                } else if p.approval_needed {
425
                    Cow::Owned(format!(
426
                        "Anyone can post, but approval from list owners is required if they are \
427
                         not subscribed. Send your post to {}",
428
                        self.address
429
                    ))
430
                } else {
431
1
                    Cow::Borrowed("This list does not allow posting.")
432
                }
433
1
            }),
434
1
            contact = self.owner_mailto().address,
435
        )
436
1
    }
437

            
438
    /// Utility function to get a `Vec<String>` -which is the expected type of
439
    /// the `topics` field- from a `serde_json::Value`, which is the value
440
    /// stored in the `topics` column in `sqlite3`.
441
    ///
442
    /// # Example
443
    ///
444
    /// ```rust
445
    /// # use mailpot::models::MailingList;
446
    /// use serde_json::Value;
447
    ///
448
    /// # fn main() -> Result<(), serde_json::Error> {
449
    /// let value: Value = serde_json::from_str(r#"["fruits","vegetables"]"#)?;
450
    /// assert_eq!(
451
    ///     MailingList::topics_from_json_value(value),
452
    ///     Ok(vec!["fruits".to_string(), "vegetables".to_string()])
453
    /// );
454
    ///
455
    /// let value: Value = serde_json::from_str(r#"{"invalid":"value"}"#)?;
456
    /// assert!(MailingList::topics_from_json_value(value).is_err());
457
    /// # Ok(())
458
    /// # }
459
    /// ```
460
117
    pub fn topics_from_json_value(
461
        v: serde_json::Value,
462
    ) -> std::result::Result<Vec<String>, rusqlite::Error> {
463
1
        let err_fn = || {
464
1
            rusqlite::Error::FromSqlConversionFailure(
465
                8,
466
1
                rusqlite::types::Type::Text,
467
2
                anyhow::Error::msg(
468
                    "topics column must be a json array of strings serialized as a string, e.g. \
469
                     \"[]\" or \"['topicA', 'topicB']\"",
470
                )
471
                .into(),
472
            )
473
1
        };
474
117
        v.as_array()
475
116
            .map(|arr| {
476
116
                arr.iter()
477
2
                    .map(|v| v.as_str().map(str::to_string))
478
                    .collect::<Option<Vec<String>>>()
479
116
            })
480
1
            .ok_or_else(err_fn)?
481
            .ok_or_else(err_fn)
482
117
    }
483
}
484

            
485
/// A mailing list subscription entry.
486
125
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
487
pub struct ListSubscription {
488
    /// Database primary key.
489
43
    pub pk: i64,
490
    /// Mailing list foreign key (See [`MailingList`]).
491
43
    pub list: i64,
492
    /// Subscription's e-mail address.
493
43
    pub address: String,
494
    /// Subscription's name, optional.
495
43
    pub name: Option<String>,
496
    /// Subscription's account foreign key, optional.
497
9
    pub account: Option<i64>,
498
    /// Whether this subscription is enabled.
499
43
    pub enabled: bool,
500
    /// Whether the e-mail address is verified.
501
43
    pub verified: bool,
502
    /// Whether subscription wishes to receive list posts as a periodical digest
503
    /// e-mail.
504
43
    pub digest: bool,
505
    /// Whether subscription wishes their e-mail address hidden from public
506
    /// view.
507
43
    pub hide_address: bool,
508
    /// Whether subscription wishes to receive mailing list post duplicates,
509
    /// i.e. posts addressed to them and the mailing list to which they are
510
    /// subscribed.
511
43
    pub receive_duplicates: bool,
512
    /// Whether subscription wishes to receive their own mailing list posts from
513
    /// the mailing list, as a confirmation.
514
43
    pub receive_own_posts: bool,
515
    /// Whether subscription wishes to receive a plain confirmation for their
516
    /// own mailing list posts.
517
43
    pub receive_confirmation: bool,
518
}
519

            
520
impl std::fmt::Display for ListSubscription {
521
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
522
        write!(
523
            fmt,
524
            "{} [digest: {}, hide_address: {} verified: {} {}]",
525
            self.address(),
526
            self.digest,
527
            self.hide_address,
528
            self.verified,
529
            if self.enabled {
530
                "enabled"
531
            } else {
532
                "not enabled"
533
            },
534
        )
535
    }
536
}
537

            
538
impl ListSubscription {
539
    /// Subscription address as a [`melib::Address`]
540
6
    pub fn address(&self) -> Address {
541
6
        Address::new(self.name.clone(), self.address.clone())
542
6
    }
543
}
544

            
545
/// A mailing list post policy entry.
546
///
547
/// Only one of the boolean flags must be set to true.
548
38
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
549
pub struct PostPolicy {
550
    /// Database primary key.
551
    pub pk: i64,
552
    /// Mailing list foreign key (See [`MailingList`]).
553
19
    pub list: i64,
554
    /// Whether the policy is announce only (Only list owners can submit posts,
555
    /// and everyone will receive them).
556
19
    pub announce_only: bool,
557
    /// Whether the policy is "subscription only" (Only list subscriptions can
558
    /// post).
559
19
    pub subscription_only: bool,
560
    /// Whether the policy is "approval needed" (Anyone can post, but approval
561
    /// from list owners is required if they are not subscribed).
562
19
    pub approval_needed: bool,
563
    /// Whether the policy is "open" (Anyone can post, but approval from list
564
    /// owners is required. Subscriptions are not enabled).
565
19
    pub open: bool,
566
    /// Custom policy.
567
19
    pub custom: bool,
568
}
569

            
570
impl std::fmt::Display for PostPolicy {
571
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
572
        write!(fmt, "{:?}", self)
573
    }
574
}
575

            
576
/// A mailing list owner entry.
577
19
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
578
pub struct ListOwner {
579
    /// Database primary key.
580
2
    pub pk: i64,
581
    /// Mailing list foreign key (See [`MailingList`]).
582
2
    pub list: i64,
583
    /// Mailing list owner e-mail address.
584
1
    pub address: String,
585
    /// Mailing list owner name, optional.
586
2
    pub name: Option<String>,
587
}
588

            
589
impl std::fmt::Display for ListOwner {
590
2
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
591
2
        write!(fmt, "[#{} {}] {}", self.pk, self.list, self.address())
592
2
    }
593
}
594

            
595
impl From<ListOwner> for ListSubscription {
596
    fn from(val: ListOwner) -> Self {
597
        Self {
598
            pk: 0,
599
            list: val.list,
600
            address: val.address,
601
            name: val.name,
602
            account: None,
603
            digest: false,
604
            hide_address: false,
605
            receive_duplicates: true,
606
            receive_own_posts: false,
607
            receive_confirmation: true,
608
            enabled: true,
609
            verified: true,
610
        }
611
    }
612
}
613

            
614
impl ListOwner {
615
    /// Owner address as a [`melib::Address`]
616
2
    pub fn address(&self) -> Address {
617
2
        Address::new(self.name.clone(), self.address.clone())
618
2
    }
619
}
620

            
621
/// A mailing list post entry.
622
2
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq)]
623
pub struct Post {
624
    /// Database primary key.
625
1
    pub pk: i64,
626
    /// Mailing list foreign key (See [`MailingList`]).
627
1
    pub list: i64,
628
    /// Envelope `From` of post.
629
1
    pub envelope_from: Option<String>,
630
    /// `From` header address of post.
631
1
    pub address: String,
632
    /// `Message-ID` header value of post.
633
1
    pub message_id: String,
634
    /// Post as bytes.
635
1
    pub message: Vec<u8>,
636
    /// Unix timestamp of date.
637
1
    pub timestamp: u64,
638
    /// Date header as string.
639
1
    pub datetime: String,
640
    /// Month-year as a `YYYY-mm` formatted string, for use in archives.
641
1
    pub month_year: String,
642
}
643

            
644
impl std::fmt::Debug for Post {
645
5
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
646
45
        fmt.debug_struct(stringify!(Post))
647
5
            .field("pk", &self.pk)
648
5
            .field("list", &self.list)
649
5
            .field("envelope_from", &self.envelope_from)
650
            .field("address", &self.address)
651
5
            .field("message_id", &self.message_id)
652
5
            .field("message", &String::from_utf8_lossy(&self.message))
653
5
            .field("timestamp", &self.timestamp)
654
5
            .field("datetime", &self.datetime)
655
5
            .field("month_year", &self.month_year)
656
            .finish()
657
5
    }
658
}
659

            
660
impl std::fmt::Display for Post {
661
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
662
        write!(fmt, "{:?}", self)
663
    }
664
}
665

            
666
/// A mailing list subscription policy entry.
667
///
668
/// Only one of the policy boolean flags must be set to true.
669
2
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
670
pub struct SubscriptionPolicy {
671
    /// Database primary key.
672
    pub pk: i64,
673
    /// Mailing list foreign key (See [`MailingList`]).
674
1
    pub list: i64,
675
    /// Send confirmation e-mail when subscription is finalized.
676
1
    pub send_confirmation: bool,
677
    /// Anyone can subscribe without restrictions.
678
1
    pub open: bool,
679
    /// Only list owners can manually add subscriptions.
680
1
    pub manual: bool,
681
    /// Anyone can request to subscribe.
682
1
    pub request: bool,
683
    /// Allow subscriptions, but handle it manually.
684
1
    pub custom: bool,
685
}
686

            
687
impl std::fmt::Display for SubscriptionPolicy {
688
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
689
        write!(fmt, "{:?}", self)
690
    }
691
}
692

            
693
/// An account entry.
694
6
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
695
pub struct Account {
696
    /// Database primary key.
697
3
    pub pk: i64,
698
    /// Accounts's display name, optional.
699
3
    pub name: Option<String>,
700
    /// Account's e-mail address.
701
2
    pub address: String,
702
    /// GPG public key.
703
3
    pub public_key: Option<String>,
704
    /// SSH public key.
705
3
    pub password: String,
706
    /// Whether this account is enabled.
707
3
    pub enabled: bool,
708
}
709

            
710
impl std::fmt::Display for Account {
711
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
712
        write!(fmt, "{:?}", self)
713
    }
714
}
715

            
716
/// A mailing list subscription candidate.
717
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
718
pub struct ListCandidateSubscription {
719
    /// Database primary key.
720
    pub pk: i64,
721
    /// Mailing list foreign key (See [`MailingList`]).
722
    pub list: i64,
723
    /// Subscription's e-mail address.
724
    pub address: String,
725
    /// Subscription's name, optional.
726
    pub name: Option<String>,
727
    /// Accepted, foreign key on [`ListSubscription`].
728
    pub accepted: Option<i64>,
729
}
730

            
731
impl ListCandidateSubscription {
732
    /// Subscription request address as a [`melib::Address`]
733
    pub fn address(&self) -> Address {
734
        Address::new(self.name.clone(), self.address.clone())
735
    }
736
}
737

            
738
impl std::fmt::Display for ListCandidateSubscription {
739
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
740
        write!(
741
            fmt,
742
            "List_pk: {} name: {:?} address: {} accepted: {:?}",
743
            self.list, self.name, self.address, self.accepted,
744
        )
745
    }
746
}