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
use super::*;
21

            
22
impl Connection {
23
    /// Fetch all subscriptions of a mailing list.
24
24
    pub fn list_subscriptions(&self, pk: i64) -> Result<Vec<DbVal<ListSubscription>>> {
25
24
        let mut stmt = self
26
            .connection
27
            .prepare("SELECT * FROM subscription WHERE list = ?;")?;
28
41
        let list_iter = stmt.query_map([&pk], |row| {
29
17
            let pk = row.get("pk")?;
30
17
            Ok(DbVal(
31
17
                ListSubscription {
32
17
                    pk: row.get("pk")?,
33
17
                    list: row.get("list")?,
34
17
                    address: row.get("address")?,
35
17
                    account: row.get("account")?,
36
17
                    name: row.get("name")?,
37
17
                    digest: row.get("digest")?,
38
17
                    enabled: row.get("enabled")?,
39
17
                    verified: row.get("verified")?,
40
17
                    hide_address: row.get("hide_address")?,
41
17
                    receive_duplicates: row.get("receive_duplicates")?,
42
17
                    receive_own_posts: row.get("receive_own_posts")?,
43
17
                    receive_confirmation: row.get("receive_confirmation")?,
44
                },
45
                pk,
46
            ))
47
17
        })?;
48

            
49
24
        let mut ret = vec![];
50
41
        for list in list_iter {
51
17
            let list = list?;
52
17
            ret.push(list);
53
        }
54
24
        Ok(ret)
55
24
    }
56

            
57
    /// Fetch mailing list subscription.
58
    pub fn list_subscription(&self, list_pk: i64, pk: i64) -> Result<DbVal<ListSubscription>> {
59
        let mut stmt = self
60
            .connection
61
            .prepare("SELECT * FROM subscription WHERE list = ? AND pk = ?;")?;
62

            
63
        let ret = stmt.query_row([&list_pk, &pk], |row| {
64
            let _pk: i64 = row.get("pk")?;
65
            debug_assert_eq!(pk, _pk);
66
            Ok(DbVal(
67
                ListSubscription {
68
                    pk,
69
                    list: row.get("list")?,
70
                    address: row.get("address")?,
71
                    account: row.get("account")?,
72
                    name: row.get("name")?,
73
                    digest: row.get("digest")?,
74
                    enabled: row.get("enabled")?,
75
                    verified: row.get("verified")?,
76
                    hide_address: row.get("hide_address")?,
77
                    receive_duplicates: row.get("receive_duplicates")?,
78
                    receive_own_posts: row.get("receive_own_posts")?,
79
                    receive_confirmation: row.get("receive_confirmation")?,
80
                },
81
                pk,
82
            ))
83
        })?;
84
        Ok(ret)
85
    }
86

            
87
    /// Fetch mailing list subscription by their address.
88
11
    pub fn list_subscription_by_address(
89
        &self,
90
        list_pk: i64,
91
        address: &str,
92
    ) -> Result<DbVal<ListSubscription>> {
93
11
        let mut stmt = self
94
            .connection
95
            .prepare("SELECT * FROM subscription WHERE list = ? AND address = ?;")?;
96

            
97
14
        let ret = stmt.query_row(rusqlite::params![&list_pk, &address], |row| {
98
3
            let pk = row.get("pk")?;
99
3
            let address_ = row.get("address")?;
100
3
            debug_assert_eq!(address, &address_);
101
3
            Ok(DbVal(
102
3
                ListSubscription {
103
                    pk,
104
3
                    list: row.get("list")?,
105
3
                    address: address_,
106
3
                    account: row.get("account")?,
107
3
                    name: row.get("name")?,
108
3
                    digest: row.get("digest")?,
109
3
                    enabled: row.get("enabled")?,
110
3
                    verified: row.get("verified")?,
111
3
                    hide_address: row.get("hide_address")?,
112
3
                    receive_duplicates: row.get("receive_duplicates")?,
113
3
                    receive_own_posts: row.get("receive_own_posts")?,
114
3
                    receive_confirmation: row.get("receive_confirmation")?,
115
                },
116
                pk,
117
            ))
118
11
        })?;
119
3
        Ok(ret)
120
11
    }
121

            
122
    /// Add subscription to mailing list.
123
10
    pub fn add_subscription(
124
        &self,
125
        list_pk: i64,
126
        mut new_val: ListSubscription,
127
    ) -> Result<DbVal<ListSubscription>> {
128
10
        new_val.list = list_pk;
129
10
        let mut stmt = self
130
            .connection
131
            .prepare(
132
                "INSERT INTO subscription(list, address, account, name, enabled, digest, \
133
                 verified, hide_address, receive_duplicates, receive_own_posts, \
134
                 receive_confirmation) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *;",
135
            )
136
            .unwrap();
137
10
        let ret = stmt.query_row(
138
10
            rusqlite::params![
139
10
                &new_val.list,
140
10
                &new_val.address,
141
10
                &new_val.account,
142
10
                &new_val.name,
143
10
                &new_val.enabled,
144
10
                &new_val.digest,
145
10
                &new_val.verified,
146
10
                &new_val.hide_address,
147
10
                &new_val.receive_duplicates,
148
10
                &new_val.receive_own_posts,
149
10
                &new_val.receive_confirmation
150
            ],
151
10
            |row| {
152
10
                let pk = row.get("pk")?;
153
10
                Ok(DbVal(
154
10
                    ListSubscription {
155
                        pk,
156
10
                        list: row.get("list")?,
157
10
                        address: row.get("address")?,
158
10
                        name: row.get("name")?,
159
10
                        account: row.get("account")?,
160
10
                        digest: row.get("digest")?,
161
10
                        enabled: row.get("enabled")?,
162
10
                        verified: row.get("verified")?,
163
10
                        hide_address: row.get("hide_address")?,
164
10
                        receive_duplicates: row.get("receive_duplicates")?,
165
10
                        receive_own_posts: row.get("receive_own_posts")?,
166
10
                        receive_confirmation: row.get("receive_confirmation")?,
167
                    },
168
                    pk,
169
                ))
170
10
            },
171
        )?;
172

            
173
10
        trace!("add_subscription {:?}.", &ret);
174
10
        Ok(ret)
175
10
    }
