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
//! How each list handles new posts and new subscriptions.
21

            
22
mod post_policy {
23
    use log::trace;
24
    use rusqlite::OptionalExtension;
25

            
26
    use crate::{
27
        errors::{ErrorKind::*, *},
28
        models::{DbVal, PostPolicy},
29
        Connection,
30
    };
31

            
32
    impl Connection {
33
        /// Fetch the post policy of a mailing list.
34
50
        pub fn list_post_policy(&self, pk: i64) -> Result<Option<DbVal<PostPolicy>>> {
35
50
            let mut stmt = self
36
                .connection
37
                .prepare("SELECT * FROM post_policy WHERE list = ?;")?;
38
50
            let ret = stmt
39
87
                .query_row([&pk], |row| {
40
37
                    let pk = row.get("pk")?;
41
37
                    Ok(DbVal(
42
37
                        PostPolicy {
43
                            pk,
44
37
                            list: row.get("list")?,
45
37
                            announce_only: row.get("announce_only")?,
46
37
                            subscription_only: row.get("subscription_only")?,
47
37
                            approval_needed: row.get("approval_needed")?,
48
37
                            open: row.get("open")?,
49
37
                            custom: row.get("custom")?,
50
                        },
51
                        pk,
52
                    ))
53
74
                })
54
50
                .optional()?;
55

            
56
50
            Ok(ret)
57
50
        }
58

            
59
        /// Remove an existing list policy.
60
        ///
61
        /// # Examples
62
        ///
63
        /// ```
64
        /// # use mailpot::{models::*, Configuration, Connection, SendMail};
65
        /// # use tempfile::TempDir;
66
        /// #
67
        /// # let tmp_dir = TempDir::new().unwrap();
68
        /// # let db_path = tmp_dir.path().join("mpot.db");
69
        /// # let config = Configuration {
70
        /// #     send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
71
        /// #     db_path: db_path.clone(),
72
        /// #     data_path: tmp_dir.path().to_path_buf(),
73
        /// #     administrators: vec![],
74
        /// # };
75
        /// #
76
        /// # fn do_test(config: Configuration) {
77
        /// let db = Connection::open_or_create_db(config).unwrap().trusted();
78
        /// # assert!(db.list_post_policy(1).unwrap().is_none());
79
        /// let list = db
80
        ///     .create_list(MailingList {
81
        ///         pk: 0,
82
        ///         name: "foobar chat".into(),
83
        ///         id: "foo-chat".into(),
84
        ///         address: "foo-chat@example.com".into(),
85
        ///         description: None,
86
        ///         topics: vec![],
87
        ///         archive_url: None,
88
        ///     })
89
        ///     .unwrap();
90
        ///
91
        /// # assert!(db.list_post_policy(list.pk()).unwrap().is_none());
92
        /// let pol = db
93
        ///     .set_list_post_policy(PostPolicy {
94
        ///         pk: -1,
95
        ///         list: list.pk(),
96
        ///         announce_only: false,
97
        ///         subscription_only: true,
98
        ///         approval_needed: false,
99
        ///         open: false,
100
        ///         custom: false,
101
        ///     })
102
        ///     .unwrap();
103
        /// # assert_eq!(db.list_post_policy(list.pk()).unwrap().as_ref(), Some(&pol));
104
        /// db.remove_list_post_policy(list.pk(), pol.pk()).unwrap();
105
        /// # assert!(db.list_post_policy(list.pk()).unwrap().is_none());
106
        /// # }
107
        /// # do_test(config);
108
        /// ```
109
        ///
110
        /// ```should_panic
111
        /// # use mailpot::{models::*, Configuration, Connection, SendMail};
112
        /// # use tempfile::TempDir;
113
        /// #
114
        /// # let tmp_dir = TempDir::new().unwrap();
115
        /// # let db_path = tmp_dir.path().join("mpot.db");
116
        /// # let config = Configuration {
117
        /// #     send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
118
        /// #     db_path: db_path.clone(),
119
        /// #     data_path: tmp_dir.path().to_path_buf(),
120
        /// #     administrators: vec![],
121
        /// # };
122
        /// #
123
        /// # fn do_test(config: Configuration) {
124
        /// let db = Connection::open_or_create_db(config).unwrap().trusted();
125
        /// db.remove_list_post_policy(1, 1).unwrap();
126
        /// # }
127
        /// # do_test(config);
128
        /// ```
129
3
        pub fn remove_list_post_policy(&self, list_pk: i64, policy_pk: i64) -> Result<()> {
130
3
            let mut stmt = self
131
                .connection
132
1
                .prepare("DELETE FROM post_policy WHERE pk = ? AND list = ? RETURNING *;")?;
133
5
            stmt.query_row(rusqlite::params![&policy_pk, &list_pk,], |_| Ok(()))
134
                .map_err(|err| {
135
                    if matches!(err, rusqlite::Error::QueryReturnedNoRows) {
136
                        Error::from(err).chain_err(|| NotFound("list or list policy not found!"))
137
                    } else {
138
                        err.into()
139
                    }
140
                })?;
141

            
142
2
            trace!("remove_list_post_policy {} {}.", list_pk, policy_pk);
143
2
            Ok(())
144
3
        }
145

            
146
        /// Set the unique post policy for a list.
147
18
        pub fn set_list_post_policy(&self, policy: PostPolicy) -> Result<DbVal<PostPolicy>> {
148
18
            if !(policy.announce_only
149
17
                || policy.subscription_only
150
7
                || policy.approval_needed
151
5
                || policy.open
152
1
                || policy.custom)
153
            {
154
                return Err(Error::new_external(
155
                    "Cannot add empty policy. Having no policies is probably what you want to do.",
156
                ));
157
            }
158
18
            let list_pk = policy.list;
159

            
160
18
            let mut stmt = self.connection.prepare(
161
                "INSERT OR REPLACE INTO post_policy(list, announce_only, subscription_only, \
162
                 approval_needed, open, custom) VALUES (?, ?, ?, ?, ?, ?) RETURNING *;",
163
1
            )?;
164
17
            let ret = stmt
165
                .query_row(
166
17
                    rusqlite::params![
167
17
                        &list_pk,
168
17
                        &policy.announce_only,
169
17
                        &policy.subscription_only,
170
17
                        &policy.approval_needed,
171
17
                        &policy.open,
172
17
                        &policy.custom,
173
                    ],
174
17
                    |row| {
175
17
                        let pk = row.get("pk")?;
176
17
                        Ok(DbVal(
177
17
                            PostPolicy {
178
                                pk,
179
17
                                list: row.get("list")?,
180
17
                                announce_only: row.get("announce_only")?,
181
17
                                subscription_only: row.get("subscription_only")?,
182
17
                                approval_needed: row.get("approval_needed")?,
183
17
                                open: row.get("open")?,
184
17
                                custom: row.get("custom")?,
185
                            },
186
                            pk,
187
                        ))
188
34
                    },
189
                )
190
                .map_err(|err| {
191
                    if matches!(
192
                        err,
193
                        rusqlite::Error::SqliteFailure(
194
                            rusqlite::ffi::Error {
195
                                code: rusqlite::ffi::ErrorCode::ConstraintViolation,
196
                                extended_code: 787
197
                            },
198
                            _
199
                        )
200
                    ) {
201
                        Error::from(err)
202
                            .chain_err(|| NotFound("Could not find a list with this pk."))
203
                    } else {
204
                        err.into()
205
                    }
206
                })?;
207

            
208
17
            trace!("set_list_post_policy {:?}.", &ret);
209
17
            Ok(ret)
210
18
        }
211
    }
