Split into crates
parent
b079ef0c86
commit
3c582f7729
File diff suppressed because it is too large
Load Diff
27
Cargo.toml
27
Cargo.toml
|
@ -1,21 +1,6 @@
|
||||||
[package]
|
[workspace]
|
||||||
name = "mailpot"
|
members = [
|
||||||
version = "0.1.0"
|
"core",
|
||||||
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
"cli",
|
||||||
edition = "2018"
|
"rest-http",
|
||||||
license = "LICENSE"
|
]
|
||||||
readme = "README.md"
|
|
||||||
description = "mailing list manager"
|
|
||||||
repository = "https://github.com/meli/mailpot"
|
|
||||||
keywords = ["mail", "mailing-lists" ]
|
|
||||||
categories = ["email"]
|
|
||||||
default-run = "mpot"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
chrono = { version = "0.4.15", features = ["serde", ] }
|
|
||||||
error-chain = "0.12.4"
|
|
||||||
melib = { version = "*", default-features = false, features = ["smtp", "unicode_algorithms"], git="https://github.com/meli/meli", branch = "master" }
|
|
||||||
rusqlite = {version = "0.20.0"}
|
|
||||||
serde = { version = "1.0.114" }
|
|
||||||
structopt = "0.3.16"
|
|
||||||
xdg = "2.1.0"
|
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
# Mailpot
|
# Mailpot
|
||||||
## flexible mailing list manager
|
## WIP mailing list manager
|
||||||
|
|
||||||
|
Crates:
|
||||||
|
|
||||||
|
- `core`
|
||||||
|
- `cli` a command line tool to manage lists
|
||||||
|
- `rest-http` a REST http server to manage lists
|
||||||
|
|
||||||
```text
|
```text
|
||||||
% mpot help
|
% mpot help
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
[package]
|
||||||
|
name = "mailpot-cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "LICENSE"
|
||||||
|
readme = "README.md"
|
||||||
|
description = "mailing list manager"
|
||||||
|
repository = "https://github.com/meli/mailpot"
|
||||||
|
keywords = ["mail", "mailing-lists" ]
|
||||||
|
categories = ["email"]
|
||||||
|
default-run = "mpot"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "mpot"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mailpot = { version = "0.1.0", path = "../core" }
|
||||||
|
structopt = "0.3.16"
|
|
@ -17,26 +17,16 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// `error_chain!` can recurse deeply
|
extern crate mailpot;
|
||||||
#![recursion_limit = "1024"]
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate error_chain;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde;
|
|
||||||
use structopt::StructOpt;
|
|
||||||
|
|
||||||
pub mod config;
|
|
||||||
pub use config::*;
|
|
||||||
pub mod models;
|
|
||||||
pub mod post;
|
|
||||||
pub use models::*;
|
|
||||||
pub mod errors;
|
|
||||||
pub use errors::*;
|
|
||||||
pub mod db;
|
|
||||||
pub use db::*;
|
|
||||||
|
|
||||||
|
pub use mailpot::config::*;
|
||||||
|
pub use mailpot::db::*;
|
||||||
|
pub use mailpot::errors::*;
|
||||||
|
pub use mailpot::models::*;
|
||||||
|
pub use mailpot::post::*;
|
||||||
|
pub use mailpot::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
#[structopt(
|
#[structopt(
|
||||||
|
@ -60,6 +50,8 @@ struct Opt {
|
||||||
enum Command {
|
enum Command {
|
||||||
///Prints database filesystem location
|
///Prints database filesystem location
|
||||||
DbLocation,
|
DbLocation,
|
||||||
|
///Dumps database data to STDOUT
|
||||||
|
DumpDatabase,
|
||||||
///Lists all registered mailing lists
|
///Lists all registered mailing lists
|
||||||
ListLists,
|
ListLists,
|
||||||
///Mailing list management
|
///Mailing list management
|
||||||
|
@ -129,11 +121,21 @@ fn run_app(opt: Opt) -> Result<()> {
|
||||||
if opt.debug {
|
if opt.debug {
|
||||||
println!("DEBUG: {:?}", &opt);
|
println!("DEBUG: {:?}", &opt);
|
||||||
}
|
}
|
||||||
|
Configuration::init()?;
|
||||||
use Command::*;
|
use Command::*;
|
||||||
match opt.cmd {
|
match opt.cmd {
|
||||||
DbLocation => {
|
DbLocation => {
|
||||||
println!("{}", Database::db_path()?.display());
|
println!("{}", Database::db_path()?.display());
|
||||||
}
|
}
|
||||||
|
DumpDatabase => {
|
||||||
|
let db = Database::open_or_create_db()?;
|
||||||
|
let lists = db.list_lists()?;
|
||||||
|
let mut stdout = std::io::stdout();
|
||||||
|
serde_json::to_writer_pretty(&mut stdout, &lists)?;
|
||||||
|
for l in &lists {
|
||||||
|
serde_json::to_writer_pretty(&mut stdout, &db.list_members(l.pk)?)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
ListLists => {
|
ListLists => {
|
||||||
let db = Database::open_or_create_db()?;
|
let db = Database::open_or_create_db()?;
|
||||||
let lists = db.list_lists()?;
|
let lists = db.list_lists()?;
|
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "mailpot"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "LICENSE"
|
||||||
|
readme = "README.md"
|
||||||
|
description = "mailing list manager"
|
||||||
|
repository = "https://github.com/meli/mailpot"
|
||||||
|
keywords = ["mail", "mailing-lists" ]
|
||||||
|
categories = ["email"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
chrono = { version = "0.4.15", features = ["serde", ] }
|
||||||
|
error-chain = "0.12.4"
|
||||||
|
diesel = { version = "1.4.5", features = ["sqlite", ] }
|
||||||
|
melib = { version = "*", default-features = false, features = ["smtp", "unicode_algorithms"], path="../../meli/melib", branch = "master" }
|
||||||
|
#melib = { version = "*", default-features = false, features = ["smtp", "unicode_algorithms"], git="https://github.com/meli/meli", branch = "master" }
|
||||||
|
rusqlite = {version = "0.20.0"}
|
||||||
|
serde = { version = "1.0.114" }
|
||||||
|
serde_json = "1.0.57"
|
||||||
|
toml = "^0.5"
|
||||||
|
xdg = "2.1.0"
|
|
@ -0,0 +1,17 @@
|
||||||
|
# mailpot-core
|
||||||
|
|
||||||
|
Initialize `sqlite3` database:
|
||||||
|
|
||||||
|
Either
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sqlite3 mpot.db < ./src/schema.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
or
|
||||||
|
|
||||||
|
|
||||||
|
```shell
|
||||||
|
# cargo install diesel_cli --no-default-features --features sqlite
|
||||||
|
diesel migration run
|
||||||
|
```
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("cargo:rerun-if-changed=src/schema.sql.m4");
|
||||||
|
|
||||||
|
let output = Command::new("m4")
|
||||||
|
.arg("./src/schema.sql.m4")
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
let mut file = std::fs::File::create("./src/schema.sql").unwrap();
|
||||||
|
file.write_all(&output.stdout).unwrap();
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/schema.rs"
|
|
@ -0,0 +1,8 @@
|
||||||
|
DROP TABLE IF EXISTS mailing_lists;
|
||||||
|
DROP TABLE IF EXISTS list_owner;
|
||||||
|
DROP TABLE IF EXISTS post_policy;
|
||||||
|
DROP TABLE IF EXISTS membership;
|
||||||
|
DROP TABLE IF EXISTS post;
|
||||||
|
DROP TABLE IF EXISTS post_event;
|
||||||
|
DROP INDEX IF EXISTS mailing_lists_idx;
|
||||||
|
DROP INDEX IF EXISTS membership_idx;
|
|
@ -0,0 +1,63 @@
|
||||||
|
PRAGMA foreign_keys = true;
|
||||||
|
PRAGMA encoding = 'UTF-8';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS mailing_lists (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
archive_url TEXT,
|
||||||
|
description TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS list_owner (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
list INTEGER NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
name TEXT,
|
||||||
|
FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS post_policy (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
list INTEGER NOT NULL UNIQUE,
|
||||||
|
announce_only BOOLEAN CHECK (announce_only in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
subscriber_only BOOLEAN CHECK (subscriber_only in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
approval_needed BOOLEAN CHECK (approval_needed in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
CHECK(((approval_needed) OR (((announce_only) OR (subscriber_only)) AND NOT ((announce_only) AND (subscriber_only)))) AND NOT ((approval_needed) AND (((announce_only) OR (subscriber_only)) AND NOT ((announce_only) AND (subscriber_only))))),
|
||||||
|
FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS membership (
|
||||||
|
list INTEGER NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
name TEXT,
|
||||||
|
digest BOOLEAN CHECK (digest in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
hide_address BOOLEAN CHECK (hide_address in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
receive_duplicates BOOLEAN CHECK (receive_duplicates in (0, 1)) NOT NULL DEFAULT 1,
|
||||||
|
receive_own_posts BOOLEAN CHECK (receive_own_posts in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
receive_confirmation BOOLEAN CHECK (receive_confirmation in (0, 1)) NOT NULL DEFAULT 1,
|
||||||
|
PRIMARY KEY (list, address),
|
||||||
|
FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS post (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
list INTEGER NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
message_id TEXT NOT NULL,
|
||||||
|
message BLOB NOT NULL,
|
||||||
|
FOREIGN KEY (list, address) REFERENCES membership(list, address) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS post_event (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
post INTEGER NOT NULL,
|
||||||
|
date INTEGER NOT NULL,
|
||||||
|
kind CHAR(1) CHECK (kind IN ('R', 'S', 'D', 'B', 'O')) NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (post) REFERENCES post(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS mailing_lists_idx ON mailing_lists(id);
|
||||||
|
CREATE INDEX IF NOT EXISTS membership_idx ON membership(address);
|
|
@ -19,10 +19,13 @@
|
||||||
|
|
||||||
use super::errors::*;
|
use super::errors::*;
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use std::io::Write;
|
use std::cell::RefCell;
|
||||||
|
use std::io::{Read, Write};
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
thread_local!(pub static CONFIG: RefCell<Configuration> = RefCell::new(Configuration::new()));
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
#[serde(tag = "type", content = "value")]
|
#[serde(tag = "type", content = "value")]
|
||||||
pub enum SendMail {
|
pub enum SendMail {
|
||||||
|
@ -32,10 +35,33 @@ pub enum SendMail {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct Configuration {
|
pub struct Configuration {
|
||||||
send_mail: SendMail,
|
pub send_mail: SendMail,
|
||||||
|
#[serde(default = "default_storage_fn")]
|
||||||
|
pub storage: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Configuration {
|
impl Configuration {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
Configuration {
|
||||||
|
send_mail: SendMail::ShellCommand(String::new()),
|
||||||
|
storage: "sqlite3".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() -> Result<()> {
|
||||||
|
let path =
|
||||||
|
xdg::BaseDirectories::with_prefix("mailpot")?.place_config_file("config.toml")?;
|
||||||
|
let mut s = String::new();
|
||||||
|
let mut file = std::fs::File::open(&path)?;
|
||||||
|
file.read_to_string(&mut s)?;
|
||||||
|
let config: Configuration = toml::from_str(&s)?;
|
||||||
|
CONFIG.with(|f| {
|
||||||
|
*f.borrow_mut() = config;
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn data_directory() -> Result<PathBuf> {
|
pub fn data_directory() -> Result<PathBuf> {
|
||||||
Ok(xdg::BaseDirectories::with_prefix("mailpot")?.get_data_home())
|
Ok(xdg::BaseDirectories::with_prefix("mailpot")?.get_data_home())
|
||||||
}
|
}
|
||||||
|
@ -74,3 +100,7 @@ impl Configuration {
|
||||||
Self::save_message_to_path(&msg, temp_path)
|
Self::save_message_to_path(&msg, temp_path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn default_storage_fn() -> String {
|
||||||
|
"sqlite3".to_string()
|
||||||
|
}
|
|
@ -0,0 +1,236 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use diesel::{prelude::*, Connection};
|
||||||
|
use melib::Envelope;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
const DB_NAME: &str = "mpot.db";
|
||||||
|
|
||||||
|
pub struct Database {
|
||||||
|
connection: SqliteConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Database {
|
||||||
|
pub fn list_lists(&self) -> Result<Vec<MailingList>> {
|
||||||
|
use schema::mailing_lists;
|
||||||
|
|
||||||
|
let ret = mailing_lists::table.load(&self.connection)?;
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_list(&self, pk: i32) -> Result<Option<MailingList>> {
|
||||||
|
use schema::mailing_lists;
|
||||||
|
|
||||||
|
let ret = mailing_lists::table
|
||||||
|
.filter(mailing_lists::pk.eq(pk))
|
||||||
|
.first(&self.connection)
|
||||||
|
.optional()?;
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_list_policy(&self, pk: i32) -> Result<Option<PostPolicy>> {
|
||||||
|
use schema::post_policy;
|
||||||
|
|
||||||
|
let ret = post_policy::table
|
||||||
|
.filter(post_policy::list.eq(pk))
|
||||||
|
.first(&self.connection)
|
||||||
|
.optional()?;
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_list_owners(&self, pk: i32) -> Result<Vec<ListOwner>> {
|
||||||
|
use schema::list_owner;
|
||||||
|
|
||||||
|
let ret = list_owner::table
|
||||||
|
.filter(list_owner::list.eq(pk))
|
||||||
|
.load(&self.connection)?;
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list_members(&self, pk: i32) -> Result<Vec<ListMembership>> {
|
||||||
|
use schema::membership;
|
||||||
|
|
||||||
|
let ret = membership::table
|
||||||
|
.filter(membership::list.eq(pk))
|
||||||
|
.load(&self.connection)?;
|
||||||
|
Ok(ret)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_member(&self, list_pk: i32, mut new_val: ListMembership) -> Result<()> {
|
||||||
|
use schema::membership;
|
||||||
|
new_val.list = list_pk;
|
||||||
|
|
||||||
|
diesel::insert_into(membership::table)
|
||||||
|
.values(&new_val)
|
||||||
|
.execute(&self.connection)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_member(&self, list_pk: i32, address: &str) -> Result<()> {
|
||||||
|
use schema::membership;
|
||||||
|
diesel::delete(
|
||||||
|
membership::table
|
||||||
|
.filter(membership::columns::list.eq(list_pk))
|
||||||
|
.filter(membership::columns::address.eq(address)),
|
||||||
|
)
|
||||||
|
.execute(&self.connection)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_list(&self, new_val: MailingList) -> Result<()> {
|
||||||
|
use schema::mailing_lists;
|
||||||
|
|
||||||
|
diesel::insert_into(mailing_lists::table)
|
||||||
|
.values(&new_val)
|
||||||
|
.execute(&self.connection)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn db_path() -> Result<PathBuf> {
|
||||||
|
let name = DB_NAME;
|
||||||
|
let data_dir = xdg::BaseDirectories::with_prefix("mailpot")?;
|
||||||
|
Ok(data_dir.place_data_file(name)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_db(db_path: PathBuf) -> Result<Self> {
|
||||||
|
if !db_path.exists() {
|
||||||
|
return Err("Database doesn't exist".into());
|
||||||
|
}
|
||||||
|
Ok(Database {
|
||||||
|
connection: SqliteConnection::establish(&db_path.to_str().unwrap())?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_or_create_db() -> Result<Self> {
|
||||||
|
let db_path = Self::db_path()?;
|
||||||
|
let mut set_mode = false;
|
||||||
|
if !db_path.exists() {
|
||||||
|
println!("Creating {} database in {}", DB_NAME, db_path.display());
|
||||||
|
set_mode = true;
|
||||||
|
}
|
||||||
|
let conn = SqliteConnection::establish(&db_path.to_str().unwrap())?;
|
||||||
|
if set_mode {
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let file = std::fs::File::open(&db_path)?;
|
||||||
|
let metadata = file.metadata()?;
|
||||||
|
let mut permissions = metadata.permissions();
|
||||||
|
|
||||||
|
permissions.set_mode(0o600); // Read/write for owner only.
|
||||||
|
file.set_permissions(permissions)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Ok(Database { connection: conn })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_list_filters(&self, _list: &MailingList) -> Vec<Box<dyn crate::post::PostFilter>> {
|
||||||
|
use crate::post::*;
|
||||||
|
vec![
|
||||||
|
Box::new(FixCRLF),
|
||||||
|
Box::new(PostRightsCheck),
|
||||||
|
Box::new(AddListHeaders),
|
||||||
|
Box::new(FinalizeRecipients),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post(&self, env: Envelope, raw: &[u8]) -> Result<()> {
|
||||||
|
let mut lists = self.list_lists()?;
|
||||||
|
let tos = env
|
||||||
|
.to()
|
||||||
|
.iter()
|
||||||
|
.map(|addr| addr.get_email())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
if tos.is_empty() {
|
||||||
|
return Err("Envelope To: field is empty!".into());
|
||||||
|
}
|
||||||
|
if env.from().is_empty() {
|
||||||
|
return Err("Envelope From: field is empty!".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
lists.retain(|list| tos.iter().any(|a| a == &list.address));
|
||||||
|
if lists.is_empty() {
|
||||||
|
return Err("Envelope To: field doesn't contain any known lists!".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut configuration = crate::config::Configuration::new();
|
||||||
|
crate::config::CONFIG.with(|f| {
|
||||||
|
configuration = f.borrow().clone();
|
||||||
|
});
|
||||||
|
use crate::post::{Post, PostAction};
|
||||||
|
for mut list in lists {
|
||||||
|
let filters = self.get_list_filters(&list);
|
||||||
|
let memberships = self.list_members(list.pk)?;
|
||||||
|
let mut post = Post {
|
||||||
|
policy: self.get_list_policy(list.pk)?,
|
||||||
|
list_owners: self.get_list_owners(list.pk)?,
|
||||||
|
list: &mut list,
|
||||||
|
from: env.from()[0].clone(),
|
||||||
|
memberships: &memberships,
|
||||||
|
bytes: raw.to_vec(),
|
||||||
|
to: env.to().to_vec(),
|
||||||
|
action: PostAction::Hold,
|
||||||
|
};
|
||||||
|
let result = filters
|
||||||
|
.into_iter()
|
||||||
|
.fold(Ok(&mut post), |p, f| p.and_then(|p| f.feed(p)));
|
||||||
|
eprintln!("result {:?}", result);
|
||||||
|
|
||||||
|
let Post { bytes, action, .. } = post;
|
||||||
|
eprintln!("send_mail {:?}", &configuration.send_mail);
|
||||||
|
match configuration.send_mail {
|
||||||
|
crate::config::SendMail::Smtp(ref smtp_conf) => {
|
||||||
|
let smtp_conf = smtp_conf.clone();
|
||||||
|
use melib::futures;
|
||||||
|
use melib::smol;
|
||||||
|
use melib::smtp::*;
|
||||||
|
std::thread::spawn(|| smol::run(futures::future::pending::<()>()));
|
||||||
|
let mut conn = futures::executor::block_on(SmtpConnection::new_connection(
|
||||||
|
smtp_conf.clone(),
|
||||||
|
))?;
|
||||||
|
match action {
|
||||||
|
PostAction::Accept {
|
||||||
|
recipients,
|
||||||
|
digests: _digests,
|
||||||
|
} => {
|
||||||
|
futures::executor::block_on(conn.mail_transaction(
|
||||||
|
&String::from_utf8_lossy(&bytes),
|
||||||
|
Some(&recipients),
|
||||||
|
))?;
|
||||||
|
/* - Save digest metadata in database */
|
||||||
|
}
|
||||||
|
PostAction::Reject { reason: _ } => {
|
||||||
|
/* - Notify submitter */
|
||||||
|
//futures::executor::block_on(conn.mail_transaction(&post.bytes, b)).unwrap();
|
||||||
|
}
|
||||||
|
PostAction::Defer { reason: _ } => {
|
||||||
|
/* - Notify submitter
|
||||||
|
* - Save in database */
|
||||||
|
}
|
||||||
|
PostAction::Hold => { /* - Save in database */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,9 +20,12 @@
|
||||||
// Create the Error, ErrorKind, ResultExt, and Result types
|
// Create the Error, ErrorKind, ResultExt, and Result types
|
||||||
error_chain! {
|
error_chain! {
|
||||||
foreign_links {
|
foreign_links {
|
||||||
Sql(rusqlite::Error);
|
Db(diesel::ConnectionError);
|
||||||
|
Sql(diesel::result::Error);
|
||||||
Io(::std::io::Error);
|
Io(::std::io::Error);
|
||||||
Xdg(xdg::BaseDirectoriesError);
|
Xdg(xdg::BaseDirectoriesError);
|
||||||
Melib(melib::error::MeliError);
|
Melib(melib::error::MeliError);
|
||||||
|
Configuration(toml::de::Error);
|
||||||
|
SerdeJson(serde_json::Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
// `error_chain!` can recurse deeply
|
||||||
|
#![recursion_limit = "1024"]
|
||||||
|
//#![warn(missing_docs)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate error_chain;
|
||||||
|
#[macro_use]
|
||||||
|
pub extern crate serde;
|
||||||
|
#[macro_use]
|
||||||
|
pub extern crate diesel;
|
||||||
|
|
||||||
|
pub use melib;
|
||||||
|
pub use serde_json;
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod models;
|
||||||
|
pub mod post;
|
||||||
|
pub mod schema;
|
||||||
|
use models::*;
|
||||||
|
pub mod errors;
|
||||||
|
use errors::*;
|
||||||
|
pub mod db;
|
|
@ -18,12 +18,12 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use rusqlite::Row;
|
use schema::*;
|
||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Insertable, Queryable, Deserialize, Serialize)]
|
||||||
|
#[table_name = "mailing_lists"]
|
||||||
pub struct MailingList {
|
pub struct MailingList {
|
||||||
pub pk: i64,
|
pub pk: i32,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub address: String,
|
pub address: String,
|
||||||
|
@ -49,20 +49,6 @@ impl std::fmt::Display for MailingList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&'_ Row<'_>> for MailingList {
|
|
||||||
type Error = rusqlite::Error;
|
|
||||||
fn try_from(row: &'_ Row<'_>) -> std::result::Result<MailingList, rusqlite::Error> {
|
|
||||||
Ok(MailingList {
|
|
||||||
pk: row.get("pk")?,
|
|
||||||
name: row.get("name")?,
|
|
||||||
id: row.get("id")?,
|
|
||||||
address: row.get("address")?,
|
|
||||||
description: row.get("description")?,
|
|
||||||
archive_url: row.get("archive_url")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MailingList {
|
impl MailingList {
|
||||||
pub fn list_id(&self) -> String {
|
pub fn list_id(&self) -> String {
|
||||||
format!("\"{}\" <{}>", self.name, self.address)
|
format!("\"{}\" <{}>", self.name, self.address)
|
||||||
|
@ -85,9 +71,10 @@ impl MailingList {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Insertable, Queryable, Deserialize, Serialize)]
|
||||||
|
#[table_name = "membership"]
|
||||||
pub struct ListMembership {
|
pub struct ListMembership {
|
||||||
pub list: i64,
|
pub list: i32,
|
||||||
pub address: String,
|
pub address: String,
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub digest: bool,
|
pub digest: bool,
|
||||||
|
@ -115,22 +102,6 @@ impl std::fmt::Display for ListMembership {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&'_ Row<'_>> for ListMembership {
|
|
||||||
type Error = rusqlite::Error;
|
|
||||||
fn try_from(row: &'_ Row<'_>) -> std::result::Result<ListMembership, rusqlite::Error> {
|
|
||||||
Ok(ListMembership {
|
|
||||||
list: row.get("list")?,
|
|
||||||
address: row.get("address")?,
|
|
||||||
name: row.get("name")?,
|
|
||||||
digest: row.get("digest")?,
|
|
||||||
hide_address: row.get("hide_address")?,
|
|
||||||
receive_duplicates: row.get("receive_duplicates")?,
|
|
||||||
receive_own_posts: row.get("receive_own_posts")?,
|
|
||||||
receive_confirmation: row.get("receive_confirmation")?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ListMembership {
|
impl ListMembership {
|
||||||
pub fn into_address(&self) -> melib::email::Address {
|
pub fn into_address(&self) -> melib::email::Address {
|
||||||
use melib::email::Address;
|
use melib::email::Address;
|
||||||
|
@ -143,3 +114,57 @@ impl ListMembership {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Insertable, Queryable, Deserialize, Serialize)]
|
||||||
|
#[table_name = "post_policy"]
|
||||||
|
pub struct PostPolicy {
|
||||||
|
pub pk: i32,
|
||||||
|
pub list: i32,
|
||||||
|
pub announce_only: bool,
|
||||||
|
pub subscriber_only: bool,
|
||||||
|
pub approval_needed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for PostPolicy {
|
||||||
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(fmt, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Insertable, Queryable, Deserialize, Serialize)]
|
||||||
|
#[table_name = "list_owner"]
|
||||||
|
pub struct ListOwner {
|
||||||
|
pub pk: i32,
|
||||||
|
pub list: i32,
|
||||||
|
pub address: String,
|
||||||
|
pub name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ListOwner {
|
||||||
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
if let Some(ref name) = self.name {
|
||||||
|
write!(
|
||||||
|
fmt,
|
||||||
|
"[#{} {}] \"{}\" <{}>",
|
||||||
|
self.pk, self.list, name, self.address
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
write!(fmt, "[#{} {}] {}", self.pk, self.list, self.address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ListOwner> for ListMembership {
|
||||||
|
fn from(val: ListOwner) -> ListMembership {
|
||||||
|
ListMembership {
|
||||||
|
list: val.list,
|
||||||
|
address: val.address,
|
||||||
|
name: val.name,
|
||||||
|
digest: false,
|
||||||
|
hide_address: false,
|
||||||
|
receive_duplicates: true,
|
||||||
|
receive_own_posts: false,
|
||||||
|
receive_confirmation: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ use melib::Address;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PostAction {
|
pub enum PostAction {
|
||||||
|
Hold,
|
||||||
Accept {
|
Accept {
|
||||||
recipients: Vec<Address>,
|
recipients: Vec<Address>,
|
||||||
digests: Vec<Address>,
|
digests: Vec<Address>,
|
||||||
|
@ -37,8 +38,10 @@ pub enum PostAction {
|
||||||
///Post to be considered by the list's `PostFilter` stack.
|
///Post to be considered by the list's `PostFilter` stack.
|
||||||
pub struct Post<'list> {
|
pub struct Post<'list> {
|
||||||
pub list: &'list mut MailingList,
|
pub list: &'list mut MailingList,
|
||||||
|
pub list_owners: Vec<ListOwner>,
|
||||||
pub from: Address,
|
pub from: Address,
|
||||||
pub memberships: &'list [ListMembership],
|
pub memberships: &'list [ListMembership],
|
||||||
|
pub policy: Option<PostPolicy>,
|
||||||
pub bytes: Vec<u8>,
|
pub bytes: Vec<u8>,
|
||||||
pub to: Vec<Address>,
|
pub to: Vec<Address>,
|
||||||
pub action: PostAction,
|
pub action: PostAction,
|
||||||
|
@ -51,6 +54,7 @@ impl<'list> core::fmt::Debug for Post<'list> {
|
||||||
.field("from", &self.from)
|
.field("from", &self.from)
|
||||||
.field("members", &format_args!("{}", self.memberships.len()))
|
.field("members", &format_args!("{}", self.memberships.len()))
|
||||||
.field("bytes", &format_args!("{}", self.bytes.len()))
|
.field("bytes", &format_args!("{}", self.bytes.len()))
|
||||||
|
.field("policy", &self.policy)
|
||||||
.field("to", &self.to.as_slice())
|
.field("to", &self.to.as_slice())
|
||||||
.field("action", &self.action)
|
.field("action", &self.action)
|
||||||
.finish()
|
.finish()
|
||||||
|
@ -60,19 +64,43 @@ impl<'list> core::fmt::Debug for Post<'list> {
|
||||||
///Filter that modifies and/or verifies a post candidate. On rejection, return a string
|
///Filter that modifies and/or verifies a post candidate. On rejection, return a string
|
||||||
///describing the error and optionally set `post.action` to `Reject` or `Defer`
|
///describing the error and optionally set `post.action` to `Reject` or `Defer`
|
||||||
pub trait PostFilter {
|
pub trait PostFilter {
|
||||||
fn feed<'list>(
|
fn feed<'p, 'list>(
|
||||||
self: Box<Self>,
|
self: Box<Self>,
|
||||||
post: &'list mut Post<'list>,
|
post: &'p mut Post<'list>,
|
||||||
) -> std::result::Result<&'list mut Post<'list>, String>;
|
) -> std::result::Result<&'p mut Post<'list>, String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
///Check that submitter can post to list, for now it accepts everything.
|
///Check that submitter can post to list, for now it accepts everything.
|
||||||
pub struct PostRightsCheck;
|
pub struct PostRightsCheck;
|
||||||
impl PostFilter for PostRightsCheck {
|
impl PostFilter for PostRightsCheck {
|
||||||
fn feed<'list>(
|
fn feed<'p, 'list>(
|
||||||
self: Box<Self>,
|
self: Box<Self>,
|
||||||
post: &'list mut Post<'list>,
|
post: &'p mut Post<'list>,
|
||||||
) -> std::result::Result<&'list mut Post<'list>, String> {
|
) -> std::result::Result<&'p mut Post<'list>, String> {
|
||||||
|
if let Some(ref policy) = post.policy {
|
||||||
|
if policy.announce_only {
|
||||||
|
let owner_addresses = post
|
||||||
|
.list_owners
|
||||||
|
.iter()
|
||||||
|
.map(|lo| {
|
||||||
|
let lm: ListMembership = lo.clone().into();
|
||||||
|
lm.into_address()
|
||||||
|
})
|
||||||
|
.collect::<Vec<Address>>();
|
||||||
|
if !owner_addresses.iter().any(|addr| *addr == post.from) {
|
||||||
|
return Err("You are not allowed to post on this list.".to_string());
|
||||||
|
}
|
||||||
|
} else if policy.subscriber_only {
|
||||||
|
let email_from = post.from.get_email();
|
||||||
|
if !post.memberships.iter().any(|lm| lm.address == email_from) {
|
||||||
|
return Err("You are not subscribed to this list.".to_string());
|
||||||
|
}
|
||||||
|
} else if policy.approval_needed {
|
||||||
|
post.action = PostAction::Defer {
|
||||||
|
reason: "Approval from the list's moderators is required.".to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(post)
|
Ok(post)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,10 +108,10 @@ impl PostFilter for PostRightsCheck {
|
||||||
///Ensure message contains only `\r\n` line terminators, required by SMTP.
|
///Ensure message contains only `\r\n` line terminators, required by SMTP.
|
||||||
pub struct FixCRLF;
|
pub struct FixCRLF;
|
||||||
impl PostFilter for FixCRLF {
|
impl PostFilter for FixCRLF {
|
||||||
fn feed<'list>(
|
fn feed<'p, 'list>(
|
||||||
self: Box<Self>,
|
self: Box<Self>,
|
||||||
post: &'list mut Post<'list>,
|
post: &'p mut Post<'list>,
|
||||||
) -> std::result::Result<&'list mut Post<'list>, String> {
|
) -> std::result::Result<&'p mut Post<'list>, String> {
|
||||||
use std::io::prelude::*;
|
use std::io::prelude::*;
|
||||||
let mut new_vec = Vec::with_capacity(post.bytes.len());
|
let mut new_vec = Vec::with_capacity(post.bytes.len());
|
||||||
for line in post.bytes.lines() {
|
for line in post.bytes.lines() {
|
||||||
|
@ -98,10 +126,10 @@ impl PostFilter for FixCRLF {
|
||||||
///Add `List-*` headers
|
///Add `List-*` headers
|
||||||
pub struct AddListHeaders;
|
pub struct AddListHeaders;
|
||||||
impl PostFilter for AddListHeaders {
|
impl PostFilter for AddListHeaders {
|
||||||
fn feed<'list>(
|
fn feed<'p, 'list>(
|
||||||
self: Box<Self>,
|
self: Box<Self>,
|
||||||
post: &'list mut Post<'list>,
|
post: &'p mut Post<'list>,
|
||||||
) -> std::result::Result<&'list mut Post<'list>, String> {
|
) -> std::result::Result<&'p mut Post<'list>, String> {
|
||||||
let (mut headers, body) = melib::email::parser::mail(&post.bytes).unwrap();
|
let (mut headers, body) = melib::email::parser::mail(&post.bytes).unwrap();
|
||||||
let list_id = post.list.list_id();
|
let list_id = post.list.list_id();
|
||||||
headers.push((&b"List-ID"[..], list_id.as_bytes()));
|
headers.push((&b"List-ID"[..], list_id.as_bytes()));
|
||||||
|
@ -142,10 +170,10 @@ impl PostFilter for AddListHeaders {
|
||||||
///Adds `Archived-At` field, if configured.
|
///Adds `Archived-At` field, if configured.
|
||||||
pub struct ArchivedAtLink;
|
pub struct ArchivedAtLink;
|
||||||
impl PostFilter for ArchivedAtLink {
|
impl PostFilter for ArchivedAtLink {
|
||||||
fn feed<'list>(
|
fn feed<'p, 'list>(
|
||||||
self: Box<Self>,
|
self: Box<Self>,
|
||||||
post: &'list mut Post<'list>,
|
post: &'p mut Post<'list>,
|
||||||
) -> std::result::Result<&'list mut Post<'list>, String> {
|
) -> std::result::Result<&'p mut Post<'list>, String> {
|
||||||
Ok(post)
|
Ok(post)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,10 +182,10 @@ impl PostFilter for ArchivedAtLink {
|
||||||
///will receive the post in `post.action` field.
|
///will receive the post in `post.action` field.
|
||||||
pub struct FinalizeRecipients;
|
pub struct FinalizeRecipients;
|
||||||
impl PostFilter for FinalizeRecipients {
|
impl PostFilter for FinalizeRecipients {
|
||||||
fn feed<'list>(
|
fn feed<'p, 'list>(
|
||||||
self: Box<Self>,
|
self: Box<Self>,
|
||||||
post: &'list mut Post<'list>,
|
post: &'p mut Post<'list>,
|
||||||
) -> std::result::Result<&'list mut Post<'list>, String> {
|
) -> std::result::Result<&'p mut Post<'list>, String> {
|
||||||
let mut recipients = vec![];
|
let mut recipients = vec![];
|
||||||
let mut digests = vec![];
|
let mut digests = vec![];
|
||||||
let email_from = post.from.get_email();
|
let email_from = post.from.get_email();
|
|
@ -0,0 +1,76 @@
|
||||||
|
table! {
|
||||||
|
list_owner (pk) {
|
||||||
|
pk -> Integer,
|
||||||
|
list -> Integer,
|
||||||
|
address -> Text,
|
||||||
|
name -> Nullable<Text>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
mailing_lists (pk) {
|
||||||
|
pk -> Integer,
|
||||||
|
name -> Text,
|
||||||
|
id -> Text,
|
||||||
|
address -> Text,
|
||||||
|
archive_url -> Nullable<Text>,
|
||||||
|
description -> Nullable<Text>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
membership (list, address) {
|
||||||
|
list -> Integer,
|
||||||
|
address -> Text,
|
||||||
|
name -> Nullable<Text>,
|
||||||
|
digest -> Bool,
|
||||||
|
hide_address -> Bool,
|
||||||
|
receive_duplicates -> Bool,
|
||||||
|
receive_own_posts -> Bool,
|
||||||
|
receive_confirmation -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
post (pk) {
|
||||||
|
pk -> Integer,
|
||||||
|
list -> Integer,
|
||||||
|
address -> Text,
|
||||||
|
message_id -> Text,
|
||||||
|
message -> Binary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
post_event (pk) {
|
||||||
|
pk -> Integer,
|
||||||
|
post -> Integer,
|
||||||
|
date -> Integer,
|
||||||
|
kind -> Text,
|
||||||
|
content -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
post_policy (pk) {
|
||||||
|
pk -> Integer,
|
||||||
|
list -> Integer,
|
||||||
|
announce_only -> Bool,
|
||||||
|
subscriber_only -> Bool,
|
||||||
|
approval_needed -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
joinable!(list_owner -> mailing_lists (list));
|
||||||
|
joinable!(membership -> mailing_lists (list));
|
||||||
|
joinable!(post_event -> post (post));
|
||||||
|
joinable!(post_policy -> mailing_lists (list));
|
||||||
|
|
||||||
|
allow_tables_to_appear_in_same_query!(
|
||||||
|
list_owner,
|
||||||
|
mailing_lists,
|
||||||
|
membership,
|
||||||
|
post,
|
||||||
|
post_event,
|
||||||
|
post_policy,
|
||||||
|
);
|
|
@ -0,0 +1,63 @@
|
||||||
|
PRAGMA foreign_keys = true;
|
||||||
|
PRAGMA encoding = 'UTF-8';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS mailing_lists (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
archive_url TEXT,
|
||||||
|
description TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS list_owner (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
list INTEGER NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
name TEXT,
|
||||||
|
FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS post_policy (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
list INTEGER NOT NULL UNIQUE,
|
||||||
|
announce_only BOOLEAN CHECK (announce_only in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
subscriber_only BOOLEAN CHECK (subscriber_only in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
approval_needed BOOLEAN CHECK (approval_needed in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
CHECK(((approval_needed) OR (((announce_only) OR (subscriber_only)) AND NOT ((announce_only) AND (subscriber_only)))) AND NOT ((approval_needed) AND (((announce_only) OR (subscriber_only)) AND NOT ((announce_only) AND (subscriber_only))))),
|
||||||
|
FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS membership (
|
||||||
|
list INTEGER NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
name TEXT,
|
||||||
|
digest BOOLEAN CHECK (digest in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
hide_address BOOLEAN CHECK (hide_address in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
receive_duplicates BOOLEAN CHECK (receive_duplicates in (0, 1)) NOT NULL DEFAULT 1,
|
||||||
|
receive_own_posts BOOLEAN CHECK (receive_own_posts in (0, 1)) NOT NULL DEFAULT 0,
|
||||||
|
receive_confirmation BOOLEAN CHECK (receive_confirmation in (0, 1)) NOT NULL DEFAULT 1,
|
||||||
|
PRIMARY KEY (list, address),
|
||||||
|
FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS post (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
list INTEGER NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
message_id TEXT NOT NULL,
|
||||||
|
message BLOB NOT NULL,
|
||||||
|
FOREIGN KEY (list, address) REFERENCES membership(list, address) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS post_event (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
post INTEGER NOT NULL,
|
||||||
|
date INTEGER NOT NULL,
|
||||||
|
kind CHAR(1) CHECK (kind IN ('R', 'S', 'D', 'B', 'O')) NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (post) REFERENCES post(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS mailing_lists_idx ON mailing_lists(id);
|
||||||
|
CREATE INDEX IF NOT EXISTS membership_idx ON membership(address);
|
|
@ -0,0 +1,67 @@
|
||||||
|
define(xor, `(($1) OR ($2)) AND NOT (($1) AND ($2))')dnl
|
||||||
|
define(BOOLEAN_TYPE, `$1 BOOLEAN CHECK ($1 in (0, 1)) NOT NULL')dnl
|
||||||
|
define(BOOLEAN_FALSE, `0')dnl
|
||||||
|
define(BOOLEAN_TRUE, `1')dnl
|
||||||
|
PRAGMA foreign_keys = true;
|
||||||
|
PRAGMA encoding = 'UTF-8';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS mailing_lists (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
id TEXT NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
archive_url TEXT,
|
||||||
|
description TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS list_owner (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
list INTEGER NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
name TEXT,
|
||||||
|
FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS post_policy (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
list INTEGER NOT NULL UNIQUE,
|
||||||
|
BOOLEAN_TYPE(announce_only) DEFAULT BOOLEAN_FALSE(),
|
||||||
|
BOOLEAN_TYPE(subscriber_only) DEFAULT BOOLEAN_FALSE(),
|
||||||
|
BOOLEAN_TYPE(approval_needed) DEFAULT BOOLEAN_FALSE(),
|
||||||
|
CHECK(xor(approval_needed, xor(announce_only, subscriber_only))),
|
||||||
|
FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS membership (
|
||||||
|
list INTEGER NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
name TEXT,
|
||||||
|
BOOLEAN_TYPE(digest) DEFAULT BOOLEAN_FALSE(),
|
||||||
|
BOOLEAN_TYPE(hide_address) DEFAULT BOOLEAN_FALSE(),
|
||||||
|
BOOLEAN_TYPE(receive_duplicates) DEFAULT BOOLEAN_TRUE(),
|
||||||
|
BOOLEAN_TYPE(receive_own_posts) DEFAULT BOOLEAN_FALSE(),
|
||||||
|
BOOLEAN_TYPE(receive_confirmation) DEFAULT BOOLEAN_TRUE(),
|
||||||
|
PRIMARY KEY (list, address),
|
||||||
|
FOREIGN KEY (list) REFERENCES mailing_lists(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS post (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
list INTEGER NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
message_id TEXT NOT NULL,
|
||||||
|
message BLOB NOT NULL,
|
||||||
|
FOREIGN KEY (list, address) REFERENCES membership(list, address) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS post_event (
|
||||||
|
pk INTEGER PRIMARY KEY NOT NULL,
|
||||||
|
post INTEGER NOT NULL,
|
||||||
|
date INTEGER NOT NULL,
|
||||||
|
kind CHAR(1) CHECK (kind IN ('R', 'S', 'D', 'B', 'O')) NOT NULL,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
FOREIGN KEY (post) REFERENCES post(pk) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS mailing_lists_idx ON mailing_lists(id);
|
||||||
|
CREATE INDEX IF NOT EXISTS membership_idx ON membership(address);
|
|
@ -0,0 +1,25 @@
|
||||||
|
[package]
|
||||||
|
name = "mailpot-http"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
|
||||||
|
edition = "2018"
|
||||||
|
license = "LICENSE"
|
||||||
|
readme = "README.md"
|
||||||
|
description = "mailing list manager"
|
||||||
|
repository = "https://github.com/meli/mailpot"
|
||||||
|
keywords = ["mail", "mailing-lists" ]
|
||||||
|
categories = ["email"]
|
||||||
|
default-run = "mpot-http"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "mpot-http"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
mailpot = { version = "0.1.0", path = "../core" }
|
||||||
|
rocket = "0.4.5"
|
||||||
|
|
||||||
|
[dependencies.rocket_contrib]
|
||||||
|
version = "0.4.5"
|
||||||
|
default-features = false
|
||||||
|
features = ["json"]
|
|
@ -0,0 +1,7 @@
|
||||||
|
# mailpot REST http server
|
||||||
|
|
||||||
|
Current `rocket` requires Nightly.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo +nightly run --bin mpot-http
|
||||||
|
```
|
|
@ -0,0 +1,93 @@
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![feature(proc_macro_hygiene, decl_macro)]
|
||||||
|
extern crate mailpot;
|
||||||
|
|
||||||
|
pub use mailpot::config::*;
|
||||||
|
pub use mailpot::db::*;
|
||||||
|
pub use mailpot::errors::*;
|
||||||
|
pub use mailpot::models::*;
|
||||||
|
pub use mailpot::post::*;
|
||||||
|
pub use mailpot::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use rocket::response::content;
|
||||||
|
use rocket_contrib::json::Json;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index() -> &'static str {
|
||||||
|
"Hello, world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/lists")]
|
||||||
|
fn lists() -> Json<Vec<MailingList>> {
|
||||||
|
let db = Database::open_or_create_db().unwrap();
|
||||||
|
let lists = db.list_lists().unwrap();
|
||||||
|
Json(lists)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/lists/<num>")]
|
||||||
|
fn lists_num(num: u64) -> Json<Option<MailingList>> {
|
||||||
|
let db = Database::open_or_create_db().unwrap();
|
||||||
|
let list = db.get_list(num as i64).unwrap();
|
||||||
|
Json(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/lists/<num>/members")]
|
||||||
|
fn lists_members(num: u64) -> Option<Json<Vec<ListMembership>>> {
|
||||||
|
let db = Database::open_or_create_db().unwrap();
|
||||||
|
db.list_members(num as i64).ok().map(|l| Json(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/lists/<num>/owners")]
|
||||||
|
fn lists_owners(num: u64) -> Option<Json<Vec<ListOwner>>> {
|
||||||
|
let db = Database::open_or_create_db().unwrap();
|
||||||
|
db.get_list_owners(num as i64).ok().map(|l| Json(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/lists/<num>/owners/add", data = "<new_owner>")]
|
||||||
|
fn lists_owner_add(num: u64, new_owner: ListOwner) -> Result<()> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/lists/<num>/policy")]
|
||||||
|
fn lists_policy(num: u64) -> Option<Json<Option<PostPolicy>>> {
|
||||||
|
let db = Database::open_or_create_db().unwrap();
|
||||||
|
db.get_list_policy(num as i64).ok().map(|l| Json(l))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
rocket::ignite()
|
||||||
|
.mount(
|
||||||
|
"/",
|
||||||
|
routes![
|
||||||
|
index,
|
||||||
|
lists_members,
|
||||||
|
lists_num,
|
||||||
|
lists_owners,
|
||||||
|
lists_policy,
|
||||||
|
lists
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.launch();
|
||||||
|
}
|
220
src/db.rs
220
src/db.rs
|
@ -1,220 +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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
use melib::Envelope;
|
|
||||||
pub use rusqlite::{self, params, Connection};
|
|
||||||
use std::convert::TryFrom;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
const DB_NAME: &str = "mpot.db";
|
|
||||||
const INIT_SCRIPT: &str = "PRAGMA foreign_keys = true;
|
|
||||||
PRAGMA encoding = 'UTF-8';
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS mailing_lists (
|
|
||||||
pk INTEGER PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
id TEXT NOT NULL,
|
|
||||||
address TEXT NOT NULL,
|
|
||||||
archive_url TEXT,
|
|
||||||
description TEXT
|
|
||||||
);
|
|
||||||
CREATE TABLE IF NOT EXISTS membership (
|
|
||||||
list INTEGER NOT NULL,
|
|
||||||
address TEXT NOT NULL,
|
|
||||||
name TEXT,
|
|
||||||
digest BOOLEAN NOT NULL DEFAULT 0,
|
|
||||||
hide_address BOOLEAN NOT NULL DEFAULT 0,
|
|
||||||
receive_duplicates BOOLEAN NOT NULL DEFAULT 1,
|
|
||||||
receive_own_posts BOOLEAN NOT NULL DEFAULT 0,
|
|
||||||
receive_confirmation BOOLEAN NOT NULL DEFAULT 1,
|
|
||||||
PRIMARY KEY (list, address),
|
|
||||||
FOREIGN KEY (list) REFERENCES mailing_lists ON DELETE CASCADE
|
|
||||||
);
|
|
||||||
CREATE INDEX IF NOT EXISTS mailing_lists_idx ON mailing_lists(id);
|
|
||||||
CREATE INDEX IF NOT EXISTS membership_idx ON membership(address);";
|
|
||||||
|
|
||||||
pub struct Database {
|
|
||||||
connection: Connection,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Database {
|
|
||||||
pub fn list_lists(&self) -> Result<Vec<MailingList>> {
|
|
||||||
let mut stmt = self
|
|
||||||
.connection
|
|
||||||
.prepare("SELECT * FROM mailing_lists;")
|
|
||||||
.unwrap();
|
|
||||||
let res = stmt
|
|
||||||
.query_map(params![], |row| MailingList::try_from(row))?
|
|
||||||
.map(|r| r.map_err(|err| err.into()))
|
|
||||||
.collect();
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_members(&self, pk: i64) -> Result<Vec<ListMembership>> {
|
|
||||||
let mut stmt = self
|
|
||||||
.connection
|
|
||||||
.prepare("SELECT * FROM membership WHERE list = ?;")
|
|
||||||
.unwrap();
|
|
||||||
let res = stmt
|
|
||||||
.query_map(params![pk], |row| ListMembership::try_from(row))?
|
|
||||||
.map(|r| r.map_err(|err| err.into()))
|
|
||||||
.collect();
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_member(&self, list_pk: i64, new_val: ListMembership) -> Result<()> {
|
|
||||||
self.connection.execute(
|
|
||||||
"INSERT INTO membership (list, address, name, digest, hide_address) VALUES (?1, ?2, ?3, ?4, ?5);",
|
|
||||||
params![
|
|
||||||
&list_pk,
|
|
||||||
&new_val.address,
|
|
||||||
&new_val.name,
|
|
||||||
&new_val.digest,
|
|
||||||
&new_val.hide_address
|
|
||||||
],
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_member(&self, list_pk: i64, address: &str) -> Result<()> {
|
|
||||||
if self.connection.execute(
|
|
||||||
"DELETE FROM membership WHERE list = ?1 AND address = ?2;",
|
|
||||||
params![&list_pk, &address,],
|
|
||||||
)? == 0
|
|
||||||
{
|
|
||||||
Err(format!("Address {} is not a member of this list.", address))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_list(&self, new_val: MailingList) -> Result<()> {
|
|
||||||
self.connection.execute(
|
|
||||||
"INSERT INTO mailing_lists (name, id, address, description) VALUES (?1, ?2, ?3, ?4)",
|
|
||||||
params![
|
|
||||||
&new_val.name,
|
|
||||||
&new_val.id,
|
|
||||||
&new_val.address,
|
|
||||||
&new_val.description
|
|
||||||
],
|
|
||||||
)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn db_path() -> Result<PathBuf> {
|
|
||||||
let name = DB_NAME;
|
|
||||||
let data_dir = xdg::BaseDirectories::with_prefix("mailpot")?;
|
|
||||||
Ok(data_dir.place_data_file(name)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_db(db_path: PathBuf) -> Result<Self> {
|
|
||||||
if !db_path.exists() {
|
|
||||||
return Err("Database doesn't exist".into());
|
|
||||||
}
|
|
||||||
Ok(Database {
|
|
||||||
connection: Connection::open(&db_path)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_or_create_db() -> Result<Self> {
|
|
||||||
let db_path = Self::db_path()?;
|
|
||||||
let mut set_mode = false;
|
|
||||||
if !db_path.exists() {
|
|
||||||
println!("Creating {} database in {}", DB_NAME, db_path.display());
|
|
||||||
set_mode = true;
|
|
||||||
}
|
|
||||||
let conn = Connection::open(&db_path)?;
|
|
||||||
if set_mode {
|
|
||||||
use std::os::unix::fs::PermissionsExt;
|
|
||||||
let file = std::fs::File::open(&db_path)?;
|
|
||||||
let metadata = file.metadata()?;
|
|
||||||
let mut permissions = metadata.permissions();
|
|
||||||
|
|
||||||
permissions.set_mode(0o600); // Read/write for owner only.
|
|
||||||
file.set_permissions(permissions)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.execute_batch(INIT_SCRIPT)?;
|
|
||||||
|
|
||||||
Ok(Database { connection: conn })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_list_filters(&self, _list: &MailingList) -> Vec<Box<dyn crate::post::PostFilter>> {
|
|
||||||
use crate::post::*;
|
|
||||||
vec![
|
|
||||||
Box::new(FixCRLF),
|
|
||||||
Box::new(PostRightsCheck),
|
|
||||||
Box::new(AddListHeaders),
|
|
||||||
Box::new(FinalizeRecipients),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn post(&self, env: Envelope, raw: &[u8]) -> Result<()> {
|
|
||||||
let mut lists = self.list_lists()?;
|
|
||||||
let tos = env
|
|
||||||
.to()
|
|
||||||
.iter()
|
|
||||||
.map(|addr| addr.get_email())
|
|
||||||
.collect::<Vec<String>>();
|
|
||||||
if tos.is_empty() {
|
|
||||||
return Err("Envelope To: field is empty!".into());
|
|
||||||
}
|
|
||||||
if env.from().is_empty() {
|
|
||||||
return Err("Envelope From: field is empty!".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
lists.retain(|list| tos.iter().any(|a| a == &list.address));
|
|
||||||
if lists.is_empty() {
|
|
||||||
return Err("Envelope To: field doesn't contain any known lists!".into());
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::post::{Post, PostAction};
|
|
||||||
for mut list in lists {
|
|
||||||
let filters = self.get_list_filters(&list);
|
|
||||||
let memberships = self.list_members(list.pk)?;
|
|
||||||
let mut post = Post {
|
|
||||||
list: &mut list,
|
|
||||||
from: env.from()[0].clone(),
|
|
||||||
memberships: &memberships,
|
|
||||||
bytes: raw.to_vec(),
|
|
||||||
to: env.to().to_vec(),
|
|
||||||
action: PostAction::Defer {
|
|
||||||
reason: "Default action.".into(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let result = {
|
|
||||||
let result: std::result::Result<_, String> = filters
|
|
||||||
.into_iter()
|
|
||||||
.fold(Ok(&mut post), |post, f| post.and_then(|p| f.feed(p)));
|
|
||||||
format!("{:#?}", result)
|
|
||||||
};
|
|
||||||
eprintln!("result for list {} is {}", list, result);
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
|
|
||||||
use melib::futures;
|
|
||||||
use melib::smol;
|
|
||||||
use melib::smtp::*;
|
|
||||||
std::thread::spawn(|| smol::run(futures::future::pending::<()>()));
|
|
||||||
let mut conn = futures::executor::block_on(SmtpConnection::new_connection(conf)).unwrap();
|
|
||||||
futures::executor::block_on(conn.mail_transaction(raw, )).unwrap();
|
|
||||||
*/
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue