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
151
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
32
#[serde(transparent)]
33
69
pub struct DbVal<T>(pub T, #[serde(skip)] pub i64);
34

            
35
impl<T> DbVal<T> {
36
    /// Primary key.
37
    #[inline(always)]
38
9
    pub fn pk(&self) -> i64 {
39
9
        self.1
40
9
    }
41

            
42
    /// Unwrap inner value.
43
    #[inline(always)]
44
6
    pub fn into_inner(self) -> T {
45
6
        self.0
46
6
    }
47
}
48

            
49
impl<T> std::ops::Deref for DbVal<T> {
50
    type Target = T;
51
333
    fn deref(&self) -> &T {
52
90
        &self.0
53
333
    }
54
}
55

            
56
impl<T> std::ops::DerefMut for DbVal<T> {
57
    fn deref_mut(&mut self) -> &mut Self::Target {
58
        &mut self.0
59
    }
60
}
61

            
62
impl<T> std::fmt::Display for DbVal<T>
63
where
64
    T: std::fmt::Display,
65
{
66
19
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
67
19
        write!(fmt, "{}", self.0)
68
19
    }
69
}
70

            
71
/// A mailing list entry.
72
61
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
73
pub struct MailingList {
74
    /// Database primary key.
75
    pub pk: i64,
76
    /// Mailing list name.
77
24
    pub name: String,
78
    /// Mailing list ID (what appears in the subject tag, e.g. `[mailing-list]
79
    /// New post!`).
80
24
    pub id: String,
81
    /// Mailing list e-mail address.
82
24
    pub address: String,
83
    /// Mailing list description.
84
24
    pub description: Option<String>,
85
    /// Mailing list archive URL.
86
24
    pub archive_url: Option<String>,
87
}
88

            
89
impl std::fmt::Display for MailingList {
90
16
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
91
16
        if let Some(description) = self.description.as_ref() {
92
            write!(
93
                fmt,
94
                "[#{} {}] {} <{}>: {}",
95
                self.pk, self.id, self.name, self.address, description
96
            )
97
        } else {
98
16
            write!(
99
                fmt,
100
                "[#{} {}] {} <{}>",
101
                self.pk, self.id, self.name, self.address
102
            )
103
        }
104
16
    }
