core: reorganise old module hierarchy
parent
fedb766942
commit
9eaa580af4
|
@ -55,8 +55,12 @@ pub enum Command {
|
|||
/// Prints a sample config file to STDOUT.
|
||||
///
|
||||
/// You can generate a new configuration file by writing the output to a
|
||||
/// file, e.g: mpot sample-config > config.toml
|
||||
SampleConfig,
|
||||
/// file, e.g: mpot sample-config --with-smtp > config.toml
|
||||
SampleConfig {
|
||||
/// Use an SMTP connection instead of a shell process.
|
||||
#[arg(long)]
|
||||
with_smtp: bool,
|
||||
},
|
||||
/// Dumps database data to STDOUT.
|
||||
DumpDatabase,
|
||||
/// Lists all registered mailing lists.
|
||||
|
|
|
@ -27,9 +27,10 @@ use std::{
|
|||
mod lints;
|
||||
use lints::*;
|
||||
use mailpot::{
|
||||
melib::{backends::maildir::MaildirPathTrait, smol, Envelope, EnvelopeHash},
|
||||
melib::{backends::maildir::MaildirPathTrait, smol, smtp::*, Envelope, EnvelopeHash},
|
||||
models::{changesets::*, *},
|
||||
*,
|
||||
queue::{Queue, QueueEntry},
|
||||
Configuration, Connection, Error, ErrorKind, Result, *,
|
||||
};
|
||||
use mailpot_cli::*;
|
||||
|
||||
|
@ -60,8 +61,27 @@ fn run_app(opt: Opt) -> Result<()> {
|
|||
if opt.debug {
|
||||
println!("DEBUG: {:?}", &opt);
|
||||
}
|
||||
if let Command::SampleConfig = opt.cmd {
|
||||
println!("{}", Configuration::new("/path/to/sqlite.db").to_toml());
|
||||
if let Command::SampleConfig { with_smtp } = opt.cmd {
|
||||
let mut new = Configuration::new("/path/to/sqlite.db");
|
||||
new.administrators.push("admin@example.com".to_string());
|
||||
if with_smtp {
|
||||
new.send_mail = mailpot::SendMail::Smtp(SmtpServerConf {
|
||||
hostname: "mail.example.com".to_string(),
|
||||
port: 587,
|
||||
envelope_from: "".to_string(),
|
||||
auth: SmtpAuth::Auto {
|
||||
username: "user".to_string(),
|
||||
password: Password::Raw("hunter2".to_string()),
|
||||
auth_type: SmtpAuthType::default(),
|
||||
require_auth: true,
|
||||
},
|
||||
security: SmtpSecurity::StartTLS {
|
||||
danger_accept_invalid_certs: false,
|
||||
},
|
||||
extensions: Default::default(),
|
||||
});
|
||||
}
|
||||
println!("{}", new.to_toml());
|
||||
return Ok(());
|
||||
};
|
||||
let config_path = if let Some(path) = opt.config.as_ref() {
|
||||
|
@ -80,7 +100,7 @@ fn run_app(opt: Opt) -> Result<()> {
|
|||
use Command::*;
|
||||
let mut db = Connection::open_or_create_db(config)?.trusted();
|
||||
match opt.cmd {
|
||||
SampleConfig => {}
|
||||
SampleConfig { .. } => {}
|
||||
DumpDatabase => {
|
||||
let lists = db.lists()?;
|
||||
let mut stdout = std::io::stdout();
|
||||
|
|
|
@ -23,7 +23,8 @@ use assert_cmd::assert::OutputAssertExt;
|
|||
use mailpot::{
|
||||
melib,
|
||||
models::{changesets::ListSubscriptionChangeset, *},
|
||||
Configuration, Connection, Queue, SendMail,
|
||||
queue::Queue,
|
||||
Configuration, Connection, SendMail,
|
||||
};
|
||||
use mailpot_tests::*;
|
||||
use predicates::prelude::*;
|
||||
|
|
|
@ -24,18 +24,20 @@ use std::{
|
|||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use melib::Envelope;
|
||||
use models::changesets::*;
|
||||
use log::{info, trace};
|
||||
use rusqlite::{Connection as DbConnection, OptionalExtension};
|
||||
|
||||
use super::{Configuration, *};
|
||||
use crate::ErrorKind::*;
|
||||
use crate::{
|
||||
config::Configuration,
|
||||
errors::{ErrorKind::*, *},
|
||||
models::{changesets::MailingListChangeset, DbVal, ListOwner, MailingList, Post},
|
||||
};
|
||||
|
||||
/// A connection to a `mailpot` database.
|
||||
pub struct Connection {
|
||||
/// The `rusqlite` connection handle.
|
||||
pub connection: DbConnection,
|
||||
conf: Configuration,
|
||||
pub(crate) conf: Configuration,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Connection {
|
||||
|
@ -61,17 +63,6 @@ impl Drop for Connection {
|
|||
}
|
||||
}
|
||||
|
||||
mod templates;
|
||||
pub use templates::*;
|
||||
mod queue;
|
||||
pub use queue::*;
|
||||
mod posts;
|
||||
pub use posts::*;
|
||||
mod subscriptions;
|
||||
pub use subscriptions::*;
|
||||
mod policies;
|
||||
pub use policies::*;
|
||||
|
||||
fn log_callback(error_code: std::ffi::c_int, message: &str) {
|
||||
match error_code {
|
||||
rusqlite::ffi::SQLITE_OK
|
||||
|
@ -569,18 +560,4 @@ impl Connection {
|
|||
tx.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return the post filters of a mailing list.
|
||||
pub fn list_filters(
|
||||
&self,
|
||||
_list: &DbVal<MailingList>,
|
||||
) -> Vec<Box<dyn crate::mail::message_filters::PostFilter>> {
|
||||
use crate::mail::message_filters::*;
|
||||
vec![
|
||||
Box::new(FixCRLF),
|
||||
Box::new(PostRightsCheck),
|
||||
Box::new(AddListHeaders),
|
||||
Box::new(FinalizeRecipients),
|
||||
]
|
||||
}
|
||||
}
|
|
@ -1,178 +0,0 @@
|
|||
/*
|
||||
* This file is part of mailpot
|
||||
*
|
||||
* Copyright 2020 - Manos Pitsidianakis
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Named templates, for generated e-mail like confirmations, alerts etc.
|
||||
|
||||
use super::*;
|
||||
|
||||
impl Connection {
|
||||
/// Fetch all.
|
||||
pub fn fetch_templates(&self) -> Result<Vec<DbVal<Template>>> {
|
||||
let mut stmt = self
|
||||
.connection
|
||||
.prepare("SELECT * FROM templates ORDER BY pk;")?;
|
||||
let iter = stmt.query_map(rusqlite::params![], |row| {
|
||||
let pk = row.get("pk")?;
|
||||
Ok(DbVal(
|
||||
Template {
|
||||
pk,
|
||||
name: row.get("name")?,
|
||||
list: row.get("list")?,
|
||||
subject: row.get("subject")?,
|
||||
headers_json: row.get("headers_json")?,
|
||||
body: row.get("body")?,
|
||||
},
|
||||
pk,
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut ret = vec![];
|
||||
for templ in iter {
|
||||
let templ = templ?;
|
||||
ret.push(templ);
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Fetch a named template.
|
||||
pub fn fetch_template(
|
||||
&self,
|
||||
template: &str,
|
||||
list_pk: Option<i64>,
|
||||
) -> Result<Option<DbVal<Template>>> {
|
||||
let mut stmt = self
|
||||
.connection
|
||||
.prepare("SELECT * FROM templates WHERE name = ? AND list IS ?;")?;
|
||||
let ret = stmt
|
||||
.query_row(rusqlite::params![&template, &list_pk], |row| {
|
||||
let pk = row.get("pk")?;
|
||||
Ok(DbVal(
|
||||
Template {
|
||||
pk,
|
||||
name: row.get("name")?,
|
||||
list: row.get("list")?,
|
||||
subject: row.get("subject")?,
|
||||
headers_json: row.get("headers_json")?,
|
||||
body: row.get("body")?,
|
||||
},
|
||||
pk,
|
||||
))
|
||||
})
|
||||
.optional()?;
|
||||
if ret.is_none() && list_pk.is_some() {
|
||||
let mut stmt = self
|
||||
.connection
|
||||
.prepare("SELECT * FROM templates WHERE name = ? AND list IS NULL;")?;
|
||||
Ok(stmt
|
||||
.query_row(rusqlite::params![&template], |row| {
|
||||
let pk = row.get("pk")?;
|
||||
Ok(DbVal(
|
||||
Template {
|
||||
pk,
|
||||
name: row.get("name")?,
|
||||
list: row.get("list")?,
|
||||
subject: row.get("subject")?,
|
||||
headers_json: row.get("headers_json")?,
|
||||
body: row.get("body")?,
|
||||
},
|
||||
pk,
|
||||
))
|
||||
})
|
||||
.optional()?)
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a named template.
|
||||
pub fn add_template(&self, template: Template) -> Result<DbVal<Template>> {
|
||||
let mut stmt = self.connection.prepare(
|
||||
"INSERT INTO templates(name, list, subject, headers_json, body) VALUES(?, ?, ?, ?, ?) \
|
||||
RETURNING *;",
|
||||
)?;
|
||||
let ret = stmt
|
||||
.query_row(
|
||||
rusqlite::params![
|
||||
&template.name,
|
||||
&template.list,
|
||||
&template.subject,
|
||||
&template.headers_json,
|
||||
&template.body
|
||||
],
|
||||
|row| {
|
||||
let pk = row.get("pk")?;
|
||||
Ok(DbVal(
|
||||
Template {
|
||||
pk,
|
||||
name: row.get("name")?,
|
||||
list: row.get("list")?,
|
||||
subject: row.get("subject")?,
|
||||
headers_json: row.get("headers_json")?,
|
||||
body: row.get("body")?,
|
||||
},
|
||||
pk,
|
||||
))
|
||||
},
|
||||
)
|
||||
.map_err(|err| {
|
||||
if matches!(
|
||||
err,
|
||||
rusqlite::Error::SqliteFailure(
|
||||
rusqlite::ffi::Error {
|
||||
code: rusqlite::ffi::ErrorCode::ConstraintViolation,
|
||||
extended_code: 787
|
||||
},
|
||||
_
|
||||
)
|
||||
) {
|
||||
Error::from(err).chain_err(|| NotFound("Could not find a list with this pk."))
|
||||
} else {
|
||||
err.into()
|
||||
}
|
||||
})?;
|
||||
|
||||
trace!("add_template {:?}.", &ret);
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Remove a named template.
|
||||
pub fn remove_template(&self, template: &str, list_pk: Option<i64>) -> Result<Template> {
|
||||
let mut stmt = self
|
||||
.connection
|
||||
.prepare("DELETE FROM templates WHERE name = ? AND list IS ? RETURNING *;")?;
|
||||
let ret = stmt.query_row(rusqlite::params![&template, &list_pk], |row| {
|
||||
Ok(Template {
|
||||
pk: -1,
|
||||
name: row.get("name")?,
|
||||
list: row.get("list")?,
|
||||
subject: row.get("subject")?,
|
||||
headers_json: row.get("headers_json")?,
|
||||
body: row.get("body")?,
|
||||
})
|
||||
})?;
|
||||
|
||||
trace!(
|
||||
"remove_template {} list_pk {:?} {:?}.",
|
||||
template,
|
||||
&list_pk,
|
||||
&ret
|
||||
);
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
|
@ -165,20 +165,23 @@ pub extern crate log;
|
|||
pub extern crate melib;
|
||||
pub extern crate serde_json;
|
||||
|
||||
use log::{info, trace};
|
||||
|
||||
mod config;
|
||||
mod db;
|
||||
mod connection;
|
||||
mod errors;
|
||||
pub mod mail;
|
||||
pub mod message_filters;
|
||||
pub mod models;
|
||||
pub mod policies;
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
pub mod postfix;
|
||||
pub mod posts;
|
||||
pub mod queue;
|
||||
pub mod submission;
|
||||
pub mod subscriptions;
|
||||
mod templates;
|
||||
|
||||
pub use config::{Configuration, SendMail};
|
||||
pub use db::*;
|
||||
pub use connection::*;
|
||||
pub use errors::*;
|
||||
use models::*;
|
||||
pub use templates::*;
|
||||
|
|
|
@ -20,11 +20,13 @@
|
|||
//! Types for processing new posts: [`PostFilter`](message_filters::PostFilter),
|
||||
//! [`ListContext`], [`MailJob`] and [`PostAction`].
|
||||
|
||||
use log::trace;
|
||||
use melib::Address;
|
||||
|
||||
use super::*;
|
||||
pub mod message_filters;
|
||||
|
||||
use crate::{
|
||||
models::{ListOwner, ListSubscription, MailingList, PostPolicy, SubscriptionPolicy},
|
||||
DbVal,
|
||||
};
|
||||
/// Post action returned from a list's
|
||||
/// [`PostFilter`](message_filters::PostFilter) stack.
|
||||
#[derive(Debug)]
|
||||
|
@ -66,7 +68,7 @@ pub struct ListContext<'list> {
|
|||
|
||||
/// Post to be considered by the list's
|
||||
/// [`PostFilter`](message_filters::PostFilter) stack.
|
||||
pub struct Post {
|
||||
pub struct PostEntry {
|
||||
/// `From` address of post.
|
||||
pub from: Address,
|
||||
/// Raw bytes of post.
|
||||
|
@ -78,9 +80,9 @@ pub struct Post {
|
|||
pub action: PostAction,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Post {
|
||||
impl core::fmt::Debug for PostEntry {
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
fmt.debug_struct("Post")
|
||||
fmt.debug_struct(stringify!(PostEntry))
|
||||
.field("from", &self.from)
|
||||
.field("bytes", &format_args!("{} bytes", self.bytes.len()))
|
||||
.field("to", &self.to.as_slice())
|
||||
|
|
|
@ -38,7 +38,26 @@
|
|||
//!
|
||||
//! so the processing stops at the first returned error.
|
||||
|
||||
use super::*;
|
||||
use log::trace;
|
||||
use melib::Address;
|
||||
|
||||
use crate::{
|
||||
mail::{ListContext, MailJob, PostAction, PostEntry},
|
||||
models::{DbVal, MailingList},
|
||||
Connection,
|
||||
};
|
||||
|
||||
impl Connection {
|
||||
/// Return the post filters of a mailing list.
|
||||
pub fn list_filters(&self, _list: &DbVal<MailingList>) -> Vec<Box<dyn PostFilter>> {
|
||||
vec![
|
||||
Box::new(FixCRLF),
|
||||
Box::new(PostRightsCheck),
|
||||
Box::new(AddListHeaders),
|
||||
Box::new(FinalizeRecipients),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter that modifies and/or verifies a post candidate. On rejection, return
|
||||
/// a string describing the error and optionally set `post.action` to `Reject`
|
||||
|
@ -49,9 +68,9 @@ pub trait PostFilter {
|
|||
/// processing to stop and return an `Result::Err`.
|
||||
fn feed<'p, 'list>(
|
||||
self: Box<Self>,
|
||||
post: &'p mut Post,
|
||||
post: &'p mut PostEntry,
|
||||
ctx: &'p mut ListContext<'list>,
|
||||
) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()>;
|
||||
) -> std::result::Result<(&'p mut PostEntry, &'p mut ListContext<'list>), ()>;
|
||||
}
|
||||
|
||||
/// Check that submitter can post to list, for now it accepts everything.
|
||||
|
@ -59,9 +78,9 @@ pub struct PostRightsCheck;
|
|||
impl PostFilter for PostRightsCheck {
|
||||
fn feed<'p, 'list>(
|
||||
self: Box<Self>,
|
||||
post: &'p mut Post,
|
||||
post: &'p mut PostEntry,
|
||||
ctx: &'p mut ListContext<'list>,
|
||||
) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()> {
|
||||
) -> std::result::Result<(&'p mut PostEntry, &'p mut ListContext<'list>), ()> {
|
||||
trace!("Running PostRightsCheck filter");
|
||||
if let Some(ref policy) = ctx.post_policy {
|
||||
if policy.announce_only {
|
||||
|
@ -117,9 +136,9 @@ pub struct FixCRLF;
|
|||
impl PostFilter for FixCRLF {
|
||||
fn feed<'p, 'list>(
|
||||
self: Box<Self>,
|
||||
post: &'p mut Post,
|
||||
post: &'p mut PostEntry,
|
||||
ctx: &'p mut ListContext<'list>,
|
||||
) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()> {
|
||||
) -> std::result::Result<(&'p mut PostEntry, &'p mut ListContext<'list>), ()> {
|
||||
trace!("Running FixCRLF filter");
|
||||
use std::io::prelude::*;
|
||||
let mut new_vec = Vec::with_capacity(post.bytes.len());
|
||||
|
@ -137,9 +156,9 @@ pub struct AddListHeaders;
|
|||
impl PostFilter for AddListHeaders {
|
||||
fn feed<'p, 'list>(
|
||||
self: Box<Self>,
|
||||
post: &'p mut Post,
|
||||
post: &'p mut PostEntry,
|
||||
ctx: &'p mut ListContext<'list>,
|
||||
) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()> {
|
||||
) -> std::result::Result<(&'p mut PostEntry, &'p mut ListContext<'list>), ()> {
|
||||
trace!("Running AddListHeaders filter");
|
||||
let (mut headers, body) = melib::email::parser::mail(&post.bytes).unwrap();
|
||||
let sender = format!("<{}>", ctx.list.address);
|
||||
|
@ -206,9 +225,9 @@ pub struct ArchivedAtLink;
|
|||
impl PostFilter for ArchivedAtLink {
|
||||
fn feed<'p, 'list>(
|
||||
self: Box<Self>,
|
||||
post: &'p mut Post,
|
||||
post: &'p mut PostEntry,
|
||||
ctx: &'p mut ListContext<'list>,
|
||||
) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()> {
|
||||
) -> std::result::Result<(&'p mut PostEntry, &'p mut ListContext<'list>), ()> {
|
||||
trace!("Running ArchivedAtLink filter");
|
||||
Ok((post, ctx))
|
||||
}
|
||||
|
@ -220,9 +239,9 @@ pub struct FinalizeRecipients;
|
|||
impl PostFilter for FinalizeRecipients {
|
||||
fn feed<'p, 'list>(
|
||||
self: Box<Self>,
|
||||
post: &'p mut Post,
|
||||
post: &'p mut PostEntry,
|
||||
ctx: &'p mut ListContext<'list>,
|
||||
) -> std::result::Result<(&'p mut Post, &'p mut ListContext<'list>), ()> {
|
||||
) -> std::result::Result<(&'p mut PostEntry, &'p mut ListContext<'list>), ()> {
|
||||
trace!("Running FinalizeRecipients filter");
|
||||
let mut recipients = vec![];
|
||||
let mut digests = vec![];
|
|
@ -17,12 +17,20 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! How each list handles new posts and new subscriptions.
|
||||
|
||||
pub use post_policy::*;
|
||||
pub use subscription_policy::*;
|
||||
|
||||
use super::*;
|
||||
mod post_policy {
|
||||
use super::*;
|
||||
use log::trace;
|
||||
use rusqlite::OptionalExtension;
|
||||
|
||||
use crate::{
|
||||
errors::{ErrorKind::*, *},
|
||||
models::{DbVal, PostPolicy},
|
||||
Connection,
|
||||
};
|
||||
|
||||
impl Connection {
|
||||
/// Fetch the post policy of a mailing list.
|
||||
|
@ -207,7 +215,14 @@ mod post_policy {
|
|||
}
|
||||
|
||||
mod subscription_policy {
|
||||
use super::*;
|
||||
use log::trace;
|
||||
use rusqlite::OptionalExtension;
|
||||
|
||||
use crate::{
|
||||
errors::{ErrorKind::*, *},
|
||||
models::{DbVal, SubscriptionPolicy},
|
||||
Connection,
|
||||
};
|
||||
|
||||
impl Connection {
|
||||
/// Fetch the subscription policy of a mailing list.
|
|
@ -17,10 +17,22 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Processing new posts.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::*;
|
||||
use crate::mail::ListRequest;
|
||||
use log::{info, trace};
|
||||
use melib::Envelope;
|
||||
use rusqlite::OptionalExtension;
|
||||
|
||||
use crate::{
|
||||
errors::*,
|
||||
mail::{ListContext, ListRequest, PostAction, PostEntry},
|
||||
models::{changesets::AccountChangeset, Account, DbVal, ListSubscription, MailingList, Post},
|
||||
queue::{Queue, QueueEntry},
|
||||
templates::Template,
|
||||
Connection,
|
||||
};
|
||||
|
||||
impl Connection {
|
||||
/// Insert a mailing list post into the database.
|
||||
|
@ -155,7 +167,6 @@ impl Connection {
|
|||
}
|
||||
|
||||
trace!("Configuration is {:#?}", &self.conf);
|
||||
use crate::mail::{ListContext, Post, PostAction};
|
||||
for mut list in lists {
|
||||
trace!("Examining list {}", list.display_name());
|
||||
let filters = self.list_filters(&list);
|
||||
|
@ -170,7 +181,7 @@ impl Connection {
|
|||
subscriptions: &subscriptions,
|
||||
scheduled_jobs: vec![],
|
||||
};
|
||||
let mut post = Post {
|
||||
let mut post = PostEntry {
|
||||
from: env.from()[0].clone(),
|
||||
bytes: raw.to_vec(),
|
||||
to: env.to().to_vec(),
|
||||
|
@ -183,7 +194,7 @@ impl Connection {
|
|||
});
|
||||
trace!("result {:#?}", result);
|
||||
|
||||
let Post { bytes, action, .. } = post;
|
||||
let PostEntry { bytes, action, .. } = post;
|
||||
trace!("Action is {:#?}", action);
|
||||
let post_env = melib::Envelope::from_bytes(&bytes, None)?;
|
||||
match action {
|
|
@ -21,7 +21,9 @@
|
|||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use super::*;
|
||||
use melib::Envelope;
|
||||
|
||||
use crate::{errors::*, models::DbVal, Connection, DateTime};
|
||||
|
||||
/// In-database queues of mail.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug, serde::Serialize, serde::Deserialize)]
|
||||
|
@ -262,6 +264,7 @@ impl Connection {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn test_queue_delete_array() {
|
|
@ -23,7 +23,7 @@ use std::{future::Future, pin::Pin};
|
|||
|
||||
use melib::smtp::*;
|
||||
|
||||
use crate::{errors::*, Connection, QueueEntry};
|
||||
use crate::{errors::*, queue::QueueEntry, Connection};
|
||||
|
||||
type ResultFuture<T> = Result<Pin<Box<dyn Future<Output = Result<T>> + Send + 'static>>>;
|
||||
|
||||
|
|
|
@ -17,7 +17,19 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use super::*;
|
||||
//! User subscriptions.
|
||||
|
||||
use log::trace;
|
||||
use rusqlite::OptionalExtension;
|
||||
|
||||
use crate::{
|
||||
errors::{ErrorKind::*, *},
|
||||
models::{
|
||||
changesets::{AccountChangeset, ListSubscriptionChangeset},
|
||||
Account, ListCandidateSubscription, ListSubscription,
|
||||
},
|
||||
Connection, DbVal,
|
||||
};
|
||||
|
||||
impl Connection {
|
||||
/// Fetch all subscriptions of a mailing list.
|
||||
|
@ -589,6 +601,7 @@ impl Connection {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
fn test_subscription_ops() {
|
|
@ -17,9 +17,17 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
//! Named templates, for generated e-mail like confirmations, alerts etc.
|
||||
//!
|
||||
//! Template database model: [`Template`].
|
||||
|
||||
use super::*;
|
||||
use log::trace;
|
||||
use rusqlite::OptionalExtension;
|
||||
|
||||
use crate::{
|
||||
errors::{ErrorKind::*, *},
|
||||
Connection, DbVal,
|
||||
};
|
||||
|
||||
/// A named template.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||
|
@ -202,3 +210,159 @@ impl Template {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
/// Fetch all.
|
||||
pub fn fetch_templates(&self) -> Result<Vec<DbVal<Template>>> {
|
||||
let mut stmt = self
|
||||
.connection
|
||||
.prepare("SELECT * FROM templates ORDER BY pk;")?;
|
||||
let iter = stmt.query_map(rusqlite::params![], |row| {
|
||||
let pk = row.get("pk")?;
|
||||
Ok(DbVal(
|
||||
Template {
|
||||
pk,
|
||||
name: row.get("name")?,
|
||||
list: row.get("list")?,
|
||||
subject: row.get("subject")?,
|
||||
headers_json: row.get("headers_json")?,
|
||||
body: row.get("body")?,
|
||||
},
|
||||
pk,
|
||||
))
|
||||
})?;
|
||||
|
||||
let mut ret = vec![];
|
||||
for templ in iter {
|
||||
let templ = templ?;
|
||||
ret.push(templ);
|
||||
}
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Fetch a named template.
|
||||
pub fn fetch_template(
|
||||
&self,
|
||||
template: &str,
|
||||
list_pk: Option<i64>,
|
||||
) -> Result<Option<DbVal<Template>>> {
|
||||
let mut stmt = self
|
||||
.connection
|
||||
.prepare("SELECT * FROM templates WHERE name = ? AND list IS ?;")?;
|
||||
let ret = stmt
|
||||
.query_row(rusqlite::params![&template, &list_pk], |row| {
|
||||
let pk = row.get("pk")?;
|
||||
Ok(DbVal(
|
||||
Template {
|
||||
pk,
|
||||
name: row.get("name")?,
|
||||
list: row.get("list")?,
|
||||
subject: row.get("subject")?,
|
||||
headers_json: row.get("headers_json")?,
|
||||
body: row.get("body")?,
|
||||
},
|
||||
pk,
|
||||
))
|
||||
})
|
||||
.optional()?;
|
||||
if ret.is_none() && list_pk.is_some() {
|
||||
let mut stmt = self
|
||||
.connection
|
||||
.prepare("SELECT * FROM templates WHERE name = ? AND list IS NULL;")?;
|
||||
Ok(stmt
|
||||
.query_row(rusqlite::params![&template], |row| {
|
||||
let pk = row.get("pk")?;
|
||||
Ok(DbVal(
|
||||
Template {
|
||||
pk,
|
||||
name: row.get("name")?,
|
||||
list: row.get("list")?,
|
||||
subject: row.get("subject")?,
|
||||
headers_json: row.get("headers_json")?,
|
||||
body: row.get("body")?,
|
||||
},
|
||||
pk,
|
||||
))
|
||||
})
|
||||
.optional()?)
|
||||
} else {
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a named template.
|
||||
pub fn add_template(&self, template: Template) -> Result<DbVal<Template>> {
|
||||
let mut stmt = self.connection.prepare(
|
||||
"INSERT INTO templates(name, list, subject, headers_json, body) VALUES(?, ?, ?, ?, ?) \
|
||||
RETURNING *;",
|
||||
)?;
|
||||
let ret = stmt
|
||||
.query_row(
|
||||
rusqlite::params![
|
||||
&template.name,
|
||||
&template.list,
|
||||
&template.subject,
|
||||
&template.headers_json,
|
||||
&template.body
|
||||
],
|
||||
|row| {
|
||||
let pk = row.get("pk")?;
|
||||
Ok(DbVal(
|
||||
Template {
|
||||
pk,
|
||||
name: row.get("name")?,
|
||||
list: row.get("list")?,
|
||||
subject: row.get("subject")?,
|
||||
headers_json: row.get("headers_json")?,
|
||||
body: row.get("body")?,
|
||||
},
|
||||
pk,
|
||||
))
|
||||
},
|
||||
)
|
||||
.map_err(|err| {
|
||||
if matches!(
|
||||
err,
|
||||
rusqlite::Error::SqliteFailure(
|
||||
rusqlite::ffi::Error {
|
||||
code: rusqlite::ffi::ErrorCode::ConstraintViolation,
|
||||
extended_code: 787
|
||||
},
|
||||
_
|
||||
)
|
||||
) {
|
||||
Error::from(err).chain_err(|| NotFound("Could not find a list with this pk."))
|
||||
} else {
|
||||
err.into()
|
||||
}
|
||||
})?;
|
||||
|
||||
trace!("add_template {:?}.", &ret);
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Remove a named template.
|
||||
pub fn remove_template(&self, template: &str, list_pk: Option<i64>) -> Result<Template> {
|
||||
let mut stmt = self
|
||||
.connection
|
||||
.prepare("DELETE FROM templates WHERE name = ? AND list IS ? RETURNING *;")?;
|
||||
let ret = stmt.query_row(rusqlite::params![&template, &list_pk], |row| {
|
||||
Ok(Template {
|
||||
pk: -1,
|
||||
name: row.get("name")?,
|
||||
list: row.get("list")?,
|
||||
subject: row.get("subject")?,
|
||||
headers_json: row.get("headers_json")?,
|
||||
body: row.get("body")?,
|
||||
})
|
||||
})?;
|
||||
|
||||
trace!(
|
||||
"remove_template {} list_pk {:?} {:?}.",
|
||||
template,
|
||||
&list_pk,
|
||||
&ret
|
||||
);
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use mailpot::{models::*, Configuration, Connection, Queue, SendMail};
|
||||
use mailpot::{models::*, queue::Queue, Configuration, Connection, SendMail};
|
||||
use mailpot_tests::init_stderr_logging;
|
||||
use tempfile::TempDir;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use mailpot::{melib, models::*, Configuration, Connection, Queue, SendMail};
|
||||
use mailpot::{melib, models::*, queue::Queue, Configuration, Connection, SendMail};
|
||||
use mailpot_tests::init_stderr_logging;
|
||||
use tempfile::TempDir;
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
|
||||
use log::{trace, warn};
|
||||
use mailpot::{melib, models::*, Configuration, Connection, Queue, SendMail};
|
||||
use mailpot::{melib, models::*, queue::Queue, Configuration, Connection, SendMail};
|
||||
use mailpot_tests::*;
|
||||
use melib::smol;
|
||||
use tempfile::TempDir;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use mailpot::{models::*, Configuration, Connection, Queue, SendMail};
|
||||
use mailpot::{models::*, queue::Queue, Configuration, Connection, SendMail};
|
||||
use mailpot_tests::init_stderr_logging;
|
||||
use tempfile::TempDir;
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use mailpot::{models::*, Configuration, Connection, Queue, SendMail, Template};
|
||||
use mailpot::{models::*, queue::Queue, Configuration, Connection, SendMail, Template};
|
||||
use mailpot_tests::init_stderr_logging;
|
||||
use tempfile::TempDir;
|
||||
|
||||
|
|
|
@ -94,7 +94,13 @@ See <https://www.postfix.org/master.5.html>.
|
|||
|
||||
.br
|
||||
|
||||
mpot sample\-config [\-\-with\-smtp \fIWITH_SMTP\fR]
|
||||
.br
|
||||
|
||||
Prints a sample config file to STDOUT.
|
||||
.TP
|
||||
\-\-with\-smtp
|
||||
Use an SMTP connection instead of a shell process.
|
||||
.ie \n(.g .ds Aq \(aq
|
||||
.el .ds Aq '
|
||||
.\fB
|
||||
|
|
Loading…
Reference in New Issue