176

            
177
    /// Create subscription candidate.
178
    pub fn add_candidate_subscription(
179
        &mut self,
180
        list_pk: i64,
181
        mut new_val: ListSubscription,
182
    ) -> Result<DbVal<ListCandidateSubscription>> {
183
        new_val.list = list_pk;
184
        let mut stmt = self.connection.prepare(
185
            "INSERT INTO candidate_subscription(list, address, name, accepted) VALUES(?, ?, ?, ?) \
186
             RETURNING *;",
187
        )?;
188
        let ret = stmt.query_row(
189
            rusqlite::params![&new_val.list, &new_val.address, &new_val.name, None::<i64>,],
190
            |row| {
191
                let pk = row.get("pk")?;
192
                Ok(DbVal(
193
                    ListCandidateSubscription {
194
                        pk,
195
                        list: row.get("list")?,
196
                        address: row.get("address")?,
197
                        name: row.get("name")?,
198
                        accepted: row.get("accepted")?,
199
                    },
200
                    pk,
201
                ))
202
            },
203
        )?;
204
        drop(stmt);
205

            
206
        trace!("add_candidate_subscription {:?}.", &ret);
207
        Ok(ret)
208
    }
209

            
210
    /// Accept subscription candidate.
211
    pub fn accept_candidate_subscription(&mut self, pk: i64) -> Result<DbVal<ListSubscription>> {
212
        let tx = self.connection.transaction()?;
213
        let mut stmt = tx.prepare(
214
            "INSERT INTO subscription(list, address, name, enabled, digest, verified, \
215
             hide_address, receive_duplicates, receive_own_posts, receive_confirmation) SELECT \
216
             list, address, name, 1, 0, 0, 0, 1, 1, 0 FROM candidate_subscription WHERE pk = ? \
217
             RETURNING *;",
218
        )?;