105
}
106

            
107
impl MailingList {
108
    /// Mailing list display name (e.g. `list name <list_address@example.com>`).
109
6
    pub fn display_name(&self) -> String {
110
6
        format!("\"{}\" <{}>", self.name, self.address)
111
6
    }
112

            
113
    #[inline]
114
    /// Request subaddress.
115
36
    pub fn request_subaddr(&self) -> String {
116
36
        let p = self.address.split('@').collect::<Vec<&str>>();
117
36
        format!("{}+request@{}", p[0], p[1])
118
36
    }
119

            
120
    /// Value of `List-Id` header.
121
    ///
122
    /// See RFC2919 Section 3: <https://www.rfc-editor.org/rfc/rfc2919>
123
22
    pub fn id_header(&self) -> String {
124
22
        let p = self.address.split('@').collect::<Vec<&str>>();
125
66
        format!(
126
            "{}{}<{}.{}>",
127
22
            self.description.as_deref().unwrap_or(""),
128
22
            self.description.as_ref().map(|_| " ").unwrap_or(""),
129
            self.id,
130
22
            p[1]
131
        )
132
22
    }
133

            
134
    /// Value of `List-Help` header.
135
    ///
136
    /// See RFC2369 Section 3.1: <https://www.rfc-editor.org/rfc/rfc2369#section-3.1>
137
22
    pub fn help_header(&self) -> Option<String> {
138
22
        Some(format!("<mailto:{}?subject=help>", self.request_subaddr()))
139
22
    }
140

            
141
    /// Value of `List-Post` header.
142
    ///
143
    /// See RFC2369 Section 3.4: <https://www.rfc-editor.org/rfc/rfc2369#section-3.4>
144
22
    pub fn post_header(&self, policy: Option<&PostPolicy>) -> Option<String> {
145
22
        Some(policy.map_or_else(
146
            || "NO".to_string(),
147
44
            |p| {
148
22
                if p.announce_only {
149
                    "NO".to_string()
150
                } else {
151
22
                    format!("<mailto:{}>", self.address)
152
                }
153
22
            },
154
        ))
155
22
    }
156

            
157
    /// Value of `List-Unsubscribe` header.
158
    ///
159
    /// See RFC2369 Section 3.2: <https://www.rfc-editor.org/rfc/rfc2369#section-3.2>
160
18
    pub fn unsubscribe_header(&self, policy: Option<&SubscriptionPolicy>) -> Option<String> {
161
18
        policy.map_or_else(
162
18
            || None,
163
18
            |p| {
164
                if p.open {
165
                    None
166
                } else {
167
                    Some(format!(
168
                        "<mailto:{}?subject=unsubscribe>",
169
                        self.request_subaddr()
170
                    ))
171
                }
172
            },
173
        )
174
18
    }
175

            
176
    /// Value of `List-Subscribe` header.
177
    ///
178
    /// See RFC2369 Section 3.3: <https://www.rfc-editor.org/rfc/rfc2369#section-3.3>
179
18
    pub fn subscribe_header(&self, policy: Option<&SubscriptionPolicy>) -> Option<String> {
180
18
        policy.map_or_else(
181
18
            || None,
182
18
            |p| {
183
                if p.open {
184
                    None
185
                } else {
186
                    Some(format!(
187
                        "<mailto:{}?subject=subscribe>",
188
                        self.request_subaddr()
189
                    ))
190
                }
191
            },
192
        )
193
18
    }
194

            
195
    /// Value of `List-Archive` header.
196
    ///
197
    /// See RFC2369 Section 3.6: <https://www.rfc-editor.org/rfc/rfc2369#section-3.6>
198
18
    pub fn archive_header(&self) -> Option<String> {
199
18
        self.archive_url.as_ref().map(|url| format!("<{}>", url))
200
18
    }
201

            
202
    /// List address as a [`melib::Address`]
203
26
    pub fn address(&self) -> Address {
204
26
        Address::new(Some(self.name.clone()), self.address.clone())
205
26
    }
206

            
207
    /// List unsubscribe action as a [`MailtoAddress`](super::MailtoAddress).
208
    pub fn unsubscription_mailto(&self) -> MailtoAddress {
209
        MailtoAddress {
210
            address: self.request_subaddr(),
211
            subject: Some("unsubscribe".to_string()),
212
        }
213
    }
214

            
215
    /// List subscribe action as a [`MailtoAddress`](super::MailtoAddress).
216
1
    pub fn subscription_mailto(&self) -> MailtoAddress {
217
1
        MailtoAddress {
218
1
            address: self.request_subaddr(),
219
1
            subject: Some("subscribe".to_string()),
220
        }
221
1
    }
222

            
223
    /// List owner as a [`MailtoAddress`](super::MailtoAddress).
224
2
    pub fn owner_mailto(&self) -> MailtoAddress {
225
2
        let p = self.address.split('@').collect::<Vec<&str>>();
226
2
        MailtoAddress {
227
2
            address: format!("{}+owner@{}", p[0], p[1]),
228
2
            subject: None,
229
        }
230
2
    }
231

            
232
    /// List archive url value.
233
    pub fn archive_url(&self) -> Option<&str> {
234
        self.archive_url.as_deref()
235
    }
236

            
237
    /// Insert all available list headers.
238
13
    pub fn insert_headers(
239
        &self,
240
        draft: &mut melib::Draft,
241
        post_policy: Option<&PostPolicy>,
242
        subscription_policy: Option<&SubscriptionPolicy>,
243
    ) {
244
104
        for (hdr, val) in [
245
13
            ("List-Id", Some(self.id_header())),
246
13
            ("List-Help", self.help_header()),
247
13
            ("List-Post", self.post_header(post_policy)),
248
13
            (
249
                "List-Unsubscribe",
250
13
                self.unsubscribe_header(subscription_policy),
251
            ),
252
13
            ("List-Subscribe", self.subscribe_header(subscription_policy)),
253
13
            ("List-Archive", self.archive_header()),
254
        ] {
255
78
            if let Some(val) = val {
256
78
                draft
257
                    .headers
258
78
                    .insert(melib::HeaderName::new_unchecked(hdr), val);
259
            }
260
78
        }
261
13
    }
262

            
263
    /// Generate help e-mail body containing information on how to subscribe,
264
    /// unsubscribe, post and how to contact the list owners.
265
1
    pub fn generate_help_email(
266
        &self,
267
        post_policy: Option<&PostPolicy>,
268
        subscription_policy: Option<&SubscriptionPolicy>,
269
    ) -> String {
270
4
        format!(
271
            "Help for {list_name}\n\n{subscribe}\n\n{post}\n\nTo contact the list owners, send an \
272
             e-mail to {contact}\n",
273
            list_name = self.name,
274
1
            subscribe = subscription_policy.map_or(
275
1
                Cow::Borrowed("This list is not open to subscriptions."),
276
1
                |p| if p.open {
277
                    Cow::Owned(format!(
278
                        "Anyone can subscribe without restrictions. Send an e-mail to {} with the \
279
                         subject `subscribe`.",
280
                        self.request_subaddr(),
281
                    ))
282
                } else if p.manual {
283
                    Cow::Borrowed(
284
                        "The list owners must manually add you to the list of subscriptions.",
285
                    )
286
                } else if p.request {
287
                    Cow::Owned(format!(
288
                        "Anyone can request to subscribe. Send an e-mail to {} with the subject \
289
                         `subscribe` and a confirmation will be sent to you when your request is \
290
                         approved.",
291
                        self.request_subaddr(),
292
                    ))
293
                } else {
294
                    Cow::Borrowed("Please contact the list owners for details on how to subscribe.")
295
                }
296
            ),
297
2
            post = post_policy.map_or(Cow::Borrowed("This list does not allow posting."), |p| {
298
1
                if p.announce_only {
299
                    Cow::Borrowed(
300
                        "This list is announce only, which means that you can only receive posts \
301
                         from the list owners.",
302
                    )
303
1
                } else if p.subscription_only {
304
                    Cow::Owned(format!(
305
                        "Only list subscriptions can post to this list. Send your post to {}",
306
                        self.address
307
                    ))
308
1
                } else if p.approval_needed {
309
                    Cow::Owned(format!(
310
                        "Anyone can post, but approval from list owners is required if they are \
311
                         not subscribed. Send your post to {}",
312
                        self.address
313
                    ))
314
                } else {
315
1
                    Cow::Borrowed("This list does not allow posting.")
316
                }
317
1
            }),
318
1
            contact = self.owner_mailto().address,
319
        )
320
1
    }
321
}
322

            
323
/// A mailing list subscription entry.
324
52
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
325
pub struct ListSubscription {
326
    /// Database primary key.
327
26
    pub pk: i64,
328
    /// Mailing list foreign key (See [`MailingList`]).
329
26
    pub list: i64,
330
    /// Subscription's e-mail address.
331
26
    pub address: String,
332
    /// Subscription's name, optional.
333
26
    pub name: Option<String>,
334
    /// Subscription's account foreign key, optional.
335
    pub account: Option<i64>,
336
    /// Whether this subscription is enabled.
337
26
    pub enabled: bool,
338
    /// Whether the e-mail address is verified.
339
26
    pub verified: bool,
340
    /// Whether subscription wishes to receive list posts as a periodical digest
341
    /// e-mail.
342
26
    pub digest: bool,
343
    /// Whether subscription wishes their e-mail address hidden from public
344
    /// view.
345
26
    pub hide_address: bool,
346
    /// Whether subscription wishes to receive mailing list post duplicates,
347
    /// i.e. posts addressed to them and the mailing list to which they are
348
    /// subscribed.
349
26
    pub receive_duplicates: bool,
350
    /// Whether subscription wishes to receive their own mailing list posts from
351
    /// the mailing list, as a confirmation.
352
26
    pub receive_own_posts: bool,
353
    /// Whether subscription wishes to receive a plain confirmation for their
354
    /// own mailing list posts.
355
26
    pub receive_confirmation: bool,
356
}
357

            
358
impl std::fmt::Display for ListSubscription {
359
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
360
        write!(
361
            fmt,
362
            "{} [digest: {}, hide_address: {} verified: {} {}]",
363
            self.address(),
364
            self.digest,
365
            self.hide_address,
366
            self.verified,
367
            if self.enabled {
368
                "enabled"
369
            } else {
370
                "not enabled"
371
            },
372
        )
373
    }