212
}
213

            
214
mod subscription_policy {
215
    use log::trace;
216
    use rusqlite::OptionalExtension;
217

            
218
    use crate::{
219
        errors::{ErrorKind::*, *},
220
        models::{DbVal, SubscriptionPolicy},
221
        Connection,
222
    };
223

            
224
    impl Connection {
225
        /// Fetch the subscription policy of a mailing list.
226
55
        pub fn list_subscription_policy(
227
            &self,
228
            pk: i64,
229
        ) -> Result<Option<DbVal<SubscriptionPolicy>>> {
230
55
            let mut stmt = self
231
                .connection
232
                .prepare("SELECT * FROM subscription_policy WHERE list = ?;")?;
233
55
            let ret = stmt
234
60
                .query_row([&pk], |row| {
235
5
                    let pk = row.get("pk")?;
236
5
                    Ok(DbVal(
237
5
                        SubscriptionPolicy {
238
                            pk,
239
5
                            list: row.get("list")?,
240
5
                            send_confirmation: row.get("send_confirmation")?,
241
5
                            open: row.get("open")?,
242
5
                            manual: row.get("manual")?,
243
5
                            request: row.get("request")?,
244
5
                            custom: row.get("custom")?,
245
                        },
246
                        pk,
247
                    ))
248
10
                })
249
55
                .optional()?;
250

            
251
55
            Ok(ret)
252
55
        }
253

            
254
        /// Remove an existing subscription policy.
255
        ///
256
        /// # Examples
257
        ///
258
        /// ```
259
        /// # use mailpot::{models::*, Configuration, Connection, SendMail};
260
        /// # use tempfile::TempDir;
261
        /// #
262
        /// # let tmp_dir = TempDir::new().unwrap();
263
        /// # let db_path = tmp_dir.path().join("mpot.db");
264
        /// # let config = Configuration {
265
        /// #     send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
266
        /// #     db_path: db_path.clone(),
267
        /// #     data_path: tmp_dir.path().to_path_buf(),
268
        /// #     administrators: vec![],
269
        /// # };
270
        /// #
271
        /// # fn do_test(config: Configuration) {
272
        /// let db = Connection::open_or_create_db(config).unwrap().trusted();
273
        /// let list = db
274
        ///     .create_list(MailingList {
275
        ///         pk: 0,
276
        ///         name: "foobar chat".into(),
277
        ///         id: "foo-chat".into(),
278
        ///         address: "foo-chat@example.com".into(),
279
        ///         description: None,
280
        ///         topics: vec![],
281
        ///         archive_url: None,
282
        ///     })
283
        ///     .unwrap();
284
        /// # assert!(db.list_subscription_policy(list.pk()).unwrap().is_none());
285
        /// let pol = db
286
        ///     .set_list_subscription_policy(SubscriptionPolicy {
287
        ///         pk: -1,
288
        ///         list: list.pk(),
289
        ///         send_confirmation: false,
290
        ///         open: true,
291
        ///         manual: false,
292
        ///         request: false,
293
        ///         custom: false,
294
        ///     })
295
        ///     .unwrap();
296
        /// # assert_eq!(db.list_subscription_policy(list.pk()).unwrap().as_ref(), Some(&pol));
297
        /// db.remove_list_subscription_policy(list.pk(), pol.pk())
298
        ///     .unwrap();
299
        /// # assert!(db.list_subscription_policy(list.pk()).unwrap().is_none());
300
        /// # }
301
        /// # do_test(config);
302
        /// ```
303
        ///
304
        /// ```should_panic
305
        /// # use mailpot::{models::*, Configuration, Connection, SendMail};
306
        /// # use tempfile::TempDir;
307
        /// #
308
        /// # let tmp_dir = TempDir::new().unwrap();
309
        /// # let db_path = tmp_dir.path().join("mpot.db");
310
        /// # let config = Configuration {
311
        /// #     send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
312
        /// #     db_path: db_path.clone(),
313
        /// #     data_path: tmp_dir.path().to_path_buf(),
314
        /// #     administrators: vec![],
315
        /// # };
316
        /// #
317
        /// # fn do_test(config: Configuration) {
318
        /// let db = Connection::open_or_create_db(config).unwrap().trusted();
319
        /// db.remove_list_post_policy(1, 1).unwrap();
320
        /// # }
321
        /// # do_test(config);
322
        /// ```
323
1
        pub fn remove_list_subscription_policy(&self, list_pk: i64, policy_pk: i64) -> Result<()> {
324
1
            let mut stmt = self.connection.prepare(
325
                "DELETE FROM subscription_policy WHERE pk = ? AND list = ? RETURNING *;",
326
            )?;
327
2
            stmt.query_row(rusqlite::params![&policy_pk, &list_pk,], |_| Ok(()))
328
                .map_err(|err| {
329
                    if matches!(err, rusqlite::Error::QueryReturnedNoRows) {
330
                        Error::from(err).chain_err(|| NotFound("list or list policy not found!"))
331
                    } else {
332
                        err.into()
333
                    }
334
                })?;
335

            
336
1
            trace!("remove_list_subscription_policy {} {}.", list_pk, policy_pk);
337
1
            Ok(())
338
1
        }
339

            
340
        /// Set the unique post policy for a list.
341
2
        pub fn set_list_subscription_policy(
342
            &self,
343
            policy: SubscriptionPolicy,
344
        ) -> Result<DbVal<SubscriptionPolicy>> {
345
2
            if !(policy.open || policy.manual || policy.request || policy.custom) {
346
                return Err(Error::new_external(
347
                    "Cannot add empty policy. Having no policy is probably what you want to do.",
348
                ));
349
            }
350
2
            let list_pk = policy.list;
351

            
352
2
            let mut stmt = self.connection.prepare(
353
                "INSERT OR REPLACE INTO subscription_policy(list, send_confirmation, open, \
354
                 manual, request, custom) VALUES (?, ?, ?, ?, ?, ?) RETURNING *;",
355
            )?;
356
2
            let ret = stmt
357
                .query_row(
358
2
                    rusqlite::params![
359
2
                        &list_pk,
360
2
                        &policy.send_confirmation,
361
2
                        &policy.open,
362
2
                        &policy.manual,
363
2
                        &policy.request,
364
2
                        &policy.custom,
365
                    ],
366
2
                    |row| {
367
2
                        let pk = row.get("pk")?;
368
2
                        Ok(DbVal(
369
2
                            SubscriptionPolicy {
370
                                pk,
371
2
                                list: row.get("list")?,
372
2
                                send_confirmation: row.get("send_confirmation")?,
373
2
                                open: row.get("open")?,
374
2
                                manual: row.get("manual")?,
375
2
                                request: row.get("request")?,
376
2
                                custom: row.get("custom")?,
377
                            },
378
                            pk,
379
                        ))
380
4
                    },
381
                )
382
                .map_err(|err| {
383
                    if matches!(
384
                        err,
385
                        rusqlite::Error::SqliteFailure(
386
                            rusqlite::ffi::Error {
387
                                code: rusqlite::ffi::ErrorCode::ConstraintViolation,
388
                                extended_code: 787
389
                            },
390
                            _
391
                        )
392
                    ) {
393
                        Error::from(err)
394
                            .chain_err(|| NotFound("Could not find a list with this pk."))
395
                    } else {
396
                        err.into()
397
                    }
398
                })?;
399

            
400
2
            trace!("set_list_subscription_policy {:?}.", &ret);
401
2
            Ok(ret)
402
2
        }
403
    }
404
}