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
//! User subscriptions.
21

            
22
use log::trace;
23
use rusqlite::OptionalExtension;
24

            
25
use crate::{
26
    errors::{ErrorKind::*, *},
27
    models::{
28
        changesets::{AccountChangeset, ListSubscriptionChangeset},
29
        Account, ListCandidateSubscription, ListSubscription,
30
    },
31
    Connection, DbVal,
32
};
33

            
34
impl Connection {
35
    /// Fetch all subscriptions of a mailing list.
36
35
    pub fn list_subscriptions(&self, list_pk: i64) -> Result<Vec<DbVal<ListSubscription>>> {
37
35
        let mut stmt = self
38
            .connection
39
            .prepare("SELECT * FROM subscription WHERE list = ?;")?;
40
56
        let list_iter = stmt.query_map([&list_pk], |row| {
41
21
            let pk = row.get("pk")?;
42
21
            Ok(DbVal(
43
21
                ListSubscription {
44
21
                    pk: row.get("pk")?,
45
21
                    list: row.get("list")?,
46
21
                    address: row.get("address")?,
47
21
                    account: row.get("account")?,
48
21
                    name: row.get("name")?,
49
21
                    digest: row.get("digest")?,
50
21
                    enabled: row.get("enabled")?,
51
21
                    verified: row.get("verified")?,
52
21
                    hide_address: row.get("hide_address")?,
53
21
                    receive_duplicates: row.get("receive_duplicates")?,
54
21
                    receive_own_posts: row.get("receive_own_posts")?,
55
21
                    receive_confirmation: row.get("receive_confirmation")?,
56
21
                },
57
                pk,
58
            ))
59
21
        })?;
60

            
61
35
        let mut ret = vec![];
62
56
        for list in list_iter {
63
21
            let list = list?;
64
21
            ret.push(list);
65
        }
66
35
        Ok(ret)
67
35
    }
68

            
69
    /// Fetch mailing list subscription.
70
23
    pub fn list_subscription(&self, list_pk: i64, pk: i64) -> Result<DbVal<ListSubscription>> {
71
23
        let mut stmt = self
72
            .connection
73
            .prepare("SELECT * FROM subscription WHERE list = ? AND pk = ?;")?;
74

            
75
46
        let ret = stmt.query_row([&list_pk, &pk], |row| {
76
23
            let _pk: i64 = row.get("pk")?;
77
46
            debug_assert_eq!(pk, _pk);
78
23
            Ok(DbVal(
79
23
                ListSubscription {
80
23
                    pk,
81
23
                    list: row.get("list")?,
82
23
                    address: row.get("address")?,
83
23
                    account: row.get("account")?,
84
23
                    name: row.get("name")?,
85
23
                    digest: row.get("digest")?,
86
23
                    enabled: row.get("enabled")?,
87
23
                    verified: row.get("verified")?,
88
23
                    hide_address: row.get("hide_address")?,
89
23
                    receive_duplicates: row.get("receive_duplicates")?,
90
23
                    receive_own_posts: row.get("receive_own_posts")?,
91
23
                    receive_confirmation: row.get("receive_confirmation")?,
92
23
                },
93
23
                pk,
94
            ))
95
46
        })?;
96
23
        Ok(ret)
97
23
    }
98

            
99
    /// Fetch mailing list subscription by their address.
100
16
    pub fn list_subscription_by_address(
101
        &self,
102
        list_pk: i64,
103
        address: &str,
104
    ) -> Result<DbVal<ListSubscription>> {
105
16
        let mut stmt = self
106
            .connection
107
            .prepare("SELECT * FROM subscription WHERE list = ? AND address = ?;")?;
108

            
109
23
        let ret = stmt.query_row(rusqlite::params![&list_pk, &address], |row| {
110
7
            let pk = row.get("pk")?;
111
7
            let address_ = row.get("address")?;
112
14
            debug_assert_eq!(address, &address_);
113
7
            Ok(DbVal(
114
7
                ListSubscription {
115
                    pk,
116
7
                    list: row.get("list")?,
117
7
                    address: address_,
118
7
                    account: row.get("account")?,
119
7
                    name: row.get("name")?,
120
7
                    digest: row.get("digest")?,
121
7
                    enabled: row.get("enabled")?,
122
7
                    verified: row.get("verified")?,
123
7
                    hide_address: row.get("hide_address")?,
124
7
                    receive_duplicates: row.get("receive_duplicates")?,
125
7
                    receive_own_posts: row.get("receive_own_posts")?,
126
7
                    receive_confirmation: row.get("receive_confirmation")?,
127
7
                },
128
                pk,
129
            ))
130
23
        })?;
131
7
        Ok(ret)
132
16
    }
133

            
134
    /// Add subscription to mailing list.
135
17
    pub fn add_subscription(
136
        &self,
137
        list_pk: i64,
138
        mut new_val: ListSubscription,
139
    ) -> Result<DbVal<ListSubscription>> {
140
17
        new_val.list = list_pk;
141
34
        let mut stmt = self
142
            .connection
143
            .prepare(
144
                "INSERT INTO subscription(list, address, account, name, enabled, digest, \
145
                 verified, hide_address, receive_duplicates, receive_own_posts, \
146
                 receive_confirmation) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *;",
147
            )
148
            .unwrap();
149
17
        let val = stmt.query_row(
150
17
            rusqlite::params![
151
17
                &new_val.list,
152
17
                &new_val.address,
153
17
                &new_val.account,
154
17
                &new_val.name,
155
17
                &new_val.enabled,
156
17
                &new_val.digest,
157
17
                &new_val.verified,
158
17
                &new_val.hide_address,
159
17
                &new_val.receive_duplicates,
160
17
                &new_val.receive_own_posts,
161
17
                &new_val.receive_confirmation
162
            ],
163
17
            |row| {
164
17
                let pk = row.get("pk")?;
165
17
                Ok(DbVal(
166
17
                    ListSubscription {
167
                        pk,
168
17
                        list: row.get("list")?,
169
17
                        address: row.get("address")?,
170
17
                        name: row.get("name")?,
171
17
                        account: row.get("account")?,
172
17
                        digest: row.get("digest")?,
173
17
                        enabled: row.get("enabled")?,
174
17
                        verified: row.get("verified")?,
175
17
                        hide_address: row.get("hide_address")?,
176
17
                        receive_duplicates: row.get("receive_duplicates")?,
177
17
                        receive_own_posts: row.get("receive_own_posts")?,
178
17
                        receive_confirmation: row.get("receive_confirmation")?,
179
17
                    },
180
                    pk,
181
                ))
182
17
            },
183
        )?;
184
17
        trace!("add_subscription {:?}.", &val);
185
        // table entry might be modified by triggers, so don't rely on RETURNING value.
186
17
        self.list_subscription(list_pk, val.pk())
187
17
    }