374
}
375

            
376
impl ListSubscription {
377
    /// Subscription address as a [`melib::Address`]
378
4
    pub fn address(&self) -> Address {
379
4
        Address::new(self.name.clone(), self.address.clone())
380
4
    }
381
}
382

            
383
/// A mailing list post policy entry.
384
///
385
/// Only one of the boolean flags must be set to true.
386
26
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
387
pub struct PostPolicy {
388
    /// Database primary key.
389
    pub pk: i64,
390
    /// Mailing list foreign key (See [`MailingList`]).
391
13
    pub list: i64,
392
    /// Whether the policy is announce only (Only list owners can submit posts,
393
    /// and everyone will receive them).
394
13
    pub announce_only: bool,
395
    /// Whether the policy is "subscription only" (Only list subscriptions can
396
    /// post).
397
13
    pub subscription_only: bool,
398
    /// Whether the policy is "approval needed" (Anyone can post, but approval
399
    /// from list owners is required if they are not subscribed).
400
13
    pub approval_needed: bool,
401
    /// Whether the policy is "open" (Anyone can post, but approval from list
402
    /// owners is required. Subscriptions are not enabled).
403
13
    pub open: bool,
404
    /// Custom policy.
405
13
    pub custom: bool,
406
}
407

            
408
impl std::fmt::Display for PostPolicy {
409
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
410
        write!(fmt, "{:?}", self)
411
    }
