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
pub use post_policy::*;
21
pub use subscription_policy::*;
22

            
23
use super::*;
24
mod post_policy {
25
    use super::*;
26

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

            
51
43
            Ok(ret)
52
43
        }
53

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

            
114
2
            trace!("remove_list_post_policy {} {}.", list_pk, policy_pk);
115
2
            Ok(())
116
3
        }
117

            
118
        /// ```should_panic
119
        /// # use mailpot::{models::*, Configuration, Connection, SendMail};
120
        /// # use tempfile::TempDir;
121
        ///
122
        /// # let tmp_dir = TempDir::new().unwrap();
123
        /// # let db_path = tmp_dir.path().join("mpot.db");
124
        /// # let config = Configuration {
125
        /// #     send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
126
        /// #     db_path: db_path.clone(),
127
        /// #     data_path: tmp_dir.path().to_path_buf(),
128
        /// #     administrators: vec![],
129
        /// # };
130
        ///
131
        /// # fn do_test(config: Configuration) {
132
        /// let db = Connection::open_or_create_db(config).unwrap().trusted();
133
        /// db.remove_list_post_policy(1, 1).unwrap();
134
        /// # }
135
        /// # do_test(config);
136
        /// ```
137
        #[cfg(doc)]
138
        pub fn remove_list_post_policy_panic() {}
139

            
140
        /// Set the unique post policy for a list.
141
14
        pub fn set_list_post_policy(&self, policy: PostPolicy) -> Result<DbVal<PostPolicy>> {
142
14
            if !(policy.announce_only
143
14
                || policy.subscription_only
144
4
                || policy.approval_needed
145
3
                || policy.open
146
                || policy.custom)
147
            {
148
                return Err(
149
                    "Cannot add empty policy. Having no policies is probably what you want to do."
150
                        .into(),
151
                );
152
            }
153
14
            let list_pk = policy.list;
154

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

            
203
13
            trace!("set_list_post_policy {:?}.", &ret);
204
13
            Ok(ret)
205
14
        }
206
    }