188

            
189
    /// Fetch all candidate subscriptions of a mailing list.
190
    pub fn list_subscription_requests(
191
        &self,
192
        list_pk: i64,
193
    ) -> Result<Vec<DbVal<ListCandidateSubscription>>> {
194
        let mut stmt = self
195
            .connection
196
            .prepare("SELECT * FROM candidate_subscription WHERE list = ?;")?;
197
        let list_iter = stmt.query_map([&list_pk], |row| {
198
            let pk = row.get("pk")?;
199
            Ok(DbVal(
200
                ListCandidateSubscription {
201
                    pk: row.get("pk")?,
202
                    list: row.get("list")?,
203
                    address: row.get("address")?,
204
                    name: row.get("name")?,
205
                    accepted: row.get("accepted")?,
206
                },
207
                pk,
208
            ))
209
        })?;
210

            
211
        let mut ret = vec![];
212
        for list in list_iter {
213
            let list = list?;
214
            ret.push(list);
215
        }
216
        Ok(ret)
217
    }
218

            
219
    /// Create subscription candidate.
220
1
    pub fn add_candidate_subscription(
221
        &self,
222
        list_pk: i64,
223
        mut new_val: ListSubscription,
224
    ) -> Result<DbVal<ListCandidateSubscription>> {
225
1
        new_val.list = list_pk;
226
2
        let mut stmt = self.connection.prepare(
227
            "INSERT INTO candidate_subscription(list, address, name, accepted) VALUES(?, ?, ?, ?) \
228
             RETURNING *;",
229
        )?;
230
1
        let val = stmt.query_row(
231
1
            rusqlite::params![&new_val.list, &new_val.address, &new_val.name, None::<i64>,],
232
1
            |row| {
233
1
                let pk = row.get("pk")?;
234
1
                Ok(DbVal(
235
1
                    ListCandidateSubscription {
236
                        pk,
237
1
                        list: row.get("list")?,
238
1
                        address: row.get("address")?,
239
1
                        name: row.get("name")?,
240
1
                        accepted: row.get("accepted")?,
241
1
                    },
242
                    pk,
243
                ))
244
1
            },
245
        )?;
246
1
        drop(stmt);
247

            
248
1
        trace!("add_candidate_subscription {:?}.", &val);
249
        // table entry might be modified by triggers, so don't rely on RETURNING value.
250
1
        self.candidate_subscription(val.pk())
251
1
    }