219
        let ret = stmt.query_row(rusqlite::params![&pk], |row| {
220
            let pk = row.get("pk")?;
221
            Ok(DbVal(
222
                ListSubscription {
223
                    pk,
224
                    list: row.get("list")?,
225
                    address: row.get("address")?,
226
                    account: row.get("account")?,
227
                    name: row.get("name")?,
228
                    digest: row.get("digest")?,
229
                    enabled: row.get("enabled")?,
230
                    verified: row.get("verified")?,
231
                    hide_address: row.get("hide_address")?,
232
                    receive_duplicates: row.get("receive_duplicates")?,
233
                    receive_own_posts: row.get("receive_own_posts")?,
234
                    receive_confirmation: row.get("receive_confirmation")?,
235
                },
236
                pk,
237
            ))
238
        })?;
239
        drop(stmt);
240
        tx.execute(
241
            "UPDATE candidate_subscription SET accepted = ? WHERE pk = ?;",
242
            [&ret.pk, &pk],
243
        )?;
244
        tx.commit()?;
245

            
246
        trace!("accept_candidate_subscription {:?}.", &ret);
247
        Ok(ret)
248
    }
249

            
250
    /// Remove a subscription by their address.
251
1
    pub fn remove_subscription(&self, list_pk: i64, address: &str) -> Result<()> {
252
2
        self.connection
253
            .query_row(
254
                "DELETE FROM subscription WHERE list = ? AND address = ? RETURNING *;",
255
1
                rusqlite::params![&list_pk, &address],
256
1
                |_| Ok(()),
257
            )
258
            .map_err(|err| {
259
                if matches!(err, rusqlite::Error::QueryReturnedNoRows) {
260
                    Error::from(err).chain_err(|| NotFound("list or list owner not found!"))
261
                } else {
262
                    err.into()
263
                }
264
            })?;
265

            
266
1
        Ok(())
267
1
    }
268

            
269
    /// Update a mailing list subscription.
270
1
    pub fn update_subscription(&mut self, change_set: ListSubscriptionChangeset) -> Result<()> {
271
2
        let pk = self
272
1
            .list_subscription_by_address(change_set.list, &change_set.address)?
273
1
            .pk;
274
1
        if matches!(
275
1
            change_set,
276
            ListSubscriptionChangeset {
277
                list: _,
278
                address: _,
279
                account: None,
280
                name: None,
281
                digest: None,
282
                verified: None,
283
                hide_address: None,
284
                receive_duplicates: None,
285
                receive_own_posts: None,
286
                receive_confirmation: None,
287
                enabled: None,
288
            }
289
        ) {
290
            return Ok(());
291
        }
292

            
293
        let ListSubscriptionChangeset {
294
1
            list,
295
            address: _,
296
1
            name,
297
1
            account,
298
1
            digest,
299
1
            enabled,
300
1
            verified,
301
1
            hide_address,
302
1
            receive_duplicates,
303
1
            receive_own_posts,
304
1
            receive_confirmation,
305
        } = change_set;
306
1
        let tx = self.connection.transaction()?;
307

            
308
        macro_rules! update {
309
            ($field:tt) => {{
310
                if let Some($field) = $field {
311
                    tx.execute(
312
                        concat!(
313
                            "UPDATE subscription SET ",
314
                            stringify!($field),
315
                            " = ? WHERE list = ? AND pk = ?;"
316
                        ),
317
                        rusqlite::params![&$field, &list, &pk],
318
                    )?;
319
                }
320
            }};
321
        }
322
1
        update!(name);
323
1
        update!(account);
324
1
        update!(digest);
325
1
        update!(enabled);
326
1
        update!(verified);
327
1
        update!(hide_address);
328
1
        update!(receive_duplicates);
329
1
        update!(receive_own_posts);
330
1
        update!(receive_confirmation);
331

            
332
2
        tx.commit()?;
333
1
        Ok(())
334
1
    }