207
}
208

            
209
mod subscription_policy {
210
    use super::*;
211

            
212
    impl Connection {
213
        /// Fetch the subscription policy of a mailing list.
214
25
        pub fn list_subscription_policy(
215
            &self,
216
            pk: i64,
217
        ) -> Result<Option<DbVal<SubscriptionPolicy>>> {
218
25
            let mut stmt = self
219
                .connection
220
                .prepare("SELECT * FROM subscription_policy WHERE list = ?;")?;
221
25
            let ret = stmt
222
26
                .query_row([&pk], |row| {
223
1
                    let pk = row.get("pk")?;
224
1
                    Ok(DbVal(
225
1
                        SubscriptionPolicy {
226
                            pk,
227
1
                            list: row.get("list")?,
228
1
                            send_confirmation: row.get("send_confirmation")?,
229
1
                            open: row.get("open")?,
230
1
                            manual: row.get("manual")?,
231
1
                            request: row.get("request")?,
232
1
                            custom: row.get("custom")?,
233
                        },
234
                        pk,
235
                    ))
236
1
                })
237
                .optional()?;
238

            
239
25
            Ok(ret)
240
25
        }
241

            
242
        /// Remove an existing subscription policy.
243
        ///
244
        /// ```
245
        /// # use mailpot::{models::*, Configuration, Connection, SendMail};
246
        /// # use tempfile::TempDir;
247
        ///
248
        /// # let tmp_dir = TempDir::new().unwrap();
249
        /// # let db_path = tmp_dir.path().join("mpot.db");
250
        /// # let config = Configuration {
251
        /// #     send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
252
        /// #     db_path: db_path.clone(),
253
        /// #     data_path: tmp_dir.path().to_path_buf(),
254
        /// #     administrators: vec![],
255
        /// # };
256
        ///
257
        /// # fn do_test(config: Configuration) {
258
        /// let db = Connection::open_or_create_db(config).unwrap().trusted();
259
        /// let list = db
260
        ///     .create_list(MailingList {
261
        ///         pk: 0,
262
        ///         name: "foobar chat".into(),
263
        ///         id: "foo-chat".into(),
264
        ///         address: "foo-chat@example.com".into(),
265
        ///         description: None,
266
        ///         archive_url: None,
267
        ///     })
268
        ///     .unwrap();
269
        /// # assert!(db.list_subscription_policy(list.pk()).unwrap().is_none());
270
        /// let pol = db
271
        ///     .set_list_subscription_policy(SubscriptionPolicy {
272
        ///         pk: -1,
273
        ///         list: list.pk(),
274
        ///         send_confirmation: false,
275
        ///         open: true,
276
        ///         manual: false,
277
        ///         request: false,
278
        ///         custom: false,
279
        ///     })
280
        ///     .unwrap();
281
        /// # assert_eq!(db.list_subscription_policy(list.pk()).unwrap().as_ref(), Some(&pol));
282
        /// db.remove_list_subscription_policy(list.pk(), pol.pk())
283
        ///     .unwrap();
284
        /// # assert!(db.list_subscription_policy(list.pk()).unwrap().is_none());
285
        /// # }
286
        /// # do_test(config);
287
        /// ```
288
1
        pub fn remove_list_subscription_policy(&self, list_pk: i64, policy_pk: i64) -> Result<()> {
289
1
            let mut stmt = self.connection.prepare(
290
                "DELETE FROM subscription_policy WHERE pk = ? AND list = ? RETURNING *;",
291
            )?;
292
2
            stmt.query_row(rusqlite::params![&policy_pk, &list_pk,], |_| Ok(()))
293
                .map_err(|err| {
294
                    if matches!(err, rusqlite::Error::QueryReturnedNoRows) {
295
                        Error::from(err).chain_err(|| NotFound("list or list policy not found!"))
296
                    } else {
297
                        err.into()
298
                    }
299
                })?;
300

            
301
1
            trace!("remove_list_subscription_policy {} {}.", list_pk, policy_pk);
302
1
            Ok(())
303
1
        }
304

            
305
        /// ```should_panic
306
        /// # use mailpot::{models::*, Configuration, Connection, SendMail};
307
        /// # use tempfile::TempDir;
308
        ///
309
        /// # let tmp_dir = TempDir::new().unwrap();
310
        /// # let db_path = tmp_dir.path().join("mpot.db");
311
        /// # let config = Configuration {
312
        /// #     send_mail: SendMail::ShellCommand("/usr/bin/false".to_string()),
313
        /// #     db_path: db_path.clone(),
314
        /// #     data_path: tmp_dir.path().to_path_buf(),
315
        /// #     administrators: vec![],
316
        /// # };
317
        ///
318
        /// # fn do_test(config: Configuration) {
319
        /// let db = Connection::open_or_create_db(config).unwrap().trusted();
320
        /// db.remove_list_post_policy(1, 1).unwrap();
321
        /// # }
322
        /// # do_test(config);
323
        /// ```
324
        #[cfg(doc)]
325
        pub fn remove_list_subscription_policy_panic() {}
326

            
327
        /// Set the unique post policy for a list.
328
1
        pub fn set_list_subscription_policy(
329
            &self,
330
            policy: SubscriptionPolicy,
331
        ) -> Result<DbVal<SubscriptionPolicy>> {
332
1
            if !(policy.open || policy.manual || policy.request || policy.custom) {
333
                return Err(
334
                    "Cannot add empty policy. Having no policy is probably what you want to do."
335
                        .into(),
336
                );
337
            }
338
1
            let list_pk = policy.list;
339

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

            
388
1
            trace!("set_list_subscription_policy {:?}.", &ret);
389
1
            Ok(ret)
390
1
        }
391
    }
392
}