252

            
253
    /// Fetch subscription candidate by primary key.
254
2
    pub fn candidate_subscription(&self, pk: i64) -> Result<DbVal<ListCandidateSubscription>> {
255
2
        let mut stmt = self
256
            .connection
257
            .prepare("SELECT * FROM candidate_subscription WHERE pk = ?;")?;
258
2
        let val = stmt
259
4
            .query_row(rusqlite::params![&pk], |row| {
260
2
                let _pk: i64 = row.get("pk")?;
261
4
                debug_assert_eq!(pk, _pk);
262
2
                Ok(DbVal(
263
2
                    ListCandidateSubscription {
264
2
                        pk,
265
2
                        list: row.get("list")?,
266
2
                        address: row.get("address")?,
267
2
                        name: row.get("name")?,
268
2
                        accepted: row.get("accepted")?,
269
2
                    },
270
2
                    pk,
271
                ))
272
2
            })
273
            .map_err(|err| {
274
                if matches!(err, rusqlite::Error::QueryReturnedNoRows) {
275
                    Error::from(err)
276
                        .chain_err(|| NotFound("Candidate subscription with this pk not found!"))
277
                } else {
278
                    err.into()
279
                }
280
2
            })?;
281

            
282
2
        Ok(val)
283
2
    }
284

            
285
    /// Accept subscription candidate.
286
1
    pub fn accept_candidate_subscription(&self, pk: i64) -> Result<DbVal<ListSubscription>> {
287
2
        let val = self.connection.query_row(
288
            "INSERT INTO subscription(list, address, name, enabled, digest, verified, \
289
             hide_address, receive_duplicates, receive_own_posts, receive_confirmation) SELECT \
290
             list, address, name, 1, 0, 0, 0, 1, 1, 0 FROM candidate_subscription WHERE pk = ? \
291
             RETURNING *;",
292
1
            rusqlite::params![&pk],
293
1
            |row| {
294
1
                let pk = row.get("pk")?;
295
1
                Ok(DbVal(
296
1
                    ListSubscription {
297
                        pk,
298
1
                        list: row.get("list")?,
299
1
                        address: row.get("address")?,
300
1
                        account: row.get("account")?,
301
1
                        name: row.get("name")?,
302
1
                        digest: row.get("digest")?,
303
1
                        enabled: row.get("enabled")?,
304
1
                        verified: row.get("verified")?,
305
1
                        hide_address: row.get("hide_address")?,
306
1
                        receive_duplicates: row.get("receive_duplicates")?,
307
1
                        receive_own_posts: row.get("receive_own_posts")?,
308
1
                        receive_confirmation: row.get("receive_confirmation")?,
309
1
                    },
310
                    pk,
311
                ))
312
1
            },
313
        )?;
314

            
315
1
        trace!("accept_candidate_subscription {:?}.", &val);
316
        // table entry might be modified by triggers, so don't rely on RETURNING value.
317
1
        let ret = self.list_subscription(val.list, val.pk())?;
318

            
319
        // assert that [ref:accept_candidate] trigger works.
320
1
        debug_assert_eq!(Some(ret.pk), self.candidate_subscription(pk)?.accepted);
321
1
        Ok(ret)
322
1
    }
323

            
324
    /// Remove a subscription by their address.
325
3
    pub fn remove_subscription(&self, list_pk: i64, address: &str) -> Result<()> {
326
6
        self.connection
327
            .query_row(
328
                "DELETE FROM subscription WHERE list = ? AND address = ? RETURNING *;",
329
3
                rusqlite::params![&list_pk, &address],
330
2
                |_| Ok(()),
331
            )
332
1
            .map_err(|err| {
333
1
                if matches!(err, rusqlite::Error::QueryReturnedNoRows) {
334
2
                    Error::from(err).chain_err(|| NotFound("list or list owner not found!"))
335
                } else {
336
                    err.into()
337
                }
338
2
            })?;
339

            
340
2
        Ok(())
341
3
    }
342

            
343
    /// Update a mailing list subscription.
344
1
    pub fn update_subscription(&self, change_set: ListSubscriptionChangeset) -> Result<()> {
345
1
        let pk = self
346
1
            .list_subscription_by_address(change_set.list, &change_set.address)?
347
1
            .pk;
348
1
        if matches!(
349
1
            change_set,
350
            ListSubscriptionChangeset {
351
                list: _,
352
                address: _,
353
                account: None,
354
                name: None,
355
                digest: None,
356
                verified: None,
357
                hide_address: None,
358
                receive_duplicates: None,
359
                receive_own_posts: None,
360
                receive_confirmation: None,
361
                enabled: None,
362
            }
363
        ) {
364
            return Ok(());
365
        }
366

            
367
        let ListSubscriptionChangeset {
368
1
            list,
369
            address: _,
370
1
            name,
371
1
            account,
372
1
            digest,
373
1
            enabled,
374
1
            verified,
375
1
            hide_address,
376
1
            receive_duplicates,
377
1
            receive_own_posts,
378
1
            receive_confirmation,
379
        } = change_set;
380
2
        let tx = self.savepoint(Some(stringify!(update_subscription)))?;
381

            
382
        macro_rules! update {
383
            ($field:tt) => {{
384
1
                if let Some($field) = $field {
385
                    tx.connection.execute(
386
                        concat!(
387
                            "UPDATE subscription SET ",
388
                            stringify!($field),
389
                            " = ? WHERE list = ? AND pk = ?;"
390
                        ),
391
                        rusqlite::params![&$field, &list, &pk],
392
                    )?;
393
                }
394
            }};
395
        }
396
1
        update!(name);
397
1
        update!(account);
398
1
        update!(digest);
399
1
        update!(enabled);
400
1
        update!(verified);
401
1
        update!(hide_address);
402
1
        update!(receive_duplicates);
403
1
        update!(receive_own_posts);
404
1
        update!(receive_confirmation);
405

            
406
2
        tx.commit()?;
407
1
        Ok(())
408
1
    }
409

            
410
    /// Fetch account by pk.
411
2
    pub fn account(&self, pk: i64) -> Result<Option<DbVal<Account>>> {
412
2
        let mut stmt = self
413
            .connection
414
            .prepare("SELECT * FROM account WHERE pk = ?;")?;
415

            
416
2
        let ret = stmt
417
3
            .query_row(rusqlite::params![&pk], |row| {
418
1
                let _pk: i64 = row.get("pk")?;
419
2
                debug_assert_eq!(pk, _pk);
420
1
                Ok(DbVal(
421
1
                    Account {
422
1
                        pk,
423
1
                        name: row.get("name")?,
424
1
                        address: row.get("address")?,
425
1
                        public_key: row.get("public_key")?,
426
1
                        password: row.get("password")?,
427
1
                        enabled: row.get("enabled")?,
428
1
                    },
429
1
                    pk,
430
                ))
431
1
            })
432
2
            .optional()?;
433
2
        Ok(ret)
434
2
    }
435

            
436
    /// Fetch account by address.
437
12
    pub fn account_by_address(&self, address: &str) -> Result<Option<DbVal<Account>>> {
438
12
        let mut stmt = self
439
            .connection
440
            .prepare("SELECT * FROM account WHERE address = ?;")?;
441

            
442
12
        let ret = stmt
443
21
            .query_row(rusqlite::params![&address], |row| {
444
9
                let pk = row.get("pk")?;
445
9
                Ok(DbVal(
446
9
                    Account {
447
                        pk,
448
9
                        name: row.get("name")?,
449
9
                        address: row.get("address")?,
450
9
                        public_key: row.get("public_key")?,
451
9
                        password: row.get("password")?,
452
9
                        enabled: row.get("enabled")?,
453
9
                    },
454
                    pk,
455
                ))
456
9
            })
457
12
            .optional()?;
458
12
        Ok(ret)
459
12
    }