412
}
413

            
414
/// A mailing list owner entry.
415
2
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
416
pub struct ListOwner {
417
    /// Database primary key.
418
    pub pk: i64,
419
    /// Mailing list foreign key (See [`MailingList`]).
420
1
    pub list: i64,
421
    /// Mailing list owner e-mail address.
422
1
    pub address: String,
423
    /// Mailing list owner name, optional.
424
1
    pub name: Option<String>,
425
}
426

            
427
impl std::fmt::Display for ListOwner {
428
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
429
        write!(fmt, "[#{} {}] {}", self.pk, self.list, self.address())
430
    }
431
}
432

            
433
impl From<ListOwner> for ListSubscription {
434
    fn from(val: ListOwner) -> Self {
435
        Self {
436
            pk: 0,
437
            list: val.list,
438
            address: val.address,
439
            name: val.name,
440
            account: None,
441
            digest: false,
442
            hide_address: false,
443
            receive_duplicates: true,
444
            receive_own_posts: false,
445
            receive_confirmation: true,
446
            enabled: true,
447
            verified: true,
448
        }
449
    }
450
}
451

            
452
impl ListOwner {
453
    /// Owner address as a [`melib::Address`]
454
    pub fn address(&self) -> Address {
455
        Address::new(self.name.clone(), self.address.clone())
456
    }
457
}
458

            
459
/// A mailing list post entry.
460
2
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
461
pub struct Post {
462
    /// Database primary key.
463
    pub pk: i64,
464
    /// Mailing list foreign key (See [`MailingList`]).
465
1
    pub list: i64,
466
    /// Envelope `From` of post.
467
1
    pub envelope_from: Option<String>,
468
    /// `From` header address of post.
469
1
    pub address: String,
470
    /// `Message-ID` header value of post.
471
1
    pub message_id: String,
472
    /// Post as bytes.
473
1
    pub message: Vec<u8>,
474
    /// Unix timestamp of date.
475
1
    pub timestamp: u64,
476
    /// Datetime as string.
477
1
    pub datetime: String,
478
    /// Month-year as a `YYYY-mm` formatted string, for use in archives.
479
1
    pub month_year: String,
480
}
481

            
482
impl std::fmt::Display for Post {
483
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
484
        write!(fmt, "{:?}", self)
485
    }
486
}
487

            
488
/// A mailing list subscription policy entry.
489
///
490
/// Only one of the policy boolean flags must be set to true.
491
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
492
pub struct SubscriptionPolicy {
493
    /// Database primary key.
494
    pub pk: i64,
495
    /// Mailing list foreign key (See [`MailingList`]).
496
    pub list: i64,
497
    /// Send confirmation e-mail when subscription is finalized.
498
    pub send_confirmation: bool,
499
    /// Anyone can subscribe without restrictions.
500
    pub open: bool,
501
    /// Only list owners can manually add subscriptions.
502
    pub manual: bool,
503
    /// Anyone can request to subscribe.
504
    pub request: bool,
505
    /// Allow subscriptions, but handle it manually.
506
    pub custom: bool,
507
}
508

            
509
impl std::fmt::Display for SubscriptionPolicy {
510
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
511
        write!(fmt, "{:?}", self)
512
    }
513
}
514

            
515
/// An account entry.
516
2
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
517
pub struct Account {
518
    /// Database primary key.
519
    pub pk: i64,
520
    /// Accounts's display name, optional.
521
1
    pub name: Option<String>,
522
    /// Account's e-mail address.
523
1
    pub address: String,
524
    /// GPG public key.
525
1
    pub public_key: Option<String>,
526
    /// SSH public key.
527
1
    pub password: String,
528
    /// Whether this account is enabled.
529
1
    pub enabled: bool,
530
}
531

            
532
impl std::fmt::Display for Account {
533
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
534
        write!(fmt, "{:?}", self)
535
    }
536
}
537

            
538
/// A mailing list subscription candidate.
539
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
540
pub struct ListCandidateSubscription {
541
    /// Database primary key.
542
    pub pk: i64,
543
    /// Mailing list foreign key (See [`MailingList`]).
544
    pub list: i64,
545
    /// Subscription's e-mail address.
546
    pub address: String,
547
    /// Subscription's name, optional.
548
    pub name: Option<String>,
549
    /// Accepted, foreign key on [`ListSubscription`].
550
    pub accepted: Option<i64>,
551
}
552

            
553
impl std::fmt::Display for ListCandidateSubscription {
554
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
555
        write!(
556
            fmt,
557
            "List_pk: {} name: {:?} address: {} accepted: {:?}",
558
            self.list, self.name, self.address, self.accepted,
559
        )
560
    }
561
}