335

            
336
    /// Fetch account by pk.
337
    pub fn account(&self, pk: i64) -> Result<Option<DbVal<Account>>> {
338
        let mut stmt = self
339
            .connection
340
            .prepare("SELECT * FROM account WHERE pk = ?;")?;
341

            
342
        let ret = stmt
343
            .query_row(rusqlite::params![&pk], |row| {
344
                let _pk: i64 = row.get("pk")?;
345
                debug_assert_eq!(pk, _pk);
346
                Ok(DbVal(
347
                    Account {
348
                        pk,
349
                        name: row.get("name")?,
350
                        address: row.get("address")?,
351
                        public_key: row.get("public_key")?,
352
                        password: row.get("password")?,
353
                        enabled: row.get("enabled")?,
354
                    },
355
                    pk,
356
                ))
357
            })
358
            .optional()?;
359
        Ok(ret)
360
    }
361

            
362
    /// Fetch account by address.
363
6
    pub fn account_by_address(&self, address: &str) -> Result<Option<DbVal<Account>>> {
364
6
        let mut stmt = self
365
            .connection
366
            .prepare("SELECT * FROM account WHERE address = ?;")?;
367

            
368
6
        let ret = stmt
369
10
            .query_row(rusqlite::params![&address], |row| {
370
4
                let pk = row.get("pk")?;
371
4
                Ok(DbVal(
372
4
                    Account {
373
                        pk,
374
4
                        name: row.get("name")?,
375
4
                        address: row.get("address")?,
376
4
                        public_key: row.get("public_key")?,
377
4
                        password: row.get("password")?,
378
4
                        enabled: row.get("enabled")?,
379
                    },
380
                    pk,
381
                ))
382
4
            })
383
            .optional()?;
384
6
        Ok(ret)
385
6
    }
386

            
387
    /// Fetch all subscriptions of an account by primary key.
388
    pub fn account_subscriptions(&self, pk: i64) -> Result<Vec<DbVal<ListSubscription>>> {
389
        let mut stmt = self
390
            .connection
391
            .prepare("SELECT * FROM subscription WHERE account = ?;")?;
392
        let list_iter = stmt.query_map([&pk], |row| {
393
            let pk = row.get("pk")?;
394
            Ok(DbVal(
395
                ListSubscription {
396
                    pk: row.get("pk")?,
397
                    list: row.get("list")?,
398
                    address: row.get("address")?,
399
                    account: row.get("account")?,
400
                    name: row.get("name")?,
401
                    digest: row.get("digest")?,
402
                    enabled: row.get("enabled")?,
403
                    verified: row.get("verified")?,
404
                    hide_address: row.get("hide_address")?,
405
                    receive_duplicates: row.get("receive_duplicates")?,
406
                    receive_own_posts: row.get("receive_own_posts")?,
407
                    receive_confirmation: row.get("receive_confirmation")?,
408
                },
409
                pk,
410
            ))
411
        })?;
412

            
413
        let mut ret = vec![];
414
        for list in list_iter {
415
            let list = list?;
416
            ret.push(list);
417
        }
418
        Ok(ret)
419
    }
420

            
421
    /// Fetch all accounts.