460

            
461
    /// Fetch all subscriptions of an account by primary key.
462
2
    pub fn account_subscriptions(&self, pk: i64) -> Result<Vec<DbVal<ListSubscription>>> {
463
2
        let mut stmt = self
464
            .connection
465
            .prepare("SELECT * FROM subscription WHERE account = ?;")?;
466
4
        let list_iter = stmt.query_map([&pk], |row| {
467
2
            let pk = row.get("pk")?;
468
2
            Ok(DbVal(
469
2
                ListSubscription {
470
2
                    pk: row.get("pk")?,
471
2
                    list: row.get("list")?,
472
2
                    address: row.get("address")?,
473
2
                    account: row.get("account")?,
474
2
                    name: row.get("name")?,
475
2
                    digest: row.get("digest")?,
476
2
                    enabled: row.get("enabled")?,
477
2
                    verified: row.get("verified")?,
478
2
                    hide_address: row.get("hide_address")?,
479
2
                    receive_duplicates: row.get("receive_duplicates")?,
480
2
                    receive_own_posts: row.get("receive_own_posts")?,
481
2
                    receive_confirmation: row.get("receive_confirmation")?,
482
2
                },
483
                pk,
484
            ))
485
2
        })?;
486

            
487
2
        let mut ret = vec![];
488
4
        for list in list_iter {
489
2
            let list = list?;
490
2
            ret.push(list);
491
        }
492
2
        Ok(ret)
493
2
    }
494

            
495
    /// Fetch all accounts.
496
3
    pub fn accounts(&self) -> Result<Vec<DbVal<Account>>> {
497
3
        let mut stmt = self
498
            .connection
499
            .prepare("SELECT * FROM account ORDER BY pk ASC;")?;
500
4
        let list_iter = stmt.query_map([], |row| {
501
1
            let pk = row.get("pk")?;
502
1
            Ok(DbVal(
503
1
                Account {
504
                    pk,
505
1
                    name: row.get("name")?,
506
1
                    address: row.get("address")?,
507
1
                    public_key: row.get("public_key")?,
508
1
                    password: row.get("password")?,
509
1
                    enabled: row.get("enabled")?,
510
1
                },
511
                pk,
512
            ))
513
1
        })?;
514

            
515
3
        let mut ret = vec![];
516
4
        for list in list_iter {
517
1
            let list = list?;
518
1
            ret.push(list);
519
        }
520
3
        Ok(ret)
521
3
    }
522

            
523
    /// Add account.
524
2
    pub fn add_account(&self, new_val: Account) -> Result<DbVal<Account>> {
525
2
        let mut stmt = self
526
            .connection
527
            .prepare(
528
                "INSERT INTO account(name, address, public_key, password, enabled) VALUES(?, ?, \
529
                 ?, ?, ?) RETURNING *;",
530
            )
531
            .unwrap();
532
2
        let ret = stmt.query_row(
533
2
            rusqlite::params![
534
2
                &new_val.name,
535
2
                &new_val.address,
536
2
                &new_val.public_key,
537
2
                &new_val.password,
538
2
                &new_val.enabled,
539
            ],
540
2
            |row| {
541
2
                let pk = row.get("pk")?;
542
2
                Ok(DbVal(
543
2
                    Account {
544
                        pk,
545
2
                        name: row.get("name")?,
546
2
                        address: row.get("address")?,
547
2
                        public_key: row.get("public_key")?,
548
2
                        password: row.get("password")?,
549
2
                        enabled: row.get("enabled")?,
550
2
                    },
551
                    pk,
552
                ))
553
2
            },
554
        )?;
555

            
556
2
        trace!("add_account {:?}.", &ret);
557
2
        Ok(ret)
558
2
    }
559

            
560
    /// Remove an account by their address.
