Author: Manos Pitsidianakis [manos@pitsidianak.is]
Hash: cfd55d3e4aae39004d9603e0ebadbd3ffe95239f
Timestamp: Tue, 25 Apr 2023 14:02:24 +0000 (1 year ago)

+1196 -749 +/-25 browse
Tidy up rustdocs
1diff --git a/.github/doc_extra.html b/.github/doc_extra.html
2new file mode 100644
3index 0000000..05524a9
4--- /dev/null
5+++ b/.github/doc_extra.html
6 @@ -0,0 +1,97 @@
7+ <style>
8+ .rustdoc { flex-wrap: wrap; }
9+ @media (prefers-color-scheme: light) {
10+ :root {
11+ --border-primary: #cdcdcd;
12+ --border-secondary: #cdcdcd;
13+ --a-normal-text: #034575;
14+ --a-normal-underline: #bbb;
15+ --a-visited-underline: #707070;
16+ --a-hover-bg: #bfbfbf40;
17+ --a-active-text: #c00;
18+ --a-active-underline: #c00;
19+ --accent-primary: #0085f2;
20+ --accent-primary-engage: #0085f21a;
21+ --accent-secondary: #0085f2;
22+ --accent-tertiary: #0085f21a;
23+ color-scheme: light;
24+ }
25+ }
26+
27+ @media (prefers-color-scheme: dark) {
28+ :root {
29+ --border-primary: #858585;
30+ --border-secondary: #696969;
31+ --a-normal-text: #4db4ff;
32+ --a-normal-underline: #8b8b8b;
33+ --a-visited-underline: #707070;
34+ --a-hover-bg: #bfbfbf40;
35+ --a-active-text: #c00;
36+ --a-active-underline: #c00;
37+ --accent-primary: #5e9eff;
38+ --accent-primary-engage: #5e9eff1a;
39+ --accent-secondary: #5e9eff;
40+ --accent-tertiary: #0085f21a;
41+ color-scheme: dark;
42+ }
43+ }
44+
45+ nav.main-nav {
46+ padding: 0rem 1rem;
47+ border: 0.1rem solid var(--border-secondary);
48+ border-left: none;
49+ border-right: none;
50+ border-radius: 2px;
51+ padding: 10px 14px 10px 10px;
52+ margin-bottom: 10px;
53+ /*! width: 100%; */
54+ height: max-content;
55+ width: 100vw;
56+ }
57+
58+ nav.main-nav * {
59+ padding: 0;
60+ margin: 0;
61+ }
62+
63+ nav.main-nav > ul {
64+ display: flex;
65+ flex-wrap: wrap;
66+ row-gap: 1rem;
67+ list-style: none;
68+ }
69+
70+ nav.main-nav > ul > li > a {
71+ padding: 1rem;
72+ }
73+ nav.main-nav a[href] {
74+ text-decoration: underline;
75+ text-decoration-color: currentcolor;
76+ color: #034575;
77+ color: var(--a-normal-text);
78+ text-decoration-color: #707070;
79+ text-decoration-color: var(--accent-secondary);
80+ text-decoration-skip-ink: none;
81+ font-synthesis: none;
82+ font-feature-settings: "onum" 1;
83+ text-rendering: optimizeLegibility;
84+ font-family: -apple-system,BlinkMacSystemFont,Roboto,Roboto Slab,Droid Serif,Segoe UI,system-ui,Arial,sans-serif;
85+ font-size: 1.125em;
86+ }
87+ nav.main-nav > ul > li > a:hover {
88+ outline: 0.1rem solid;
89+ outline-offset: -0.5rem;
90+ }
91+ a[href]:focus, a[href]:hover {
92+ text-decoration-thickness: 2px;
93+ text-decoration-skip-ink: none;
94+ }
95+ </style>
96+ <nav class="main-nav">
97+ <ul>
98+ <li><a href="https://git.meli.delivery/meli/mailpot">Gitea Repository</a></li>
99+ <li><a href="https://github.com/meli/mailpot">Github Repository</a></li>
100+ <li><a href="https://lists.meli.delivery/">Official Instance</a></li>
101+ <li><a href="https://meli.github.io/mailpot/lists/2/">Static Demo</a></li>
102+ </ul>
103+ </nav>
104 diff --git a/.github/workflows/grcov.yaml b/.github/workflows/grcov.yaml
105index 88f9544..6646048 100644
106--- a/.github/workflows/grcov.yaml
107+++ b/.github/workflows/grcov.yaml
108 @@ -84,8 +84,8 @@ jobs:
109 run: ls -R
110 - name: Push
111 run: |
112- git config --global user.name 'Github Actions'
113- git config --global user.email 'actions@users.noreply.github.com'
114+ git config --global user.name 'github-actions[bot]'
115+ git config --global user.email 'github-actions[bot]@users.noreply.github.com'
116 git show-ref
117 git add coverage
118 git commit -m "Update grcov report"
119 diff --git a/.github/workflows/rustdoc.yml b/.github/workflows/rustdoc.yml
120new file mode 100644
121index 0000000..d52d860
122--- /dev/null
123+++ b/.github/workflows/rustdoc.yml
124 @@ -0,0 +1,81 @@
125+ name: Build rustdoc for Github Pages
126+
127+ env:
128+ RUST_BACKTRACE: 1
129+ CARGO_REGISTRIES_CRATES_IO_PROTOCOL: sparse
130+
131+ on:
132+ workflow_dispatch:
133+
134+ jobs:
135+ build-docs:
136+ runs-on: ubuntu-latest
137+ steps:
138+ - uses: actions/checkout@v1
139+ - id: cache-rustup
140+ name: Cache Rust toolchain
141+ uses: actions/cache@v3
142+ with:
143+ path: ~/.rustup
144+ key: toolchain-grcov
145+ - id: cache-cargo
146+ name: Cache Cargo
147+ uses: actions/cache@v3
148+ with:
149+ path: ~/.cargo
150+ key: cargo-grcov
151+ - uses: actions-rs/toolchain@v1
152+ with:
153+ toolchain: nightly
154+ override: true
155+ - name: Make rustdocs
156+ run: |
157+ make rustdoc
158+ rm -rf docs
159+ ls -R
160+ mv target/doc docs
161+ - name: Upload report artifacts
162+ uses: actions/upload-artifact@v3
163+ with:
164+ name: docs
165+ path: docs
166+
167+ deploy-docs:
168+ needs: build-docs
169+ permissions:
170+ pages: write
171+ id-token: write
172+
173+ environment:
174+ name: github-pages
175+ url: ${{ steps.deployment.outputs.page_url }}
176+
177+ runs-on: ubuntu-latest
178+ steps:
179+ - name: Deploy to GitHub Pages
180+ id: deployment
181+ uses: actions/checkout@v3
182+ with:
183+ ref: 'gh-pages'
184+ token: ${{ secrets.GRCOVGHPAGES }}
185+ - name: Download docs
186+ id: download
187+ uses: actions/download-artifact@v3
188+ with:
189+ name: docs
190+ path: docs
191+ - name: 'Echo download path'
192+ run: echo ${{steps.download.outputs.download-path}}
193+ - name: Display structure of downloaded files
194+ run: ls -R
195+ - name: Push
196+ run: |
197+ git config --global user.name 'github-actions[bot]'
198+ git config --global user.email 'github-actions[bot]@users.noreply.github.com'
199+ git show-ref
200+ git add docs
201+ git commit -m "Update rustdoc"
202+ git show-ref
203+ git branch --verbose
204+ git remote set-url origin "https://${{github.actor}}:${{ secrets.GRCOVGHPAGES }}@github.com/${{github.repository}}.git"
205+ git push
206 diff --git a/Makefile b/Makefile
207index 36c0206..54adb8b 100644
208--- a/Makefile
209+++ b/Makefile
210 @@ -21,3 +21,7 @@ lint:
211 .PHONY: test
212 test: check lint
213 @cargo test --all --no-fail-fast --all-features
214+
215+ .PHONY: rustdoc
216+ rustdoc:
217+ @RUSTDOCFLAGS="--html-before-content ./.github/doc_extra.html" cargo doc --workspace --all-features --no-deps --document-private-items
218 diff --git a/archive-http/src/cal.rs b/archive-http/src/cal.rs
219index fc0ede5..571950d 100644
220--- a/archive-http/src/cal.rs
221+++ b/archive-http/src/cal.rs
222 @@ -31,7 +31,7 @@ use chrono::*;
223 ///
224 /// # Examples
225 /// ```
226- /// use calendarize::calendarize;
227+ /// use mailpot_archives::cal::calendarize;
228 /// use chrono::*;
229 ///
230 /// let date = NaiveDate::parse_from_str("2021-01-02", "%Y-%m-%d").unwrap();
231 @@ -59,7 +59,7 @@ pub fn calendarize(date: NaiveDate) -> Vec<[u32; 7]> {
232 ///
233 /// # Examples
234 /// ```
235- /// use calendarize::calendarize_with_offset;
236+ /// use mailpot_archives::cal::calendarize_with_offset;
237 /// use chrono::*;
238 ///
239 /// let date = NaiveDate::parse_from_str("2021-01-02", "%Y-%m-%d").unwrap();
240 diff --git a/archive-http/src/gen.rs b/archive-http/src/gen.rs
241index 6516a8e..9f9025a 100644
242--- a/archive-http/src/gen.rs
243+++ b/archive-http/src/gen.rs
244 @@ -17,19 +17,11 @@
245 * along with this program. If not, see <https://www.gnu.org/licenses/>.
246 */
247
248- use chrono::Datelike;
249+ use std::{fs::OpenOptions, io::Write};
250
251- mod cal;
252- mod utils;
253-
254- use std::{borrow::Cow, fs::OpenOptions, io::Write};
255-
256- pub use mailpot::{models::DbVal, *};
257- use minijinja::{
258- value::{Object, Value},
259- Environment, Error, Source, State,
260- };
261- use utils::*;
262+ use mailpot::*;
263+ use mailpot_archives::utils::*;
264+ use minijinja::value::Value;
265
266 fn run_app() -> std::result::Result<(), Box<dyn std::error::Error>> {
267 let args = std::env::args().collect::<Vec<_>>();
268 diff --git a/archive-http/src/lib.rs b/archive-http/src/lib.rs
269new file mode 100644
270index 0000000..bf855fd
271--- /dev/null
272+++ b/archive-http/src/lib.rs
273 @@ -0,0 +1,21 @@
274+ /*
275+ * This file is part of mailpot
276+ *
277+ * Copyright 2020 - Manos Pitsidianakis
278+ *
279+ * This program is free software: you can redistribute it and/or modify
280+ * it under the terms of the GNU Affero General Public License as
281+ * published by the Free Software Foundation, either version 3 of the
282+ * License, or (at your option) any later version.
283+ *
284+ * This program is distributed in the hope that it will be useful,
285+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
286+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
287+ * GNU Affero General Public License for more details.
288+ *
289+ * You should have received a copy of the GNU Affero General Public License
290+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
291+ */
292+
293+ pub mod cal;
294+ pub mod utils;
295 diff --git a/archive-http/src/main.rs b/archive-http/src/main.rs
296index 3bd3e27..b50ffcc 100644
297--- a/archive-http/src/main.rs
298+++ b/archive-http/src/main.rs
299 @@ -17,20 +17,10 @@
300 * along with this program. If not, see <https://www.gnu.org/licenses/>.
301 */
302
303- use chrono::Datelike;
304-
305- mod cal;
306- mod utils;
307-
308- use std::borrow::Cow;
309-
310- pub use mailpot::{models::DbVal, *};
311- use minijinja::{
312- value::{Object, Value},
313- Environment, Error, Source, State,
314- };
315+ use mailpot::*;
316+ use mailpot_archives::utils::*;
317+ use minijinja::value::Value;
318 use percent_encoding::percent_decode_str;
319- use utils::*;
320 use warp::Filter;
321
322 #[tokio::main]
323 diff --git a/archive-http/src/utils.rs b/archive-http/src/utils.rs
324index c53f74b..7e2597b 100644
325--- a/archive-http/src/utils.rs
326+++ b/archive-http/src/utils.rs
327 @@ -17,7 +17,14 @@
328 * along with this program. If not, see <https://www.gnu.org/licenses/>.
329 */
330
331- use super::*;
332+ use std::borrow::Cow;
333+
334+ use chrono::{Datelike, Month};
335+ use mailpot::{models::DbVal, *};
336+ use minijinja::{
337+ value::{Object, Value},
338+ Environment, Error, Source, State,
339+ };
340
341 lazy_static::lazy_static! {
342 pub static ref TEMPLATES: Environment<'static> = {
343 @@ -131,8 +138,6 @@ impl minijinja::value::StructObject for MailingList {
344 }
345
346 pub fn calendarize(_state: &State, args: Value, hists: Value) -> std::result::Result<Value, Error> {
347- use chrono::Month;
348-
349 macro_rules! month {
350 ($int:expr) => {{
351 let int = $int;
352 @@ -175,7 +180,7 @@ pub fn calendarize(_state: &State, args: Value, hists: Value) -> std::result::Re
353 month => month,
354 month_int => date.month() as usize,
355 year => date.year(),
356- weeks => cal::calendarize_with_offset(date, 1),
357+ weeks => crate::cal::calendarize_with_offset(date, 1),
358 hist => hist,
359 sum => sum,
360 })
361 diff --git a/cli/build.rs b/cli/build.rs
362index a913f38..0f3e9a4 100644
363--- a/cli/build.rs
364+++ b/cli/build.rs
365 @@ -27,10 +27,10 @@ use clap::ArgAction;
366 use clap_mangen::{roff, Man};
367 use roff::{bold, italic, roman, Inline, Roff};
368
369- include!("src/args.rs");
370+ include!("src/lib.rs");
371
372 fn main() -> std::io::Result<()> {
373- println!("cargo:rerun-if-changed=./src/args.rs");
374+ println!("cargo:rerun-if-changed=./src/lib.rs");
375 println!("cargo:rerun-if-changed=./build.rs");
376 std::env::set_current_dir("..").expect("could not chdir('..')");
377
378 diff --git a/cli/src/args.rs b/cli/src/args.rs
379deleted file mode 100644
380index a1acaa4..0000000
381--- a/cli/src/args.rs
382+++ /dev/null
383 @@ -1,438 +0,0 @@
384- /*
385- * This file is part of mailpot
386- *
387- * Copyright 2020 - Manos Pitsidianakis
388- *
389- * This program is free software: you can redistribute it and/or modify
390- * it under the terms of the GNU Affero General Public License as
391- * published by the Free Software Foundation, either version 3 of the
392- * License, or (at your option) any later version.
393- *
394- * This program is distributed in the hope that it will be useful,
395- * but WITHOUT ANY WARRANTY; without even the implied warranty of
396- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
397- * GNU Affero General Public License for more details.
398- *
399- * You should have received a copy of the GNU Affero General Public License
400- * along with this program. If not, see <https://www.gnu.org/licenses/>.
401- */
402-
403- use std::path::PathBuf;
404-
405- pub use clap::{Args, CommandFactory, Parser, Subcommand};
406-
407- #[derive(Debug, Parser)]
408- #[command(
409- name = "mpot",
410- about = "mailing list manager",
411- long_about = "Tool for mailpot mailing list management.",
412- before_long_help = "GNU Affero version 3 or later <https://www.gnu.org/licenses/>",
413- author,
414- version
415- )]
416- pub struct Opt {
417- /// Print logs.
418- #[arg(short, long)]
419- pub debug: bool,
420- /// Configuration file to use.
421- #[arg(short, long, value_parser)]
422- pub config: Option<PathBuf>,
423- #[command(subcommand)]
424- pub cmd: Command,
425- /// Silence all output.
426- #[arg(short, long)]
427- pub quiet: bool,
428- /// Verbose mode (-v, -vv, -vvv, etc).
429- #[arg(short, long, action = clap::ArgAction::Count)]
430- pub verbose: u8,
431- /// Debug log timestamp (sec, ms, ns, none).
432- #[arg(short, long)]
433- pub ts: Option<stderrlog::Timestamp>,
434- }
435-
436- #[derive(Debug, Subcommand)]
437- pub enum Command {
438- /// Prints a sample config file to STDOUT.
439- ///
440- /// You can generate a new configuration file by writing the output to a
441- /// file, e.g: mpot sample-config > config.toml
442- SampleConfig,
443- /// Dumps database data to STDOUT.
444- DumpDatabase,
445- /// Lists all registered mailing lists.
446- ListLists,
447- /// Mailing list management.
448- List {
449- /// Selects mailing list to operate on.
450- list_id: String,
451- #[command(subcommand)]
452- cmd: ListCommand,
453- },
454- /// Create new list.
455- CreateList {
456- /// List name.
457- #[arg(long)]
458- name: String,
459- /// List ID.
460- #[arg(long)]
461- id: String,
462- /// List e-mail address.
463- #[arg(long)]
464- address: String,
465- /// List description.
466- #[arg(long)]
467- description: Option<String>,
468- /// List archive URL.
469- #[arg(long)]
470- archive_url: Option<String>,
471- },
472- /// Post message from STDIN to list.
473- Post {
474- /// Show e-mail processing result without actually consuming it.
475- #[arg(long)]
476- dry_run: bool,
477- },
478- /// Flush outgoing e-mail queue.
479- FlushQueue {
480- /// Show e-mail processing result without actually consuming it.
481- #[arg(long)]
482- dry_run: bool,
483- },
484- /// Mail that has not been handled properly end up in the error queue.
485- ErrorQueue {
486- #[command(subcommand)]
487- cmd: ErrorQueueCommand,
488- },
489- /// Import a maildir folder into an existing list.
490- ImportMaildir {
491- /// List-ID or primary key value.
492- list_id: String,
493- /// Path to a maildir mailbox.
494- /// Must contain {cur, tmp, new} folders.
495- #[arg(long, value_parser)]
496- maildir_path: PathBuf,
497- },
498- /// Update postfix maps and master.cf (probably needs root permissions).
499- UpdatePostfixConfig {
500- #[arg(short = 'p', long)]
501- /// Override location of master.cf file (default:
502- /// /etc/postfix/master.cf)
503- master_cf: Option<PathBuf>,
504- #[clap(flatten)]
505- config: PostfixConfig,
506- },
507- /// Print postfix maps and master.cf entry to STDOUT.
508- ///
509- /// Map output should be added to transport_maps and local_recipient_maps
510- /// parameters in postfix's main.cf. It must be saved in a plain text
511- /// file. To make postfix be able to read them, the postmap application
512- /// must be executed with the path to the map file as its sole argument.
513- ///
514- /// postmap /path/to/mylist_maps
515- ///
516- /// postmap is usually distributed along with the other postfix binaries.
517- ///
518- /// The master.cf entry must be manually appended to the master.cf file. See <https://www.postfix.org/master.5.html>.
519- PrintPostfixConfig {
520- #[clap(flatten)]
521- config: PostfixConfig,
522- },
523- /// All Accounts.
524- Accounts,
525- /// Account info.
526- AccountInfo {
527- /// Account address.
528- address: String,
529- },
530- /// Add account.
531- AddAccount {
532- /// E-mail address.
533- #[arg(long)]
534- address: String,
535- /// SSH public key for authentication.
536- #[arg(long)]
537- password: String,
538- /// Name.
539- #[arg(long)]
540- name: Option<String>,
541- /// Public key.
542- #[arg(long)]
543- public_key: Option<String>,
544- #[arg(long)]
545- /// Is account enabled.
546- enabled: Option<bool>,
547- },
548- /// Remove account.
549- RemoveAccount {
550- #[arg(long)]
551- /// E-mail address.
552- address: String,
553- },
554- /// Update account info.
555- UpdateAccount {
556- /// Address to edit.
557- address: String,
558- /// Public key for authentication.
559- #[arg(long)]
560- password: Option<String>,
561- /// Name.
562- #[arg(long)]
563- name: Option<Option<String>>,
564- /// Public key.
565- #[arg(long)]
566- public_key: Option<Option<String>>,
567- #[arg(long)]
568- /// Is account enabled.
569- enabled: Option<Option<bool>>,
570- },
571- }
572-
573- /// Postfix config values.
574- #[derive(Debug, Args)]
575- pub struct PostfixConfig {
576- /// User that runs mailpot when postfix relays a message.
577- ///
578- /// Must not be the `postfix` user.
579- /// Must have permissions to access the database file and the data
580- /// directory.
581- #[arg(short, long)]
582- pub user: String,
583- /// Group that runs mailpot when postfix relays a message.
584- /// Optional.
585- #[arg(short, long)]
586- pub group: Option<String>,
587- /// The path to the mailpot binary postfix will execute.
588- #[arg(long)]
589- pub binary_path: PathBuf,
590- /// Limit the number of mailpot instances that can exist at the same time.
591- ///
592- /// Default is 1.
593- #[arg(long, default_value = "1")]
594- pub process_limit: Option<u64>,
595- /// The directory in which the map files are saved.
596- ///
597- /// Default is `data_path` from [`Configuration`](mailpot::Configuration).
598- #[arg(long)]
599- pub map_output_path: Option<PathBuf>,
600- /// The name of the postfix service name to use.
601- /// Default is `mailpot`.
602- ///
603- /// A postfix service is a daemon managed by the postfix process.
604- /// Each entry in the `master.cf` configuration file defines a single
605- /// service.
606- ///
607- /// The `master.cf` file is documented in [`master(5)`](https://www.postfix.org/master.5.html):
608- /// <https://www.postfix.org/master.5.html>.
609- #[arg(long)]
610- pub transport_name: Option<String>,
611- }
612-
613- #[derive(Debug, Subcommand)]
614- pub enum ErrorQueueCommand {
615- /// List.
616- List,
617- /// Print entry in RFC5322 or JSON format.
618- Print {
619- /// index of entry.
620- #[arg(long)]
621- index: Vec<i64>,
622- },
623- /// Delete entry and print it in stdout.
624- Delete {
625- /// index of entry.
626- #[arg(long)]
627- index: Vec<i64>,
628- /// Do not print in stdout.
629- #[arg(long)]
630- quiet: bool,
631- },
632- }
633-
634- /// Subscription options.
635- #[derive(Debug, Args)]
636- pub struct SubscriptionOptions {
637- /// Name.
638- #[arg(long)]
639- pub name: Option<String>,
640- /// Send messages as digest.
641- #[arg(long, default_value = "false")]
642- pub digest: Option<bool>,
643- /// Hide message from list when posting.
644- #[arg(long, default_value = "false")]
645- pub hide_address: Option<bool>,
646- /// Hide message from list when posting.
647- #[arg(long, default_value = "false")]
648- /// E-mail address verification status.
649- pub verified: Option<bool>,
650- #[arg(long, default_value = "true")]
651- /// Receive confirmation email when posting.
652- pub receive_confirmation: Option<bool>,
653- #[arg(long, default_value = "true")]
654- /// Receive posts from list even if address exists in To or Cc header.
655- pub receive_duplicates: Option<bool>,
656- #[arg(long, default_value = "false")]
657- /// Receive own posts from list.
658- pub receive_own_posts: Option<bool>,
659- #[arg(long, default_value = "true")]
660- /// Is subscription enabled.
661- pub enabled: Option<bool>,
662- }
663-
664- /// Account options.
665- #[derive(Debug, Args)]
666- pub struct AccountOptions {
667- /// Name.
668- #[arg(long)]
669- pub name: Option<String>,
670- /// Public key.
671- #[arg(long)]
672- pub public_key: Option<String>,
673- #[arg(long)]
674- /// Is account enabled.
675- pub enabled: Option<bool>,
676- }
677-
678- #[derive(Debug, Subcommand)]
679- pub enum ListCommand {
680- /// List subscriptions of list.
681- Subscriptions,
682- /// Add subscription to list.
683- AddSubscription {
684- /// E-mail address.
685- #[arg(long)]
686- address: String,
687- #[clap(flatten)]
688- subscription_options: SubscriptionOptions,
689- },
690- /// Remove subscription from list.
691- RemoveSubscription {
692- #[arg(long)]
693- /// E-mail address.
694- address: String,
695- },
696- /// Update subscription info.
697- UpdateSubscription {
698- /// Address to edit.
699- address: String,
700- #[clap(flatten)]
701- subscription_options: SubscriptionOptions,
702- },
703- /// Add a new post policy.
704- AddPolicy {
705- #[arg(long)]
706- /// Only list owners can post.
707- announce_only: bool,
708- #[arg(long)]
709- /// Only subscriptions can post.
710- subscription_only: bool,
711- #[arg(long)]
712- /// Subscriptions can post.
713- /// Other posts must be approved by list owners.
714- approval_needed: bool,
715- #[arg(long)]
716- /// Anyone can post without restrictions.
717- open: bool,
718- #[arg(long)]
719- /// Allow posts, but handle it manually.
720- custom: bool,
721- },
722- // Remove post policy.
723- RemovePolicy {
724- #[arg(long)]
725- /// Post policy primary key.
726- pk: i64,
727- },
728- /// Add subscription policy to list.
729- AddSubscribePolicy {
730- #[arg(long)]
731- /// Send confirmation e-mail when subscription is finalized.
732- send_confirmation: bool,
733- #[arg(long)]
734- /// Anyone can subscribe without restrictions.
735- open: bool,
736- #[arg(long)]
737- /// Only list owners can manually add subscriptions.
738- manual: bool,
739- #[arg(long)]
740- /// Anyone can request to subscribe.
741- request: bool,
742- #[arg(long)]
743- /// Allow subscriptions, but handle it manually.
744- custom: bool,
745- },
746- RemoveSubscribePolicy {
747- #[arg(long)]
748- /// Subscribe policy primary key.
749- pk: i64,
750- },
751- /// Add list owner to list.
752- AddListOwner {
753- #[arg(long)]
754- address: String,
755- #[arg(long)]
756- name: Option<String>,
757- },
758- RemoveListOwner {
759- #[arg(long)]
760- /// List owner primary key.
761- pk: i64,
762- },
763- /// Alias for update-subscription --enabled true.
764- EnableSubscription {
765- /// Subscription address.
766- address: String,
767- },
768- /// Alias for update-subscription --enabled false.
769- DisableSubscription {
770- /// Subscription address.
771- address: String,
772- },
773- /// Update mailing list details.
774- Update {
775- /// New list name.
776- #[arg(long)]
777- name: Option<String>,
778- /// New List-ID.
779- #[arg(long)]
780- id: Option<String>,
781- /// New list address.
782- #[arg(long)]
783- address: Option<String>,
784- /// New list description.
785- #[arg(long)]
786- description: Option<String>,
787- /// New list archive URL.
788- #[arg(long)]
789- archive_url: Option<String>,
790- /// New owner address local part.
791- /// If empty, it defaults to '+owner'.
792- #[arg(long)]
793- owner_local_part: Option<String>,
794- /// New request address local part.
795- /// If empty, it defaults to '+request'.
796- #[arg(long)]
797- request_local_part: Option<String>,
798- /// Require verification of e-mails for new subscriptions.
799- ///
800- /// Subscriptions that are initiated from the subscription's address are
801- /// verified automatically.
802- #[arg(long)]
803- verify: Option<bool>,
804- /// Public visibility of list.
805- ///
806- /// If hidden, the list will not show up in public APIs unless
807- /// requests to it won't work.
808- #[arg(long)]
809- hidden: Option<bool>,
810- /// Enable or disable the list's functionality.
811- ///
812- /// If not enabled, the list will continue to show up in the database
813- /// but e-mails and requests to it won't work.
814- #[arg(long)]
815- enabled: Option<bool>,
816- },
817- /// Show mailing list health status.
818- Health,
819- /// Show mailing list info.
820- Info,
821- }
822 diff --git a/cli/src/lib.rs b/cli/src/lib.rs
823new file mode 100644
824index 0000000..67c6de3
825--- /dev/null
826+++ b/cli/src/lib.rs
827 @@ -0,0 +1,438 @@
828+ /*
829+ * This file is part of mailpot
830+ *
831+ * Copyright 2020 - Manos Pitsidianakis
832+ *
833+ * This program is free software: you can redistribute it and/or modify
834+ * it under the terms of the GNU Affero General Public License as
835+ * published by the Free Software Foundation, either version 3 of the
836+ * License, or (at your option) any later version.
837+ *
838+ * This program is distributed in the hope that it will be useful,
839+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
840+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
841+ * GNU Affero General Public License for more details.
842+ *
843+ * You should have received a copy of the GNU Affero General Public License
844+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
845+ */
846+
847+ pub use std::path::PathBuf;
848+
849+ pub use clap::{Args, CommandFactory, Parser, Subcommand};
850+
851+ #[derive(Debug, Parser)]
852+ #[command(
853+ name = "mpot",
854+ about = "mailing list manager",
855+ long_about = "Tool for mailpot mailing list management.",
856+ before_long_help = "GNU Affero version 3 or later <https://www.gnu.org/licenses/>",
857+ author,
858+ version
859+ )]
860+ pub struct Opt {
861+ /// Print logs.
862+ #[arg(short, long)]
863+ pub debug: bool,
864+ /// Configuration file to use.
865+ #[arg(short, long, value_parser)]
866+ pub config: Option<PathBuf>,
867+ #[command(subcommand)]
868+ pub cmd: Command,
869+ /// Silence all output.
870+ #[arg(short, long)]
871+ pub quiet: bool,
872+ /// Verbose mode (-v, -vv, -vvv, etc).
873+ #[arg(short, long, action = clap::ArgAction::Count)]
874+ pub verbose: u8,
875+ /// Debug log timestamp (sec, ms, ns, none).
876+ #[arg(short, long)]
877+ pub ts: Option<stderrlog::Timestamp>,
878+ }
879+
880+ #[derive(Debug, Subcommand)]
881+ pub enum Command {
882+ /// Prints a sample config file to STDOUT.
883+ ///
884+ /// You can generate a new configuration file by writing the output to a
885+ /// file, e.g: mpot sample-config > config.toml
886+ SampleConfig,
887+ /// Dumps database data to STDOUT.
888+ DumpDatabase,
889+ /// Lists all registered mailing lists.
890+ ListLists,
891+ /// Mailing list management.
892+ List {
893+ /// Selects mailing list to operate on.
894+ list_id: String,
895+ #[command(subcommand)]
896+ cmd: ListCommand,
897+ },
898+ /// Create new list.
899+ CreateList {
900+ /// List name.
901+ #[arg(long)]
902+ name: String,
903+ /// List ID.
904+ #[arg(long)]
905+ id: String,
906+ /// List e-mail address.
907+ #[arg(long)]
908+ address: String,
909+ /// List description.
910+ #[arg(long)]
911+ description: Option<String>,
912+ /// List archive URL.
913+ #[arg(long)]
914+ archive_url: Option<String>,
915+ },
916+ /// Post message from STDIN to list.
917+ Post {
918+ /// Show e-mail processing result without actually consuming it.
919+ #[arg(long)]
920+ dry_run: bool,
921+ },
922+ /// Flush outgoing e-mail queue.
923+ FlushQueue {
924+ /// Show e-mail processing result without actually consuming it.
925+ #[arg(long)]
926+ dry_run: bool,
927+ },
928+ /// Mail that has not been handled properly end up in the error queue.
929+ ErrorQueue {
930+ #[command(subcommand)]
931+ cmd: ErrorQueueCommand,
932+ },
933+ /// Import a maildir folder into an existing list.
934+ ImportMaildir {
935+ /// List-ID or primary key value.
936+ list_id: String,
937+ /// Path to a maildir mailbox.
938+ /// Must contain {cur, tmp, new} folders.
939+ #[arg(long, value_parser)]
940+ maildir_path: PathBuf,
941+ },
942+ /// Update postfix maps and master.cf (probably needs root permissions).
943+ UpdatePostfixConfig {
944+ #[arg(short = 'p', long)]
945+ /// Override location of master.cf file (default:
946+ /// /etc/postfix/master.cf)
947+ master_cf: Option<PathBuf>,
948+ #[clap(flatten)]
949+ config: PostfixConfig,
950+ },
951+ /// Print postfix maps and master.cf entry to STDOUT.
952+ ///
953+ /// Map output should be added to transport_maps and local_recipient_maps
954+ /// parameters in postfix's main.cf. It must be saved in a plain text
955+ /// file. To make postfix be able to read them, the postmap application
956+ /// must be executed with the path to the map file as its sole argument.
957+ ///
958+ /// postmap /path/to/mylist_maps
959+ ///
960+ /// postmap is usually distributed along with the other postfix binaries.
961+ ///
962+ /// The master.cf entry must be manually appended to the master.cf file. See <https://www.postfix.org/master.5.html>.
963+ PrintPostfixConfig {
964+ #[clap(flatten)]
965+ config: PostfixConfig,
966+ },
967+ /// All Accounts.
968+ Accounts,
969+ /// Account info.
970+ AccountInfo {
971+ /// Account address.
972+ address: String,
973+ },
974+ /// Add account.
975+ AddAccount {
976+ /// E-mail address.
977+ #[arg(long)]
978+ address: String,
979+ /// SSH public key for authentication.
980+ #[arg(long)]
981+ password: String,
982+ /// Name.
983+ #[arg(long)]
984+ name: Option<String>,
985+ /// Public key.
986+ #[arg(long)]
987+ public_key: Option<String>,
988+ #[arg(long)]
989+ /// Is account enabled.
990+ enabled: Option<bool>,
991+ },
992+ /// Remove account.
993+ RemoveAccount {
994+ #[arg(long)]
995+ /// E-mail address.
996+ address: String,
997+ },
998+ /// Update account info.
999+ UpdateAccount {
1000+ /// Address to edit.
1001+ address: String,
1002+ /// Public key for authentication.
1003+ #[arg(long)]
1004+ password: Option<String>,
1005+ /// Name.
1006+ #[arg(long)]
1007+ name: Option<Option<String>>,
1008+ /// Public key.
1009+ #[arg(long)]
1010+ public_key: Option<Option<String>>,
1011+ #[arg(long)]
1012+ /// Is account enabled.
1013+ enabled: Option<Option<bool>>,
1014+ },
1015+ }
1016+
1017+ /// Postfix config values.
1018+ #[derive(Debug, Args)]
1019+ pub struct PostfixConfig {
1020+ /// User that runs mailpot when postfix relays a message.
1021+ ///
1022+ /// Must not be the `postfix` user.
1023+ /// Must have permissions to access the database file and the data
1024+ /// directory.
1025+ #[arg(short, long)]
1026+ pub user: String,
1027+ /// Group that runs mailpot when postfix relays a message.
1028+ /// Optional.
1029+ #[arg(short, long)]
1030+ pub group: Option<String>,
1031+ /// The path to the mailpot binary postfix will execute.
1032+ #[arg(long)]
1033+ pub binary_path: PathBuf,
1034+ /// Limit the number of mailpot instances that can exist at the same time.
1035+ ///
1036+ /// Default is 1.
1037+ #[arg(long, default_value = "1")]
1038+ pub process_limit: Option<u64>,
1039+ /// The directory in which the map files are saved.
1040+ ///
1041+ /// Default is `data_path` from [`Configuration`](mailpot::Configuration).
1042+ #[arg(long)]
1043+ pub map_output_path: Option<PathBuf>,
1044+ /// The name of the postfix service name to use.
1045+ /// Default is `mailpot`.
1046+ ///
1047+ /// A postfix service is a daemon managed by the postfix process.
1048+ /// Each entry in the `master.cf` configuration file defines a single
1049+ /// service.
1050+ ///
1051+ /// The `master.cf` file is documented in [`master(5)`](https://www.postfix.org/master.5.html):
1052+ /// <https://www.postfix.org/master.5.html>.
1053+ #[arg(long)]
1054+ pub transport_name: Option<String>,
1055+ }
1056+
1057+ #[derive(Debug, Subcommand)]
1058+ pub enum ErrorQueueCommand {
1059+ /// List.
1060+ List,
1061+ /// Print entry in RFC5322 or JSON format.
1062+ Print {
1063+ /// index of entry.
1064+ #[arg(long)]
1065+ index: Vec<i64>,
1066+ },
1067+ /// Delete entry and print it in stdout.
1068+ Delete {
1069+ /// index of entry.
1070+ #[arg(long)]
1071+ index: Vec<i64>,
1072+ /// Do not print in stdout.
1073+ #[arg(long)]
1074+ quiet: bool,
1075+ },
1076+ }
1077+
1078+ /// Subscription options.
1079+ #[derive(Debug, Args)]
1080+ pub struct SubscriptionOptions {
1081+ /// Name.
1082+ #[arg(long)]
1083+ pub name: Option<String>,
1084+ /// Send messages as digest.
1085+ #[arg(long, default_value = "false")]
1086+ pub digest: Option<bool>,
1087+ /// Hide message from list when posting.
1088+ #[arg(long, default_value = "false")]
1089+ pub hide_address: Option<bool>,
1090+ /// Hide message from list when posting.
1091+ #[arg(long, default_value = "false")]
1092+ /// E-mail address verification status.
1093+ pub verified: Option<bool>,
1094+ #[arg(long, default_value = "true")]
1095+ /// Receive confirmation email when posting.
1096+ pub receive_confirmation: Option<bool>,
1097+ #[arg(long, default_value = "true")]
1098+ /// Receive posts from list even if address exists in To or Cc header.
1099+ pub receive_duplicates: Option<bool>,
1100+ #[arg(long, default_value = "false")]
1101+ /// Receive own posts from list.
1102+ pub receive_own_posts: Option<bool>,
1103+ #[arg(long, default_value = "true")]
1104+ /// Is subscription enabled.
1105+ pub enabled: Option<bool>,
1106+ }
1107+
1108+ /// Account options.
1109+ #[derive(Debug, Args)]
1110+ pub struct AccountOptions {
1111+ /// Name.
1112+ #[arg(long)]
1113+ pub name: Option<String>,
1114+ /// Public key.
1115+ #[arg(long)]
1116+ pub public_key: Option<String>,
1117+ #[arg(long)]
1118+ /// Is account enabled.
1119+ pub enabled: Option<bool>,
1120+ }
1121+
1122+ #[derive(Debug, Subcommand)]
1123+ pub enum ListCommand {
1124+ /// List subscriptions of list.
1125+ Subscriptions,
1126+ /// Add subscription to list.
1127+ AddSubscription {
1128+ /// E-mail address.
1129+ #[arg(long)]
1130+ address: String,
1131+ #[clap(flatten)]
1132+ subscription_options: SubscriptionOptions,
1133+ },
1134+ /// Remove subscription from list.
1135+ RemoveSubscription {
1136+ #[arg(long)]
1137+ /// E-mail address.
1138+ address: String,
1139+ },
1140+ /// Update subscription info.
1141+ UpdateSubscription {
1142+ /// Address to edit.
1143+ address: String,
1144+ #[clap(flatten)]
1145+ subscription_options: SubscriptionOptions,
1146+ },
1147+ /// Add a new post policy.
1148+ AddPolicy {
1149+ #[arg(long)]
1150+ /// Only list owners can post.
1151+ announce_only: bool,
1152+ #[arg(long)]
1153+ /// Only subscriptions can post.
1154+ subscription_only: bool,
1155+ #[arg(long)]
1156+ /// Subscriptions can post.
1157+ /// Other posts must be approved by list owners.
1158+ approval_needed: bool,
1159+ #[arg(long)]
1160+ /// Anyone can post without restrictions.
1161+ open: bool,
1162+ #[arg(long)]
1163+ /// Allow posts, but handle it manually.
1164+ custom: bool,
1165+ },
1166+ // Remove post policy.
1167+ RemovePolicy {
1168+ #[arg(long)]
1169+ /// Post policy primary key.
1170+ pk: i64,
1171+ },
1172+ /// Add subscription policy to list.
1173+ AddSubscribePolicy {
1174+ #[arg(long)]
1175+ /// Send confirmation e-mail when subscription is finalized.
1176+ send_confirmation: bool,
1177+ #[arg(long)]
1178+ /// Anyone can subscribe without restrictions.
1179+ open: bool,
1180+ #[arg(long)]
1181+ /// Only list owners can manually add subscriptions.
1182+ manual: bool,
1183+ #[arg(long)]
1184+ /// Anyone can request to subscribe.
1185+ request: bool,
1186+ #[arg(long)]
1187+ /// Allow subscriptions, but handle it manually.
1188+ custom: bool,
1189+ },
1190+ RemoveSubscribePolicy {
1191+ #[arg(long)]
1192+ /// Subscribe policy primary key.
1193+ pk: i64,
1194+ },
1195+ /// Add list owner to list.
1196+ AddListOwner {
1197+ #[arg(long)]
1198+ address: String,
1199+ #[arg(long)]
1200+ name: Option<String>,
1201+ },
1202+ RemoveListOwner {
1203+ #[arg(long)]
1204+ /// List owner primary key.
1205+ pk: i64,
1206+ },
1207+ /// Alias for update-subscription --enabled true.
1208+ EnableSubscription {
1209+ /// Subscription address.
1210+ address: String,
1211+ },
1212+ /// Alias for update-subscription --enabled false.
1213+ DisableSubscription {
1214+ /// Subscription address.
1215+ address: String,
1216+ },
1217+ /// Update mailing list details.
1218+ Update {
1219+ /// New list name.
1220+ #[arg(long)]
1221+ name: Option<String>,
1222+ /// New List-ID.
1223+ #[arg(long)]
1224+ id: Option<String>,
1225+ /// New list address.
1226+ #[arg(long)]
1227+ address: Option<String>,
1228+ /// New list description.
1229+ #[arg(long)]
1230+ description: Option<String>,
1231+ /// New list archive URL.
1232+ #[arg(long)]
1233+ archive_url: Option<String>,
1234+ /// New owner address local part.
1235+ /// If empty, it defaults to '+owner'.
1236+ #[arg(long)]
1237+ owner_local_part: Option<String>,
1238+ /// New request address local part.
1239+ /// If empty, it defaults to '+request'.
1240+ #[arg(long)]
1241+ request_local_part: Option<String>,
1242+ /// Require verification of e-mails for new subscriptions.
1243+ ///
1244+ /// Subscriptions that are initiated from the subscription's address are
1245+ /// verified automatically.
1246+ #[arg(long)]
1247+ verify: Option<bool>,
1248+ /// Public visibility of list.
1249+ ///
1250+ /// If hidden, the list will not show up in public APIs unless
1251+ /// requests to it won't work.
1252+ #[arg(long)]
1253+ hidden: Option<bool>,
1254+ /// Enable or disable the list's functionality.
1255+ ///
1256+ /// If not enabled, the list will continue to show up in the database
1257+ /// but e-mails and requests to it won't work.
1258+ #[arg(long)]
1259+ enabled: Option<bool>,
1260+ },
1261+ /// Show mailing list health status.
1262+ Health,
1263+ /// Show mailing list info.
1264+ Info,
1265+ }
1266 diff --git a/cli/src/main.rs b/cli/src/main.rs
1267index d10f015..b46049c 100644
1268--- a/cli/src/main.rs
1269+++ b/cli/src/main.rs
1270 @@ -24,15 +24,12 @@ use std::{
1271 process::Stdio,
1272 };
1273
1274- pub use mailpot::{
1275- mail::*,
1276+ use mailpot::{
1277 melib::{backends::maildir::MaildirPathTrait, smol, Envelope, EnvelopeHash},
1278 models::{changesets::*, *},
1279 *,
1280 };
1281-
1282- mod args;
1283- use args::*;
1284+ use mailpot_cli::*;
1285
1286 macro_rules! list {
1287 ($db:ident, $list_id:expr) => {{
1288 diff --git a/cli/tests/out_queue_flush.rs b/cli/tests/out_queue_flush.rs
1289index b1a82c7..dc99b01 100644
1290--- a/cli/tests/out_queue_flush.rs
1291+++ b/cli/tests/out_queue_flush.rs
1292 @@ -21,6 +21,7 @@
1293
1294 use assert_cmd::assert::OutputAssertExt;
1295 use mailpot::{
1296+ melib,
1297 models::{changesets::ListSubscriptionChangeset, *},
1298 Configuration, Connection, Queue, SendMail,
1299 };
1300 diff --git a/core/build.rs b/core/build.rs
1301index 0e0d59c..77a3159 100644
1302--- a/core/build.rs
1303+++ b/core/build.rs
1304 @@ -29,6 +29,12 @@ fn main() {
1305 .arg("./src/schema.sql.m4")
1306 .output()
1307 .unwrap();
1308+ if String::from_utf8_lossy(&output.stdout).trim().is_empty() {
1309+ panic!(
1310+ "m4 output is empty. stderr was {}",
1311+ String::from_utf8_lossy(&output.stderr)
1312+ );
1313+ }
1314 let mut verify = Command::new("sqlite3")
1315 .stdin(Stdio::piped())
1316 .stdout(Stdio::piped())
1317 diff --git a/core/src/db.rs b/core/src/db.rs
1318index 38cb8e3..2f400bc 100644
1319--- a/core/src/db.rs
1320+++ b/core/src/db.rs
1321 @@ -131,6 +131,13 @@ fn user_authorizer_callback(
1322 }
1323
1324 impl Connection {
1325+ /// The database schema.
1326+ ///
1327+ /// ```sql
1328+ #[doc = include_str!("./schema.sql")]
1329+ /// ```
1330+ pub const SCHEMA: &str = include_str!("./schema.sql");
1331+
1332 /// Creates a new database connection.
1333 ///
1334 /// `Connection` supports a limited subset of operations by default (see
1335 diff --git a/core/src/lib.rs b/core/src/lib.rs
1336index a84b1a3..0139c2a 100644
1337--- a/core/src/lib.rs
1338+++ b/core/src/lib.rs
1339 @@ -48,6 +48,18 @@
1340
1341 //! Mailing list manager library.
1342 //!
1343+ //! Data is stored in a `sqlite3` database.
1344+ //! You can inspect the schema in [`SCHEMA`](crate::Connection::SCHEMA).
1345+ //!
1346+ //! # Usage
1347+ //!
1348+ //! `mailpot` can be used with the CLI tool in [`mailpot-cli`](mailpot-cli),
1349+ //! and/or in the web interface of the [`mailpot-web`](mailpot-web) crate.
1350+ //!
1351+ //! You can also directly use this crate as a library.
1352+ //!
1353+ //! # Example
1354+ //!
1355 //! ```
1356 //! use mailpot::{models::*, Configuration, Connection, SendMail};
1357 //! # use tempfile::TempDir;
1358 diff --git a/core/src/schema.sql b/core/src/schema.sql
1359index d556c54..9c5cf75 100644
1360--- a/core/src/schema.sql
1361+++ b/core/src/schema.sql
1362 @@ -2,19 +2,19 @@ PRAGMA foreign_keys = true;
1363 PRAGMA encoding = 'UTF-8';
1364
1365 CREATE TABLE IF NOT EXISTS list (
1366- pk INTEGER PRIMARY KEY NOT NULL,
1367- name TEXT NOT NULL,
1368- id TEXT NOT NULL UNIQUE,
1369- address TEXT NOT NULL UNIQUE,
1370- owner_local_part TEXT,
1371- request_local_part TEXT,
1372- archive_url TEXT,
1373- description TEXT,
1374- created INTEGER NOT NULL DEFAULT (unixepoch()),
1375- last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
1376- verify BOOLEAN CHECK (verify in (0, 1)) NOT NULL DEFAULT 1,
1377- hidden BOOLEAN CHECK (hidden in (0, 1)) NOT NULL DEFAULT 0,
1378- enabled BOOLEAN CHECK (enabled in (0, 1)) NOT NULL DEFAULT 1
1379+ pk INTEGER PRIMARY KEY NOT NULL,
1380+ name TEXT NOT NULL,
1381+ id TEXT NOT NULL UNIQUE,
1382+ address TEXT NOT NULL UNIQUE,
1383+ owner_local_part TEXT,
1384+ request_local_part TEXT,
1385+ archive_url TEXT,
1386+ description TEXT,
1387+ created INTEGER NOT NULL DEFAULT (unixepoch()),
1388+ last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
1389+ verify BOOLEAN CHECK (verify IN (0, 1)) NOT NULL DEFAULT 1,
1390+ hidden BOOLEAN CHECK (hidden IN (0, 1)) NOT NULL DEFAULT 0,
1391+ enabled BOOLEAN CHECK (enabled IN (0, 1)) NOT NULL DEFAULT 1
1392 );
1393
1394 CREATE TABLE IF NOT EXISTS owner (
1395 @@ -28,100 +28,243 @@ CREATE TABLE IF NOT EXISTS owner (
1396 );
1397
1398 CREATE TABLE IF NOT EXISTS post_policy (
1399- pk INTEGER PRIMARY KEY NOT NULL,
1400- list INTEGER NOT NULL UNIQUE,
1401- announce_only BOOLEAN CHECK (announce_only in (0, 1)) NOT NULL DEFAULT 0,
1402- subscription_only BOOLEAN CHECK (subscription_only in (0, 1)) NOT NULL DEFAULT 0,
1403- approval_needed BOOLEAN CHECK (approval_needed in (0, 1)) NOT NULL DEFAULT 0,
1404- open BOOLEAN CHECK (open in (0, 1)) NOT NULL DEFAULT 0,
1405- custom BOOLEAN CHECK (custom in (0, 1)) NOT NULL DEFAULT 0,
1406- created INTEGER NOT NULL DEFAULT (unixepoch()),
1407- last_modified INTEGER NOT NULL DEFAULT (unixepoch())
1408- CHECK(((custom) OR (((open) OR (((approval_needed) OR (((announce_only) OR (subscription_only)) AND NOT ((announce_only) AND (subscription_only)))) AND NOT ((approval_needed) AND (((announce_only) OR (subscription_only)) AND NOT ((announce_only) AND (subscription_only)))))) AND NOT ((open) AND (((approval_needed) OR (((announce_only) OR (subscription_only)) AND NOT ((announce_only) AND (subscription_only)))) AND NOT ((approval_needed) AND (((announce_only) OR (subscription_only)) AND NOT ((announce_only) AND (subscription_only)))))))) AND NOT ((custom) AND (((open) OR (((approval_needed) OR (((announce_only) OR (subscription_only)) AND NOT ((announce_only) AND (subscription_only)))) AND NOT ((approval_needed) AND (((announce_only) OR (subscription_only)) AND NOT ((announce_only) AND (subscription_only)))))) AND NOT ((open) AND (((approval_needed) OR (((announce_only) OR (subscription_only)) AND NOT ((announce_only) AND (subscription_only)))) AND NOT ((approval_needed) AND (((announce_only) OR (subscription_only)) AND NOT ((announce_only) AND (subscription_only))))))))),
1409+ pk INTEGER PRIMARY KEY NOT NULL,
1410+ list INTEGER NOT NULL UNIQUE,
1411+ announce_only BOOLEAN CHECK (announce_only IN (0, 1)) NOT NULL
1412+ DEFAULT 0,
1413+ subscription_only BOOLEAN CHECK (subscription_only IN (0, 1)) NOT NULL
1414+ DEFAULT 0,
1415+ approval_needed BOOLEAN CHECK (approval_needed IN (0, 1)) NOT NULL
1416+ DEFAULT 0,
1417+ open BOOLEAN CHECK (open IN (0, 1)) NOT NULL DEFAULT 0,
1418+ custom BOOLEAN CHECK (custom IN (0, 1)) NOT NULL DEFAULT 0,
1419+ created INTEGER NOT NULL DEFAULT (unixepoch()),
1420+ last_modified INTEGER NOT NULL DEFAULT (unixepoch())
1421+ CHECK((
1422+ (custom) OR ((
1423+ (open) OR ((
1424+ (approval_needed) OR ((
1425+ (announce_only) OR (subscription_only)
1426+ )
1427+ AND NOT
1428+ (
1429+ (announce_only) AND (subscription_only)
1430+ ))
1431+ )
1432+ AND NOT
1433+ (
1434+ (approval_needed) AND ((
1435+ (announce_only) OR (subscription_only)
1436+ )
1437+ AND NOT
1438+ (
1439+ (announce_only) AND (subscription_only)
1440+ ))
1441+ ))
1442+ )
1443+ AND NOT
1444+ (
1445+ (open) AND ((
1446+ (approval_needed) OR ((
1447+ (announce_only) OR (subscription_only)
1448+ )
1449+ AND NOT
1450+ (
1451+ (announce_only) AND (subscription_only)
1452+ ))
1453+ )
1454+ AND NOT
1455+ (
1456+ (approval_needed) AND ((
1457+ (announce_only) OR (subscription_only)
1458+ )
1459+ AND NOT
1460+ (
1461+ (announce_only) AND (subscription_only)
1462+ ))
1463+ ))
1464+ ))
1465+ )
1466+ AND NOT
1467+ (
1468+ (custom) AND ((
1469+ (open) OR ((
1470+ (approval_needed) OR ((
1471+ (announce_only) OR (subscription_only)
1472+ )
1473+ AND NOT
1474+ (
1475+ (announce_only) AND (subscription_only)
1476+ ))
1477+ )
1478+ AND NOT
1479+ (
1480+ (approval_needed) AND ((
1481+ (announce_only) OR (subscription_only)
1482+ )
1483+ AND NOT
1484+ (
1485+ (announce_only) AND (subscription_only)
1486+ ))
1487+ ))
1488+ )
1489+ AND NOT
1490+ (
1491+ (open) AND ((
1492+ (approval_needed) OR ((
1493+ (announce_only) OR (subscription_only)
1494+ )
1495+ AND NOT
1496+ (
1497+ (announce_only) AND (subscription_only)
1498+ ))
1499+ )
1500+ AND NOT
1501+ (
1502+ (approval_needed) AND ((
1503+ (announce_only) OR (subscription_only)
1504+ )
1505+ AND NOT
1506+ (
1507+ (announce_only) AND (subscription_only)
1508+ ))
1509+ ))
1510+ ))
1511+ )),
1512 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE
1513 );
1514
1515 CREATE TABLE IF NOT EXISTS subscription_policy (
1516- pk INTEGER PRIMARY KEY NOT NULL,
1517- list INTEGER NOT NULL UNIQUE,
1518- send_confirmation BOOLEAN CHECK (send_confirmation in (0, 1)) NOT NULL DEFAULT 1,
1519- open BOOLEAN CHECK (open in (0, 1)) NOT NULL DEFAULT 0,
1520- manual BOOLEAN CHECK (manual in (0, 1)) NOT NULL DEFAULT 0,
1521- request BOOLEAN CHECK (request in (0, 1)) NOT NULL DEFAULT 0,
1522- custom BOOLEAN CHECK (custom in (0, 1)) NOT NULL DEFAULT 0,
1523- created INTEGER NOT NULL DEFAULT (unixepoch()),
1524- last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
1525- CHECK(((open) OR (((manual) OR (((request) OR (custom)) AND NOT ((request) AND (custom)))) AND NOT ((manual) AND (((request) OR (custom)) AND NOT ((request) AND (custom)))))) AND NOT ((open) AND (((manual) OR (((request) OR (custom)) AND NOT ((request) AND (custom)))) AND NOT ((manual) AND (((request) OR (custom)) AND NOT ((request) AND (custom))))))),
1526+ pk INTEGER PRIMARY KEY NOT NULL,
1527+ list INTEGER NOT NULL UNIQUE,
1528+ send_confirmation BOOLEAN CHECK (send_confirmation IN (0, 1)) NOT NULL
1529+ DEFAULT 1,
1530+ open BOOLEAN CHECK (open IN (0, 1)) NOT NULL DEFAULT 0,
1531+ manual BOOLEAN CHECK (manual IN (0, 1)) NOT NULL DEFAULT 0,
1532+ request BOOLEAN CHECK (request IN (0, 1)) NOT NULL DEFAULT 0,
1533+ custom BOOLEAN CHECK (custom IN (0, 1)) NOT NULL DEFAULT 0,
1534+ created INTEGER NOT NULL DEFAULT (unixepoch()),
1535+ last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
1536+ CHECK((
1537+ (open) OR ((
1538+ (manual) OR ((
1539+ (request) OR (custom)
1540+ )
1541+ AND NOT
1542+ (
1543+ (request) AND (custom)
1544+ ))
1545+ )
1546+ AND NOT
1547+ (
1548+ (manual) AND ((
1549+ (request) OR (custom)
1550+ )
1551+ AND NOT
1552+ (
1553+ (request) AND (custom)
1554+ ))
1555+ ))
1556+ )
1557+ AND NOT
1558+ (
1559+ (open) AND ((
1560+ (manual) OR ((
1561+ (request) OR (custom)
1562+ )
1563+ AND NOT
1564+ (
1565+ (request) AND (custom)
1566+ ))
1567+ )
1568+ AND NOT
1569+ (
1570+ (manual) AND ((
1571+ (request) OR (custom)
1572+ )
1573+ AND NOT
1574+ (
1575+ (request) AND (custom)
1576+ ))
1577+ ))
1578+ )),
1579 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE
1580 );
1581
1582 CREATE TABLE IF NOT EXISTS subscription (
1583- pk INTEGER PRIMARY KEY NOT NULL,
1584- list INTEGER NOT NULL,
1585- address TEXT NOT NULL,
1586- name TEXT,
1587- account INTEGER,
1588- enabled BOOLEAN CHECK (enabled in (0, 1)) NOT NULL DEFAULT 1,
1589- verified BOOLEAN CHECK (verified in (0, 1)) NOT NULL DEFAULT 1,
1590- digest BOOLEAN CHECK (digest in (0, 1)) NOT NULL DEFAULT 0,
1591- hide_address BOOLEAN CHECK (hide_address in (0, 1)) NOT NULL DEFAULT 0,
1592- receive_duplicates BOOLEAN CHECK (receive_duplicates in (0, 1)) NOT NULL DEFAULT 1,
1593- receive_own_posts BOOLEAN CHECK (receive_own_posts in (0, 1)) NOT NULL DEFAULT 0,
1594- receive_confirmation BOOLEAN CHECK (receive_confirmation in (0, 1)) NOT NULL DEFAULT 1,
1595- last_digest INTEGER NOT NULL DEFAULT (unixepoch()),
1596- created INTEGER NOT NULL DEFAULT (unixepoch()),
1597- last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
1598+ pk INTEGER PRIMARY KEY NOT NULL,
1599+ list INTEGER NOT NULL,
1600+ address TEXT NOT NULL,
1601+ name TEXT,
1602+ account INTEGER,
1603+ enabled BOOLEAN CHECK (enabled IN (0, 1)) NOT NULL
1604+ DEFAULT 1,
1605+ verified BOOLEAN CHECK (verified IN (0, 1)) NOT NULL
1606+ DEFAULT 1,
1607+ digest BOOLEAN CHECK (digest IN (0, 1)) NOT NULL
1608+ DEFAULT 0,
1609+ hide_address BOOLEAN CHECK (hide_address IN (0, 1)) NOT NULL
1610+ DEFAULT 0,
1611+ receive_duplicates BOOLEAN CHECK (receive_duplicates IN (0, 1)) NOT NULL
1612+ DEFAULT 1,
1613+ receive_own_posts BOOLEAN CHECK (receive_own_posts IN (0, 1)) NOT NULL
1614+ DEFAULT 0,
1615+ receive_confirmation BOOLEAN CHECK (receive_confirmation IN (0, 1)) NOT NULL
1616+ DEFAULT 1,
1617+ last_digest INTEGER NOT NULL DEFAULT (unixepoch()),
1618+ created INTEGER NOT NULL DEFAULT (unixepoch()),
1619+ last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
1620 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE,
1621 FOREIGN KEY (account) REFERENCES account(pk) ON DELETE SET NULL,
1622 UNIQUE (list, address) ON CONFLICT ROLLBACK
1623 );
1624
1625 CREATE TABLE IF NOT EXISTS account (
1626- pk INTEGER PRIMARY KEY NOT NULL,
1627- name TEXT,
1628- address TEXT NOT NULL UNIQUE,
1629- public_key TEXT,
1630- password TEXT NOT NULL,
1631- enabled BOOLEAN CHECK (enabled in (0, 1)) NOT NULL DEFAULT 1,
1632- created INTEGER NOT NULL DEFAULT (unixepoch()),
1633- last_modified INTEGER NOT NULL DEFAULT (unixepoch())
1634+ pk INTEGER PRIMARY KEY NOT NULL,
1635+ name TEXT,
1636+ address TEXT NOT NULL UNIQUE,
1637+ public_key TEXT,
1638+ password TEXT NOT NULL,
1639+ enabled BOOLEAN CHECK (enabled IN (0, 1)) NOT NULL DEFAULT 1,
1640+ created INTEGER NOT NULL DEFAULT (unixepoch()),
1641+ last_modified INTEGER NOT NULL DEFAULT (unixepoch())
1642 );
1643
1644 CREATE TABLE IF NOT EXISTS candidate_subscription (
1645- pk INTEGER PRIMARY KEY NOT NULL,
1646- list INTEGER NOT NULL,
1647- address TEXT NOT NULL,
1648- name TEXT,
1649- accepted INTEGER UNIQUE,
1650- created INTEGER NOT NULL DEFAULT (unixepoch()),
1651- last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
1652+ pk INTEGER PRIMARY KEY NOT NULL,
1653+ list INTEGER NOT NULL,
1654+ address TEXT NOT NULL,
1655+ name TEXT,
1656+ accepted INTEGER UNIQUE,
1657+ created INTEGER NOT NULL DEFAULT (unixepoch()),
1658+ last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
1659 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE,
1660 FOREIGN KEY (accepted) REFERENCES subscription(pk) ON DELETE CASCADE,
1661 UNIQUE (list, address) ON CONFLICT ROLLBACK
1662 );
1663
1664 CREATE TABLE IF NOT EXISTS post (
1665- pk INTEGER PRIMARY KEY NOT NULL,
1666- list INTEGER NOT NULL,
1667- envelope_from TEXT,
1668- address TEXT NOT NULL,
1669- message_id TEXT NOT NULL,
1670- message BLOB NOT NULL,
1671- headers_json TEXT,
1672- timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
1673- datetime TEXT NOT NULL DEFAULT (datetime()),
1674- created INTEGER NOT NULL DEFAULT (unixepoch())
1675+ pk INTEGER PRIMARY KEY NOT NULL,
1676+ list INTEGER NOT NULL,
1677+ envelope_from TEXT,
1678+ address TEXT NOT NULL,
1679+ message_id TEXT NOT NULL,
1680+ message BLOB NOT NULL,
1681+ headers_json TEXT,
1682+ timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
1683+ datetime TEXT NOT NULL DEFAULT (datetime()),
1684+ created INTEGER NOT NULL DEFAULT (unixepoch())
1685 );
1686
1687 CREATE TABLE IF NOT EXISTS templates (
1688- pk INTEGER PRIMARY KEY NOT NULL,
1689- name TEXT NOT NULL,
1690- list INTEGER,
1691- subject TEXT,
1692- headers_json TEXT,
1693- body TEXT NOT NULL,
1694- created INTEGER NOT NULL DEFAULT (unixepoch()),
1695- last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
1696+ pk INTEGER PRIMARY KEY NOT NULL,
1697+ name TEXT NOT NULL,
1698+ list INTEGER,
1699+ subject TEXT,
1700+ headers_json TEXT,
1701+ body TEXT NOT NULL,
1702+ created INTEGER NOT NULL DEFAULT (unixepoch()),
1703+ last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
1704 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE,
1705 UNIQUE (list, name) ON CONFLICT ROLLBACK
1706 );
1707 @@ -130,47 +273,56 @@ CREATE TABLE IF NOT EXISTS templates (
1708 --
1709 -- ## The "maildrop" queue
1710 --
1711- -- Messages that have been submitted but not yet processed, await processing in
1712- -- the "maildrop" queue. Messages can be added to the "maildrop" queue even when
1713- -- mailpot is not running.
1714+ -- Messages that have been submitted but not yet processed, await processing
1715+ -- in the "maildrop" queue. Messages can be added to the "maildrop" queue
1716+ -- even when mailpot is not running.
1717 --
1718 -- ## The "deferred" queue
1719 --
1720- -- When all the deliverable recipients for a message are delivered, and for some
1721- -- recipients delivery failed for a transient reason (it might succeed later), the
1722- -- message is placed in the "deferred" queue.
1723+ -- When all the deliverable recipients for a message are delivered, and for
1724+ -- some recipients delivery failed for a transient reason (it might succeed
1725+ -- later), the message is placed in the "deferred" queue.
1726 --
1727 -- ## The "hold" queue
1728 --
1729- -- List administrators may introduce rules for emails to be placed indefinitely in
1730- -- the "hold" queue. Messages placed in the "hold" queue stay there until the
1731- -- administrator intervenes. No periodic delivery attempts are made for messages
1732- -- in the "hold" queue.
1733+ -- List administrators may introduce rules for emails to be placed
1734+ -- indefinitely in the "hold" queue. Messages placed in the "hold" queue stay
1735+ -- there until the administrator intervenes. No periodic delivery attempts
1736+ -- are made for messages in the "hold" queue.
1737
1738 -- ## The "out" queue
1739 --
1740 -- Emails that must be sent as soon as possible.
1741 CREATE TABLE IF NOT EXISTS queue (
1742- pk INTEGER PRIMARY KEY NOT NULL,
1743- which TEXT CHECK (which IN ('maildrop', 'hold', 'deferred', 'corrupt', 'error', 'out')) NOT NULL,
1744- list INTEGER,
1745- comment TEXT,
1746- to_addresses TEXT NOT NULL,
1747- from_address TEXT NOT NULL,
1748- subject TEXT NOT NULL,
1749- message_id TEXT NOT NULL,
1750- message BLOB NOT NULL,
1751- timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
1752- datetime TEXT NOT NULL DEFAULT (datetime()),
1753+ pk INTEGER PRIMARY KEY NOT NULL,
1754+ which TEXT
1755+ CHECK (
1756+ which IN
1757+ ('maildrop',
1758+ 'hold',
1759+ 'deferred',
1760+ 'corrupt',
1761+ 'error',
1762+ 'out')
1763+ ) NOT NULL,
1764+ list INTEGER,
1765+ comment TEXT,
1766+ to_addresses TEXT NOT NULL,
1767+ from_address TEXT NOT NULL,
1768+ subject TEXT NOT NULL,
1769+ message_id TEXT NOT NULL,
1770+ message BLOB NOT NULL,
1771+ timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
1772+ datetime TEXT NOT NULL DEFAULT (datetime()),
1773 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE,
1774 UNIQUE (to_addresses, message_id) ON CONFLICT ROLLBACK
1775 );
1776
1777 CREATE TABLE IF NOT EXISTS bounce (
1778- pk INTEGER PRIMARY KEY NOT NULL,
1779- subscription INTEGER NOT NULL UNIQUE,
1780- count INTEGER NOT NULL DEFAULT 0,
1781- last_bounce TEXT NOT NULL DEFAULT (datetime()),
1782+ pk INTEGER PRIMARY KEY NOT NULL,
1783+ subscription INTEGER NOT NULL UNIQUE,
1784+ count INTEGER NOT NULL DEFAULT 0,
1785+ last_bounce TEXT NOT NULL DEFAULT (datetime()),
1786 FOREIGN KEY (subscription) REFERENCES subscription(pk) ON DELETE CASCADE
1787 );
1788
1789 @@ -179,7 +331,8 @@ CREATE INDEX IF NOT EXISTS post_msgid_idx ON post(message_id);
1790 CREATE INDEX IF NOT EXISTS list_idx ON list(id);
1791 CREATE INDEX IF NOT EXISTS subscription_idx ON subscription(address);
1792
1793- -- [tag:accept_candidate]: Update candidacy with 'subscription' foreign key on 'subscription' insert.
1794+ -- [tag:accept_candidate]: Update candidacy with 'subscription' foreign key on
1795+ -- 'subscription' insert.
1796 CREATE TRIGGER IF NOT EXISTS accept_candidate AFTER INSERT ON subscription
1797 FOR EACH ROW
1798 BEGIN
1799 @@ -187,13 +340,18 @@ BEGIN
1800 WHERE candidate_subscription.list = NEW.list AND candidate_subscription.address = NEW.address;
1801 END;
1802
1803- -- [tag:verify_subscription_email]: If list settings require e-mail to be verified,
1804- -- update new subscription's 'verify' column value.
1805+ -- [tag:verify_subscription_email]: If list settings require e-mail to be
1806+ -- verified, update new subscription's 'verify' column value.
1807 CREATE TRIGGER IF NOT EXISTS verify_subscription_email AFTER INSERT ON subscription
1808 FOR EACH ROW
1809 BEGIN
1810- UPDATE subscription SET verified = 0, last_modified = unixepoch()
1811- WHERE subscription.pk = NEW.pk AND EXISTS (SELECT 1 FROM list WHERE pk = NEW.list AND verify = 1);
1812+ UPDATE subscription
1813+ SET verified = 0, last_modified = unixepoch()
1814+ WHERE
1815+ subscription.pk = NEW.pk
1816+ AND
1817+ EXISTS
1818+ (SELECT 1 FROM list WHERE pk = NEW.list AND verify = 1);
1819 END;
1820
1821 -- [tag:add_account]: Update list subscription entries with 'account' foreign
1822 @@ -206,11 +364,14 @@ BEGIN
1823 END;
1824
1825 -- [tag:add_account_to_subscription]: When adding a new 'subscription', auto
1826- -- set 'account' value if there already exists an 'account' entry with the same
1827- -- address.
1828- CREATE TRIGGER IF NOT EXISTS add_account_to_subscription AFTER INSERT ON subscription
1829+ -- set 'account' value if there already exists an 'account' entry with the
1830+ -- same address.
1831+ CREATE TRIGGER IF NOT EXISTS add_account_to_subscription
1832+ AFTER INSERT ON subscription
1833 FOR EACH ROW
1834- WHEN NEW.account IS NULL AND EXISTS (SELECT 1 FROM account WHERE address = NEW.address)
1835+ WHEN
1836+ NEW.account IS NULL
1837+ AND EXISTS (SELECT 1 FROM account WHERE address = NEW.address)
1838 BEGIN
1839 UPDATE subscription
1840 SET account = (SELECT pk FROM account WHERE address = NEW.address),
1841 @@ -218,8 +379,11 @@ BEGIN
1842 WHERE subscription.pk = NEW.pk;
1843 END;
1844
1845- -- [tag:last_modified_list] update last_modified on every change.
1846- CREATE TRIGGER IF NOT EXISTS last_modified_list AFTER UPDATE ON list
1847+
1848+ -- [tag:last_modified_list]: update last_modified on every change.
1849+ CREATE TRIGGER
1850+ IF NOT EXISTS last_modified_list
1851+ AFTER UPDATE ON list
1852 FOR EACH ROW
1853 WHEN NEW.last_modified != OLD.last_modified
1854 BEGIN
1855 @@ -227,8 +391,10 @@ BEGIN
1856 WHERE pk = NEW.pk;
1857 END;
1858
1859- -- [tag:last_modified_owner] update last_modified on every change.
1860- CREATE TRIGGER IF NOT EXISTS last_modified_owner AFTER UPDATE ON owner
1861+ -- [tag:last_modified_owner]: update last_modified on every change.
1862+ CREATE TRIGGER
1863+ IF NOT EXISTS last_modified_owner
1864+ AFTER UPDATE ON owner
1865 FOR EACH ROW
1866 WHEN NEW.last_modified != OLD.last_modified
1867 BEGIN
1868 @@ -236,8 +402,10 @@ BEGIN
1869 WHERE pk = NEW.pk;
1870 END;
1871
1872- -- [tag:last_modified_post_policy] update last_modified on every change.
1873- CREATE TRIGGER IF NOT EXISTS last_modified_post_policy AFTER UPDATE ON post_policy
1874+ -- [tag:last_modified_post_policy]: update last_modified on every change.
1875+ CREATE TRIGGER
1876+ IF NOT EXISTS last_modified_post_policy
1877+ AFTER UPDATE ON post_policy
1878 FOR EACH ROW
1879 WHEN NEW.last_modified != OLD.last_modified
1880 BEGIN
1881 @@ -245,8 +413,10 @@ BEGIN
1882 WHERE pk = NEW.pk;
1883 END;
1884
1885- -- [tag:last_modified_subscription_policy] update last_modified on every change.
1886- CREATE TRIGGER IF NOT EXISTS last_modified_subscription_policy AFTER UPDATE ON subscription_policy
1887+ -- [tag:last_modified_subscription_policy]: update last_modified on every change.
1888+ CREATE TRIGGER
1889+ IF NOT EXISTS last_modified_subscription_policy
1890+ AFTER UPDATE ON subscription_policy
1891 FOR EACH ROW
1892 WHEN NEW.last_modified != OLD.last_modified
1893 BEGIN
1894 @@ -254,8 +424,10 @@ BEGIN
1895 WHERE pk = NEW.pk;
1896 END;
1897
1898- -- [tag:last_modified_subscription] update last_modified on every change.
1899- CREATE TRIGGER IF NOT EXISTS last_modified_subscription AFTER UPDATE ON subscription
1900+ -- [tag:last_modified_subscription]: update last_modified on every change.
1901+ CREATE TRIGGER
1902+ IF NOT EXISTS last_modified_subscription
1903+ AFTER UPDATE ON subscription
1904 FOR EACH ROW
1905 WHEN NEW.last_modified != OLD.last_modified
1906 BEGIN
1907 @@ -263,8 +435,10 @@ BEGIN
1908 WHERE pk = NEW.pk;
1909 END;
1910
1911- -- [tag:last_modified_account] update last_modified on every change.
1912- CREATE TRIGGER IF NOT EXISTS last_modified_account AFTER UPDATE ON account
1913+ -- [tag:last_modified_account]: update last_modified on every change.
1914+ CREATE TRIGGER
1915+ IF NOT EXISTS last_modified_account
1916+ AFTER UPDATE ON account
1917 FOR EACH ROW
1918 WHEN NEW.last_modified != OLD.last_modified
1919 BEGIN
1920 @@ -272,8 +446,10 @@ BEGIN
1921 WHERE pk = NEW.pk;
1922 END;
1923
1924- -- [tag:last_modified_candidate_subscription] update last_modified on every change.
1925- CREATE TRIGGER IF NOT EXISTS last_modified_candidate_subscription AFTER UPDATE ON candidate_subscription
1926+ -- [tag:last_modified_candidate_subscription]: update last_modified on every change.
1927+ CREATE TRIGGER
1928+ IF NOT EXISTS last_modified_candidate_subscription
1929+ AFTER UPDATE ON candidate_subscription
1930 FOR EACH ROW
1931 WHEN NEW.last_modified != OLD.last_modified
1932 BEGIN
1933 @@ -281,8 +457,10 @@ BEGIN
1934 WHERE pk = NEW.pk;
1935 END;
1936
1937- -- [tag:last_modified_templates] update last_modified on every change.
1938- CREATE TRIGGER IF NOT EXISTS last_modified_templates AFTER UPDATE ON templates
1939+ -- [tag:last_modified_templates]: update last_modified on every change.
1940+ CREATE TRIGGER
1941+ IF NOT EXISTS last_modified_templates
1942+ AFTER UPDATE ON templates
1943 FOR EACH ROW
1944 WHEN NEW.last_modified != OLD.last_modified
1945 BEGIN
1946 diff --git a/core/src/schema.sql.m4 b/core/src/schema.sql.m4
1947index 32df925..3d0fa1f 100644
1948--- a/core/src/schema.sql.m4
1949+++ b/core/src/schema.sql.m4
1950 @@ -1,34 +1,54 @@
1951- define(xor, `(($1) OR ($2)) AND NOT (($1) AND ($2))')dnl
1952- define(BOOLEAN_TYPE, `$1 BOOLEAN CHECK ($1 in (0, 1)) NOT NULL')dnl
1953+ define(xor, `dnl
1954+ (
1955+ ($1) OR ($2)
1956+ )
1957+ AND NOT
1958+ (
1959+ ($1) AND ($2)
1960+ )')dnl
1961+ dnl
1962+ dnl # Define boolean column types and defaults
1963+ define(BOOLEAN_TYPE, `BOOLEAN CHECK ($1 IN (0, 1)) NOT NULL')dnl
1964 define(BOOLEAN_FALSE, `0')dnl
1965 define(BOOLEAN_TRUE, `1')dnl
1966- define(__TAG, `tag')dnl # Write the string '['+'tag'+':'+... with a macro so that tagref check doesn't pick up on it as a duplicate.
1967+ dnl
1968+ dnl # defile comment functions
1969+ dnl
1970+ dnl # Write the string '['+'tag'+':'+... with a macro so that tagref check
1971+ dnl # doesn't pick up on it as a duplicate.
1972+ define(__TAG, `tag')dnl
1973 define(TAG, `['__TAG()`:$1]')dnl
1974- define(update_last_modified, `-- 'TAG(last_modified_$1)` update last_modified on every change.
1975- CREATE TRIGGER IF NOT EXISTS last_modified_$1 AFTER UPDATE ON $1
1976+ dnl
1977+ dnl # define triggers
1978+ define(update_last_modified, `
1979+ -- 'TAG(last_modified_$1)`: update last_modified on every change.
1980+ CREATE TRIGGER
1981+ IF NOT EXISTS last_modified_$1
1982+ AFTER UPDATE ON $1
1983 FOR EACH ROW
1984 WHEN NEW.last_modified != OLD.last_modified
1985 BEGIN
1986 UPDATE $1 SET last_modified = unixepoch()
1987 WHERE pk = NEW.pk;
1988 END;')dnl
1989+ dnl
1990 PRAGMA foreign_keys = true;
1991 PRAGMA encoding = 'UTF-8';
1992
1993 CREATE TABLE IF NOT EXISTS list (
1994- pk INTEGER PRIMARY KEY NOT NULL,
1995- name TEXT NOT NULL,
1996- id TEXT NOT NULL UNIQUE,
1997- address TEXT NOT NULL UNIQUE,
1998- owner_local_part TEXT,
1999- request_local_part TEXT,
2000- archive_url TEXT,
2001- description TEXT,
2002- created INTEGER NOT NULL DEFAULT (unixepoch()),
2003- last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
2004- BOOLEAN_TYPE(verify) DEFAULT BOOLEAN_TRUE(),
2005- BOOLEAN_TYPE(hidden) DEFAULT BOOLEAN_FALSE(),
2006- BOOLEAN_TYPE(enabled) DEFAULT BOOLEAN_TRUE()
2007+ pk INTEGER PRIMARY KEY NOT NULL,
2008+ name TEXT NOT NULL,
2009+ id TEXT NOT NULL UNIQUE,
2010+ address TEXT NOT NULL UNIQUE,
2011+ owner_local_part TEXT,
2012+ request_local_part TEXT,
2013+ archive_url TEXT,
2014+ description TEXT,
2015+ created INTEGER NOT NULL DEFAULT (unixepoch()),
2016+ last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
2017+ verify BOOLEAN_TYPE(verify) DEFAULT BOOLEAN_TRUE(),
2018+ hidden BOOLEAN_TYPE(hidden) DEFAULT BOOLEAN_FALSE(),
2019+ enabled BOOLEAN_TYPE(enabled) DEFAULT BOOLEAN_TRUE()
2020 );
2021
2022 CREATE TABLE IF NOT EXISTS owner (
2023 @@ -42,100 +62,111 @@ CREATE TABLE IF NOT EXISTS owner (
2024 );
2025
2026 CREATE TABLE IF NOT EXISTS post_policy (
2027- pk INTEGER PRIMARY KEY NOT NULL,
2028- list INTEGER NOT NULL UNIQUE,
2029- BOOLEAN_TYPE(announce_only) DEFAULT BOOLEAN_FALSE(),
2030- BOOLEAN_TYPE(subscription_only) DEFAULT BOOLEAN_FALSE(),
2031- BOOLEAN_TYPE(approval_needed) DEFAULT BOOLEAN_FALSE(),
2032- BOOLEAN_TYPE(open) DEFAULT BOOLEAN_FALSE(),
2033- BOOLEAN_TYPE(custom) DEFAULT BOOLEAN_FALSE(),
2034- created INTEGER NOT NULL DEFAULT (unixepoch()),
2035- last_modified INTEGER NOT NULL DEFAULT (unixepoch())
2036+ pk INTEGER PRIMARY KEY NOT NULL,
2037+ list INTEGER NOT NULL UNIQUE,
2038+ announce_only BOOLEAN_TYPE(announce_only)
2039+ DEFAULT BOOLEAN_FALSE(),
2040+ subscription_only BOOLEAN_TYPE(subscription_only)
2041+ DEFAULT BOOLEAN_FALSE(),
2042+ approval_needed BOOLEAN_TYPE(approval_needed)
2043+ DEFAULT BOOLEAN_FALSE(),
2044+ open BOOLEAN_TYPE(open) DEFAULT BOOLEAN_FALSE(),
2045+ custom BOOLEAN_TYPE(custom) DEFAULT BOOLEAN_FALSE(),
2046+ created INTEGER NOT NULL DEFAULT (unixepoch()),
2047+ last_modified INTEGER NOT NULL DEFAULT (unixepoch())
2048 CHECK(xor(custom, xor(open, xor(approval_needed, xor(announce_only, subscription_only))))),
2049 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE
2050 );
2051
2052 CREATE TABLE IF NOT EXISTS subscription_policy (
2053- pk INTEGER PRIMARY KEY NOT NULL,
2054- list INTEGER NOT NULL UNIQUE,
2055- BOOLEAN_TYPE(send_confirmation) DEFAULT BOOLEAN_TRUE(),
2056- BOOLEAN_TYPE(open) DEFAULT BOOLEAN_FALSE(),
2057- BOOLEAN_TYPE(manual) DEFAULT BOOLEAN_FALSE(),
2058- BOOLEAN_TYPE(request) DEFAULT BOOLEAN_FALSE(),
2059- BOOLEAN_TYPE(custom) DEFAULT BOOLEAN_FALSE(),
2060- created INTEGER NOT NULL DEFAULT (unixepoch()),
2061- last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
2062+ pk INTEGER PRIMARY KEY NOT NULL,
2063+ list INTEGER NOT NULL UNIQUE,
2064+ send_confirmation BOOLEAN_TYPE(send_confirmation)
2065+ DEFAULT BOOLEAN_TRUE(),
2066+ open BOOLEAN_TYPE(open) DEFAULT BOOLEAN_FALSE(),
2067+ manual BOOLEAN_TYPE(manual) DEFAULT BOOLEAN_FALSE(),
2068+ request BOOLEAN_TYPE(request) DEFAULT BOOLEAN_FALSE(),
2069+ custom BOOLEAN_TYPE(custom) DEFAULT BOOLEAN_FALSE(),
2070+ created INTEGER NOT NULL DEFAULT (unixepoch()),
2071+ last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
2072 CHECK(xor(open, xor(manual, xor(request, custom)))),
2073 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE
2074 );
2075
2076 CREATE TABLE IF NOT EXISTS subscription (
2077- pk INTEGER PRIMARY KEY NOT NULL,
2078- list INTEGER NOT NULL,
2079- address TEXT NOT NULL,
2080- name TEXT,
2081- account INTEGER,
2082- BOOLEAN_TYPE(enabled) DEFAULT BOOLEAN_TRUE(),
2083- BOOLEAN_TYPE(verified) DEFAULT BOOLEAN_TRUE(),
2084- BOOLEAN_TYPE(digest) DEFAULT BOOLEAN_FALSE(),
2085- BOOLEAN_TYPE(hide_address) DEFAULT BOOLEAN_FALSE(),
2086- BOOLEAN_TYPE(receive_duplicates) DEFAULT BOOLEAN_TRUE(),
2087- BOOLEAN_TYPE(receive_own_posts) DEFAULT BOOLEAN_FALSE(),
2088- BOOLEAN_TYPE(receive_confirmation) DEFAULT BOOLEAN_TRUE(),
2089- last_digest INTEGER NOT NULL DEFAULT (unixepoch()),
2090- created INTEGER NOT NULL DEFAULT (unixepoch()),
2091- last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
2092+ pk INTEGER PRIMARY KEY NOT NULL,
2093+ list INTEGER NOT NULL,
2094+ address TEXT NOT NULL,
2095+ name TEXT,
2096+ account INTEGER,
2097+ enabled BOOLEAN_TYPE(enabled)
2098+ DEFAULT BOOLEAN_TRUE(),
2099+ verified BOOLEAN_TYPE(verified)
2100+ DEFAULT BOOLEAN_TRUE(),
2101+ digest BOOLEAN_TYPE(digest)
2102+ DEFAULT BOOLEAN_FALSE(),
2103+ hide_address BOOLEAN_TYPE(hide_address)
2104+ DEFAULT BOOLEAN_FALSE(),
2105+ receive_duplicates BOOLEAN_TYPE(receive_duplicates)
2106+ DEFAULT BOOLEAN_TRUE(),
2107+ receive_own_posts BOOLEAN_TYPE(receive_own_posts)
2108+ DEFAULT BOOLEAN_FALSE(),
2109+ receive_confirmation BOOLEAN_TYPE(receive_confirmation)
2110+ DEFAULT BOOLEAN_TRUE(),
2111+ last_digest INTEGER NOT NULL DEFAULT (unixepoch()),
2112+ created INTEGER NOT NULL DEFAULT (unixepoch()),
2113+ last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
2114 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE,
2115 FOREIGN KEY (account) REFERENCES account(pk) ON DELETE SET NULL,
2116 UNIQUE (list, address) ON CONFLICT ROLLBACK
2117 );
2118
2119 CREATE TABLE IF NOT EXISTS account (
2120- pk INTEGER PRIMARY KEY NOT NULL,
2121- name TEXT,
2122- address TEXT NOT NULL UNIQUE,
2123- public_key TEXT,
2124- password TEXT NOT NULL,
2125- BOOLEAN_TYPE(enabled) DEFAULT BOOLEAN_TRUE(),
2126- created INTEGER NOT NULL DEFAULT (unixepoch()),
2127- last_modified INTEGER NOT NULL DEFAULT (unixepoch())
2128+ pk INTEGER PRIMARY KEY NOT NULL,
2129+ name TEXT,
2130+ address TEXT NOT NULL UNIQUE,
2131+ public_key TEXT,
2132+ password TEXT NOT NULL,
2133+ enabled BOOLEAN_TYPE(enabled) DEFAULT BOOLEAN_TRUE(),
2134+ created INTEGER NOT NULL DEFAULT (unixepoch()),
2135+ last_modified INTEGER NOT NULL DEFAULT (unixepoch())
2136 );
2137
2138 CREATE TABLE IF NOT EXISTS candidate_subscription (
2139- pk INTEGER PRIMARY KEY NOT NULL,
2140- list INTEGER NOT NULL,
2141- address TEXT NOT NULL,
2142- name TEXT,
2143- accepted INTEGER UNIQUE,
2144- created INTEGER NOT NULL DEFAULT (unixepoch()),
2145- last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
2146+ pk INTEGER PRIMARY KEY NOT NULL,
2147+ list INTEGER NOT NULL,
2148+ address TEXT NOT NULL,
2149+ name TEXT,
2150+ accepted INTEGER UNIQUE,
2151+ created INTEGER NOT NULL DEFAULT (unixepoch()),
2152+ last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
2153 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE,
2154 FOREIGN KEY (accepted) REFERENCES subscription(pk) ON DELETE CASCADE,
2155 UNIQUE (list, address) ON CONFLICT ROLLBACK
2156 );
2157
2158 CREATE TABLE IF NOT EXISTS post (
2159- pk INTEGER PRIMARY KEY NOT NULL,
2160- list INTEGER NOT NULL,
2161- envelope_from TEXT,
2162- address TEXT NOT NULL,
2163- message_id TEXT NOT NULL,
2164- message BLOB NOT NULL,
2165- headers_json TEXT,
2166- timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
2167- datetime TEXT NOT NULL DEFAULT (datetime()),
2168- created INTEGER NOT NULL DEFAULT (unixepoch())
2169+ pk INTEGER PRIMARY KEY NOT NULL,
2170+ list INTEGER NOT NULL,
2171+ envelope_from TEXT,
2172+ address TEXT NOT NULL,
2173+ message_id TEXT NOT NULL,
2174+ message BLOB NOT NULL,
2175+ headers_json TEXT,
2176+ timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
2177+ datetime TEXT NOT NULL DEFAULT (datetime()),
2178+ created INTEGER NOT NULL DEFAULT (unixepoch())
2179 );
2180
2181 CREATE TABLE IF NOT EXISTS templates (
2182- pk INTEGER PRIMARY KEY NOT NULL,
2183- name TEXT NOT NULL,
2184- list INTEGER,
2185- subject TEXT,
2186- headers_json TEXT,
2187- body TEXT NOT NULL,
2188- created INTEGER NOT NULL DEFAULT (unixepoch()),
2189- last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
2190+ pk INTEGER PRIMARY KEY NOT NULL,
2191+ name TEXT NOT NULL,
2192+ list INTEGER,
2193+ subject TEXT,
2194+ headers_json TEXT,
2195+ body TEXT NOT NULL,
2196+ created INTEGER NOT NULL DEFAULT (unixepoch()),
2197+ last_modified INTEGER NOT NULL DEFAULT (unixepoch()),
2198 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE,
2199 UNIQUE (list, name) ON CONFLICT ROLLBACK
2200 );
2201 @@ -144,47 +175,56 @@ CREATE TABLE IF NOT EXISTS templates (
2202 --
2203 -- ## The "maildrop" queue
2204 --
2205- -- Messages that have been submitted but not yet processed, await processing in
2206- -- the "maildrop" queue. Messages can be added to the "maildrop" queue even when
2207- -- mailpot is not running.
2208+ -- Messages that have been submitted but not yet processed, await processing
2209+ -- in the "maildrop" queue. Messages can be added to the "maildrop" queue
2210+ -- even when mailpot is not running.
2211 --
2212 -- ## The "deferred" queue
2213 --
2214- -- When all the deliverable recipients for a message are delivered, and for some
2215- -- recipients delivery failed for a transient reason (it might succeed later), the
2216- -- message is placed in the "deferred" queue.
2217+ -- When all the deliverable recipients for a message are delivered, and for
2218+ -- some recipients delivery failed for a transient reason (it might succeed
2219+ -- later), the message is placed in the "deferred" queue.
2220 --
2221 -- ## The "hold" queue
2222 --
2223- -- List administrators may introduce rules for emails to be placed indefinitely in
2224- -- the "hold" queue. Messages placed in the "hold" queue stay there until the
2225- -- administrator intervenes. No periodic delivery attempts are made for messages
2226- -- in the "hold" queue.
2227+ -- List administrators may introduce rules for emails to be placed
2228+ -- indefinitely in the "hold" queue. Messages placed in the "hold" queue stay
2229+ -- there until the administrator intervenes. No periodic delivery attempts
2230+ -- are made for messages in the "hold" queue.
2231
2232 -- ## The "out" queue
2233 --
2234 -- Emails that must be sent as soon as possible.
2235 CREATE TABLE IF NOT EXISTS queue (
2236- pk INTEGER PRIMARY KEY NOT NULL,
2237- which TEXT CHECK (which IN ('maildrop', 'hold', 'deferred', 'corrupt', 'error', 'out')) NOT NULL,
2238- list INTEGER,
2239- comment TEXT,
2240- to_addresses TEXT NOT NULL,
2241- from_address TEXT NOT NULL,
2242- subject TEXT NOT NULL,
2243- message_id TEXT NOT NULL,
2244- message BLOB NOT NULL,
2245- timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
2246- datetime TEXT NOT NULL DEFAULT (datetime()),
2247+ pk INTEGER PRIMARY KEY NOT NULL,
2248+ which TEXT
2249+ CHECK (
2250+ which IN
2251+ ('maildrop',
2252+ 'hold',
2253+ 'deferred',
2254+ 'corrupt',
2255+ 'error',
2256+ 'out')
2257+ ) NOT NULL,
2258+ list INTEGER,
2259+ comment TEXT,
2260+ to_addresses TEXT NOT NULL,
2261+ from_address TEXT NOT NULL,
2262+ subject TEXT NOT NULL,
2263+ message_id TEXT NOT NULL,
2264+ message BLOB NOT NULL,
2265+ timestamp INTEGER NOT NULL DEFAULT (unixepoch()),
2266+ datetime TEXT NOT NULL DEFAULT (datetime()),
2267 FOREIGN KEY (list) REFERENCES list(pk) ON DELETE CASCADE,
2268 UNIQUE (to_addresses, message_id) ON CONFLICT ROLLBACK
2269 );
2270
2271 CREATE TABLE IF NOT EXISTS bounce (
2272- pk INTEGER PRIMARY KEY NOT NULL,
2273- subscription INTEGER NOT NULL UNIQUE,
2274- count INTEGER NOT NULL DEFAULT 0,
2275- last_bounce TEXT NOT NULL DEFAULT (datetime()),
2276+ pk INTEGER PRIMARY KEY NOT NULL,
2277+ subscription INTEGER NOT NULL UNIQUE,
2278+ count INTEGER NOT NULL DEFAULT 0,
2279+ last_bounce TEXT NOT NULL DEFAULT (datetime()),
2280 FOREIGN KEY (subscription) REFERENCES subscription(pk) ON DELETE CASCADE
2281 );
2282
2283 @@ -193,7 +233,8 @@ CREATE INDEX IF NOT EXISTS post_msgid_idx ON post(message_id);
2284 CREATE INDEX IF NOT EXISTS list_idx ON list(id);
2285 CREATE INDEX IF NOT EXISTS subscription_idx ON subscription(address);
2286
2287- -- TAG(accept_candidate): Update candidacy with 'subscription' foreign key on 'subscription' insert.
2288+ -- TAG(accept_candidate): Update candidacy with 'subscription' foreign key on
2289+ -- 'subscription' insert.
2290 CREATE TRIGGER IF NOT EXISTS accept_candidate AFTER INSERT ON subscription
2291 FOR EACH ROW
2292 BEGIN
2293 @@ -201,13 +242,18 @@ BEGIN
2294 WHERE candidate_subscription.list = NEW.list AND candidate_subscription.address = NEW.address;
2295 END;
2296
2297- -- TAG(verify_subscription_email): If list settings require e-mail to be verified,
2298- -- update new subscription's 'verify' column value.
2299+ -- TAG(verify_subscription_email): If list settings require e-mail to be
2300+ -- verified, update new subscription's 'verify' column value.
2301 CREATE TRIGGER IF NOT EXISTS verify_subscription_email AFTER INSERT ON subscription
2302 FOR EACH ROW
2303 BEGIN
2304- UPDATE subscription SET verified = BOOLEAN_FALSE(), last_modified = unixepoch()
2305- WHERE subscription.pk = NEW.pk AND EXISTS (SELECT 1 FROM list WHERE pk = NEW.list AND verify = BOOLEAN_TRUE());
2306+ UPDATE subscription
2307+ SET verified = BOOLEAN_FALSE(), last_modified = unixepoch()
2308+ WHERE
2309+ subscription.pk = NEW.pk
2310+ AND
2311+ EXISTS
2312+ (SELECT 1 FROM list WHERE pk = NEW.list AND verify = BOOLEAN_TRUE());
2313 END;
2314
2315 -- TAG(add_account): Update list subscription entries with 'account' foreign
2316 @@ -220,11 +266,14 @@ BEGIN
2317 END;
2318
2319 -- TAG(add_account_to_subscription): When adding a new 'subscription', auto
2320- -- set 'account' value if there already exists an 'account' entry with the same
2321- -- address.
2322- CREATE TRIGGER IF NOT EXISTS add_account_to_subscription AFTER INSERT ON subscription
2323+ -- set 'account' value if there already exists an 'account' entry with the
2324+ -- same address.
2325+ CREATE TRIGGER IF NOT EXISTS add_account_to_subscription
2326+ AFTER INSERT ON subscription
2327 FOR EACH ROW
2328- WHEN NEW.account IS NULL AND EXISTS (SELECT 1 FROM account WHERE address = NEW.address)
2329+ WHEN
2330+ NEW.account IS NULL
2331+ AND EXISTS (SELECT 1 FROM account WHERE address = NEW.address)
2332 BEGIN
2333 UPDATE subscription
2334 SET account = (SELECT pk FROM account WHERE address = NEW.address),
2335 @@ -233,17 +282,10 @@ BEGIN
2336 END;
2337
2338 update_last_modified(`list')
2339-
2340 update_last_modified(`owner')
2341-
2342 update_last_modified(`post_policy')
2343-
2344 update_last_modified(`subscription_policy')
2345-
2346 update_last_modified(`subscription')
2347-
2348 update_last_modified(`account')
2349-
2350 update_last_modified(`candidate_subscription')
2351-
2352 update_last_modified(`templates')
2353 diff --git a/core/tests/smtp.rs b/core/tests/smtp.rs
2354index 31527b1..b8c6469 100644
2355--- a/core/tests/smtp.rs
2356+++ b/core/tests/smtp.rs
2357 @@ -18,7 +18,7 @@
2358 */
2359
2360 use log::{trace, warn};
2361- use mailpot::{melib, Configuration, Connection, Queue, SendMail};
2362+ use mailpot::{melib, models::*, Configuration, Connection, Queue, SendMail};
2363 use mailpot_tests::*;
2364 use melib::smol;
2365 use tempfile::TempDir;
2366 diff --git a/mailpot-tests/src/lib.rs b/mailpot-tests/src/lib.rs
2367index 20307a2..dccb32b 100644
2368--- a/mailpot-tests/src/lib.rs
2369+++ b/mailpot-tests/src/lib.rs
2370 @@ -33,11 +33,6 @@ use mailin_embedded::{
2371 response::{INTERNAL_ERROR, OK},
2372 Handler, Response, Server,
2373 };
2374- pub use mailpot::{
2375- melib::{self, smol, smtp::SmtpServerConf},
2376- models::{changesets::ListSubscriptionChangeset, *},
2377- Configuration, Connection, Queue, SendMail,
2378- };
2379 pub use predicates;
2380 pub use tempfile::{self, TempDir};
2381
2382 @@ -84,9 +79,9 @@ pub struct TestSmtpHandler {
2383 address: Cow<'static, str>,
2384 ssl: SslConfig,
2385 envelope_from: Cow<'static, str>,
2386- auth: melib::smtp::SmtpAuth,
2387+ auth: mailpot::melib::smtp::SmtpAuth,
2388 pub messages: Arc<Mutex<Vec<((IpAddr, String), Message)>>>,
2389- pub stored: Arc<Mutex<Vec<(String, melib::Envelope)>>>,
2390+ pub stored: Arc<Mutex<Vec<(String, mailpot::melib::Envelope)>>>,
2391 }
2392
2393 impl Handler for TestSmtpHandler {
2394 @@ -181,7 +176,7 @@ impl Handler for TestSmtpHandler {
2395 let last = self.messages.lock().unwrap().pop();
2396 if let Some(((ip, domain), Message::Data { from: _, to, buf })) = last {
2397 for to in to {
2398- match melib::Envelope::from_bytes(&buf, None) {
2399+ match mailpot::melib::Envelope::from_bytes(&buf, None) {
2400 Ok(env) => {
2401 self.stored.lock().unwrap().push((to.clone(), env));
2402 }
2403 @@ -202,8 +197,8 @@ impl Handler for TestSmtpHandler {
2404
2405 impl TestSmtpHandler {
2406 #[inline]
2407- pub fn smtp_conf(&self) -> melib::smtp::SmtpServerConf {
2408- use melib::smtp::*;
2409+ pub fn smtp_conf(&self) -> mailpot::melib::smtp::SmtpServerConf {
2410+ use mailpot::melib::smtp::*;
2411 let sockaddr = self
2412 .address
2413 .as_ref()
2414 @@ -234,7 +229,7 @@ impl TestSmtpHandler {
2415 pub struct TestSmtpHandlerBuilder {
2416 address: Cow<'static, str>,
2417 ssl: SslConfig,
2418- auth: melib::smtp::SmtpAuth,
2419+ auth: mailpot::melib::smtp::SmtpAuth,
2420 envelope_from: Cow<'static, str>,
2421 }
2422
2423 @@ -243,7 +238,7 @@ impl TestSmtpHandlerBuilder {
2424 Self {
2425 address: ADDRESS.into(),
2426 ssl: SslConfig::None,
2427- auth: melib::smtp::SmtpAuth::None,
2428+ auth: mailpot::melib::smtp::SmtpAuth::None,
2429 envelope_from: "foo-chat@example.com".into(),
2430 }
2431 }
2432 diff --git a/rest-http/src/lib.rs b/rest-http/src/lib.rs
2433new file mode 100644
2434index 0000000..48813d6
2435--- /dev/null
2436+++ b/rest-http/src/lib.rs
2437 @@ -0,0 +1,18 @@
2438+ /*
2439+ * This file is part of mailpot
2440+ *
2441+ * Copyright 2020 - Manos Pitsidianakis
2442+ *
2443+ * This program is free software: you can redistribute it and/or modify
2444+ * it under the terms of the GNU Affero General Public License as
2445+ * published by the Free Software Foundation, either version 3 of the
2446+ * License, or (at your option) any later version.
2447+ *
2448+ * This program is distributed in the hope that it will be useful,
2449+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2450+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2451+ * GNU Affero General Public License for more details.
2452+ *
2453+ * You should have received a copy of the GNU Affero General Public License
2454+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
2455+ */
2456 diff --git a/rest-http/src/main.rs b/rest-http/src/main.rs
2457index 8de3a08..85a1065 100644
2458--- a/rest-http/src/main.rs
2459+++ b/rest-http/src/main.rs
2460 @@ -17,7 +17,7 @@
2461 * along with this program. If not, see <https://www.gnu.org/licenses/>.
2462 */
2463
2464- pub use mailpot::{models::*, *};
2465+ use mailpot::*;
2466 use warp::Filter;
2467
2468 #[tokio::main]
2469 diff --git a/web/src/lib.rs b/web/src/lib.rs
2470index dfc7f66..6b17d81 100644
2471--- a/web/src/lib.rs
2472+++ b/web/src/lib.rs
2473 @@ -74,7 +74,7 @@ use std::{borrow::Cow, collections::HashMap, sync::Arc};
2474
2475 use chrono::Datelike;
2476 pub use http::{Request, Response, StatusCode};
2477- pub use mailpot::{models::DbVal, rusqlite::OptionalExtension, *};
2478+ use mailpot::{models::DbVal, rusqlite::OptionalExtension, *};
2479 use minijinja::{
2480 value::{Object, Value},
2481 Environment, Error,
2482 diff --git a/web/src/main.rs b/web/src/main.rs
2483index c694c81..52b7a00 100644
2484--- a/web/src/main.rs
2485+++ b/web/src/main.rs
2486 @@ -19,6 +19,7 @@
2487
2488 use std::{collections::HashMap, sync::Arc};
2489
2490+ use mailpot::{Configuration, Connection};
2491 use mailpot_web::*;
2492 use minijinja::value::Value;
2493 use rand::Rng;