422
    pub fn accounts(&self) -> Result<Vec<DbVal<Account>>> {
423
        let mut stmt = self
424
            .connection
425
            .prepare("SELECT * FROM account ORDER BY pk ASC;")?;
426
        let list_iter = stmt.query_map([], |row| {
427
            let pk = row.get("pk")?;
428
            Ok(DbVal(
429
                Account {
430
                    pk,
431
                    name: row.get("name")?,
432
                    address: row.get("address")?,
433
                    public_key: row.get("public_key")?,
434
                    password: row.get("password")?,
435
                    enabled: row.get("enabled")?,
436
                },
437
                pk,
438
            ))
439
        })?;
440

            
441
        let mut ret = vec![];
442
        for list in list_iter {
443
            let list = list?;
444
            ret.push(list);
445
        }
446
        Ok(ret)
447
    }
448

            
449
    /// Add account.
450
1
    pub fn add_account(&self, new_val: Account) -> Result<DbVal<Account>> {
451
1
        let mut stmt = self
452
            .connection
453
            .prepare(
454
                "INSERT INTO account(name, address, public_key, password, enabled) VALUES(?, ?, \
455
                 ?, ?, ?) RETURNING *;",
456
            )
457
            .unwrap();
458
1
        let ret = stmt.query_row(
459
1
            rusqlite::params![
460
1
                &new_val.name,
461
1
                &new_val.address,
462
1
                &new_val.public_key,
463
1
                &new_val.password,
464
1
                &new_val.enabled,
465
            ],
466
1
            |row| {
467
1
                let pk = row.get("pk")?;
468
1
                Ok(DbVal(
469
1
                    Account {
470
                        pk,
471
1
                        name: row.get("name")?,
472
1
                        address: row.get("address")?,
473
1
                        public_key: row.get("public_key")?,
474
1
                        password: row.get("password")?,
475
1
                        enabled: row.get("enabled")?,
476
                    },
477
                    pk,
478
                ))
479
1
            },
480
        )?;
481

            
482
1
        trace!("add_account {:?}.", &ret);
483
1
        Ok(ret)
484
1
    }
485

            
486
    /// Remove an account by their address.
487
    pub fn remove_account(&self, address: &str) -> Result<()> {
488
        self.connection
489
            .query_row(
490
                "DELETE FROM account WHERE address = ? RETURNING *;",
491
                rusqlite::params![&address],
492
                |_| Ok(()),
493
            )
494
            .map_err(|err| {
495
                if matches!(err, rusqlite::Error::QueryReturnedNoRows) {
496
                    Error::from(err).chain_err(|| NotFound("account not found!"))
497
                } else {
498
                    err.into()
499
                }
500
            })?;
501

            
502
        Ok(())
503
    }
504

            
505
    /// Update an account.
506
1
    pub fn update_account(&mut self, change_set: AccountChangeset) -> Result<()> {
507
1
        let Some(acc) = self.account_by_address(&change_set.address)? else {
508
            return Err(NotFound("account with this address not found!").into());
509
        };
510
1
        let pk = acc.pk;
511
1
        if matches!(
512
1
            change_set,
513
            AccountChangeset {
514
                address: _,
515
                name: None,
516
                public_key: None,
517
                password: None,
518
                enabled: None,
519
            }
520
        ) {
521
            return Ok(());
522
        }
523

            
524
        let AccountChangeset {
525
            address: _,
526
1
            name,
527
1
            public_key,
528
1
            password,
529
1
            enabled,
530
        } = change_set;
531
1
        let tx = self.connection.transaction()?;
532

            
533
        macro_rules! update {
534
            ($field:tt) => {{
535
                if let Some($field) = $field {
536
                    tx.execute(
537
                        concat!(
538
                            "UPDATE account SET ",
539
                            stringify!($field),
540
                            " = ? WHERE pk = ?;"
541
                        ),
542
                        rusqlite::params![&$field, &pk],
543
                    )?;
544
                }
545
            }};
546
        }
547
1
        update!(name);
548
1
        update!(public_key);
549
1
        update!(password);
550
1
        update!(enabled);
551

            
552
2
        tx.commit()?;
553
1
        Ok(())
554
1
    }
555
}