561
2
    pub fn remove_account(&self, address: &str) -> Result<()> {
562
4
        self.connection
563
            .query_row(
564
                "DELETE FROM account WHERE address = ? RETURNING *;",
565
2
                rusqlite::params![&address],
566
1
                |_| Ok(()),
567
            )
568
1
            .map_err(|err| {
569
1
                if matches!(err, rusqlite::Error::QueryReturnedNoRows) {
570
2
                    Error::from(err).chain_err(|| NotFound("account not found!"))
571
                } else {
572
                    err.into()
573
                }
574
2
            })?;
575

            
576
1
        Ok(())
577
2
    }
578

            
579
    /// Update an account.
580
4
    pub fn update_account(&self, change_set: AccountChangeset) -> Result<()> {
581
4
        let Some(acc) = self.account_by_address(&change_set.address)? else {
582
1
            return Err(NotFound("account with this address not found!").into());
583
1
        };
584
3
        let pk = acc.pk;
585
3
        if matches!(
586
3
            change_set,
587
            AccountChangeset {
588
                address: _,
589
                name: None,
590
                public_key: None,
591
                password: None,
592
                enabled: None,
593
            }
594
        ) {
595
1
            return Ok(());
596
        }
597

            
598
        let AccountChangeset {
599
            address: _,
600
2
            name,
601
2
            public_key,
602
2
            password,
603
2
            enabled,
604
        } = change_set;
605
4
        let tx = self.savepoint(Some(stringify!(update_account)))?;
606

            
607
        macro_rules! update {
608
            ($field:tt) => {{
609
2
                if let Some($field) = $field {
610
                    tx.connection.execute(
611
                        concat!(
612
                            "UPDATE account SET ",
613
                            stringify!($field),
614
                            " = ? WHERE pk = ?;"
615
                        ),
616
                        rusqlite::params![&$field, &pk],
617
                    )?;
618
                }
619
            }};
620
        }
621
2
        update!(name);
622
2
        update!(public_key);
623
2
        update!(password);
624
2
        update!(enabled);
625

            
626
6
        tx.commit()?;
627
2
        Ok(())
628
4
    }
629
}
630

            
631
#[cfg(test)]
632
mod tests {
633
    use super::*;
634
    use crate::*;
635

            
636
    #[test]
637
2
    fn test_subscription_ops() {
638
        use tempfile::TempDir;
639

            
640
1
        let tmp_dir = TempDir::new().unwrap();
641
1
        let db_path = tmp_dir.path().join("mpot.db");
642
1
        let config = Configuration {
643
1
            send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
644
            db_path,
645
1
            data_path: tmp_dir.path().to_path_buf(),
646
1
            administrators: vec![],
647
        };
648

            
649
1
        let db = Connection::open_or_create_db(config).unwrap().trusted();
650
1
        let list = db
651
1
            .create_list(MailingList {
652
                pk: -1,
653
1
                name: "foobar chat".into(),
654
1
                id: "foo-chat".into(),
655
1
                address: "foo-chat@example.com".into(),
656
1
                topics: vec![],
657
1
                description: None,
658
1
                archive_url: None,
659
            })
660
            .unwrap();
661
1
        let secondary_list = db
662
1
            .create_list(MailingList {
663
                pk: -1,
664
1
                name: "foobar chat2".into(),
665
1
                id: "foo-chat2".into(),
666
1
                address: "foo-chat2@example.com".into(),
667
1
                topics: vec![],
668
1
                description: None,
669
1
                archive_url: None,
670
            })
671
            .unwrap();
672
5
        for i in 0..4 {
673
4
            let sub = db
674
                .add_subscription(
675
4
                    list.pk(),
676
4
                    ListSubscription {
677
                        pk: -1,
678
4
                        list: list.pk(),
679
4
                        address: format!("{i}@example.com"),
680
4
                        account: None,
681
4
                        name: Some(format!("User{i}")),
682
                        digest: false,
683
                        hide_address: false,
684
                        receive_duplicates: false,
685
                        receive_own_posts: false,
686
                        receive_confirmation: false,
687
                        enabled: true,
688
                        verified: false,
689
                    },
690
                )
691
                .unwrap();
692
4
            assert_eq!(db.list_subscription(list.pk(), sub.pk()).unwrap(), sub);
693
4
            assert_eq!(
694
4
                db.list_subscription_by_address(list.pk(), &sub.address)
695
                    .unwrap(),
696
                sub
697
            );
698
4
        }
699

            
700
1
        assert_eq!(db.accounts().unwrap(), vec![]);
701
1
        assert_eq!(
702
1
            db.remove_subscription(list.pk(), "nonexistent@example.com")
703
1
                .map_err(|err| err.to_string())
704
                .unwrap_err(),
705
1
            NotFound("list or list owner not found!").to_string()
706
        );
707

            
708
1
        let cand = db
709
            .add_candidate_subscription(
710
1
                list.pk(),
711
1
                ListSubscription {
712
                    pk: -1,
713
1
                    list: list.pk(),
714
1
                    address: "4@example.com".into(),
715
1
                    account: None,
716
1
                    name: Some("User4".into()),
717
                    digest: false,
718
                    hide_address: false,
719
                    receive_duplicates: false,
720
                    receive_own_posts: false,
721
                    receive_confirmation: false,
722
                    enabled: true,
723
                    verified: false,
724
                },
725
            )
726
            .unwrap();
727
1
        let accepted = db.accept_candidate_subscription(cand.pk()).unwrap();
728

            
729
1
        assert_eq!(db.account(5).unwrap(), None);
730
1
        assert_eq!(
731
1
            db.remove_account("4@example.com")
732
1
                .map_err(|err| err.to_string())
733
                .unwrap_err(),
734
1
            NotFound("account not found!").to_string()
735
        );
736

            
737
1
        let acc = db
738
1
            .add_account(Account {
739
                pk: -1,
740
1
                name: accepted.name.clone(),
741
1
                address: accepted.address.clone(),
742
1
                public_key: None,
743
1
                password: String::new(),
744
                enabled: true,
745
            })
746
            .unwrap();
747

            
748
        // Test [ref:add_account] SQL trigger (see schema.sql)
749
1
        assert_eq!(
750
1
            db.list_subscription(list.pk(), accepted.pk())
751
                .unwrap()
752
                .account,
753
1
            Some(acc.pk())
754
        );
755
        // Test [ref:add_account_to_subscription] SQL trigger (see schema.sql)
756
1
        let sub = db
757
            .add_subscription(
758
1
                secondary_list.pk(),
759
1
                ListSubscription {
760
                    pk: -1,
761
1
                    list: secondary_list.pk(),
762
1
                    address: "4@example.com".into(),
763
1
                    account: None,
764
1
                    name: Some("User4".into()),
765
                    digest: false,
766
                    hide_address: false,
767
                    receive_duplicates: false,
768
                    receive_own_posts: false,
769
                    receive_confirmation: false,
770
                    enabled: true,
771
                    verified: true,
772
                },
773
            )
774
            .unwrap();
775
1
        assert_eq!(sub.account, Some(acc.pk()));
776
        // Test [ref:verify_subscription_email] SQL trigger (see schema.sql)
777
1
        assert!(!sub.verified);
778

            
779
1
        assert_eq!(db.accounts().unwrap(), vec![acc.clone()]);
780

            
781
1
        assert_eq!(
782
1
            db.update_account(AccountChangeset {
783
1
                address: "nonexistent@example.com".into(),
784
1
                ..AccountChangeset::default()
785
            })
786
1
            .map_err(|err| err.to_string())
787
            .unwrap_err(),
788
1
            NotFound("account with this address not found!").to_string()
789
        );
790
1
        assert_eq!(
791
1
            db.update_account(AccountChangeset {
792
1
                address: acc.address.clone(),
793
1
                ..AccountChangeset::default()
794
            })
795
            .map_err(|err| err.to_string()),
796
            Ok(())
797
        );
798
1
        assert_eq!(
799
1
            db.update_account(AccountChangeset {
800
1
                address: acc.address.clone(),
801
1
                enabled: Some(Some(false)),
802
1
                ..AccountChangeset::default()
803
            })
804
            .map_err(|err| err.to_string()),
805
            Ok(())
806
        );
807
1
        assert!(!db.account(acc.pk()).unwrap().unwrap().enabled);
808
1
        assert_eq!(
809
1
            db.remove_account("4@example.com")
810
                .map_err(|err| err.to_string()),
811
            Ok(())
812
        );
813
1
        assert_eq!(db.accounts().unwrap(), vec![]);
814
2
    }
815
}