Author:
Hash:
Timestamp:
+144 -58 +/-5 browse
Kevin Schoon [me@kevinschoon.com]
7f02c9a31e23d5743577cfa2592e72f8a7ce0cec
Sun, 31 Dec 2023 15:20:06 +0000 (1.8 years ago)
| 1 | diff --git a/ayllu-mail/src/config.rs b/ayllu-mail/src/config.rs |
| 2 | index a6ef261..a324b05 100644 |
| 3 | --- a/ayllu-mail/src/config.rs |
| 4 | +++ b/ayllu-mail/src/config.rs |
| 5 | @@ -21,12 +21,39 @@ impl Database { |
| 6 | } |
| 7 | |
| 8 | #[derive(Deserialize, Serialize, Clone, Debug, Default)] |
| 9 | + pub enum PostPolicy { |
| 10 | + AnnounceOnly, |
| 11 | + SubscriptionOnly, |
| 12 | + ApprovalNeeded, |
| 13 | + Open, |
| 14 | + #[default] |
| 15 | + Custom, |
| 16 | + } |
| 17 | + |
| 18 | + #[derive(Deserialize, Serialize, Clone, Debug, Default, PartialEq, Eq)] |
| 19 | + pub enum SubscriptionPolicyKind { |
| 20 | + Request, |
| 21 | + Open, |
| 22 | + Manual, |
| 23 | + #[default] |
| 24 | + Custom, |
| 25 | + } |
| 26 | + |
| 27 | + #[derive(Deserialize, Serialize, Clone, Debug, Default)] |
| 28 | + pub struct SubscriptionPolicy { |
| 29 | + pub send_confirmation: bool, |
| 30 | + pub kind: SubscriptionPolicyKind, |
| 31 | + } |
| 32 | + |
| 33 | + #[derive(Deserialize, Serialize, Clone, Debug, Default)] |
| 34 | pub struct MailingList { |
| 35 | pub id: String, |
| 36 | pub name: Option<String>, |
| 37 | pub address: String, |
| 38 | pub description: Option<String>, |
| 39 | pub topics: Vec<String>, |
| 40 | + pub post_policy: PostPolicy, |
| 41 | + pub subscription_policy: SubscriptionPolicy, |
| 42 | } |
| 43 | |
| 44 | #[derive(Serialize, Deserialize, Clone)] |
| 45 | diff --git a/ayllu-mail/src/declarative.rs b/ayllu-mail/src/declarative.rs |
| 46 | new file mode 100644 |
| 47 | index 0000000..0708e12 |
| 48 | --- /dev/null |
| 49 | +++ b/ayllu-mail/src/declarative.rs |
| 50 | @@ -0,0 +1,104 @@ |
| 51 | + use std::collections::HashMap; |
| 52 | + |
| 53 | + use mailpot::{ |
| 54 | + models::{MailingList, PostPolicy, SubscriptionPolicy}, |
| 55 | + Connection, Result as MpResult, |
| 56 | + }; |
| 57 | + use tracing::log; |
| 58 | + |
| 59 | + use crate::{config, config::Config}; |
| 60 | + |
| 61 | + /// ensure the mailing lists in the global config are available |
| 62 | + pub fn initialize(cfg: &Config) -> MpResult<()> { |
| 63 | + let db = Connection::open_or_create_db(cfg.mailpot_config())?.trusted(); |
| 64 | + |
| 65 | + let mut lists_by_name: HashMap<String, i64> = HashMap::new(); |
| 66 | + let mut managed_lists: Vec<String> = Vec::new(); |
| 67 | + |
| 68 | + for list in db.lists()? { |
| 69 | + lists_by_name.insert(list.name.clone(), list.pk); |
| 70 | + } |
| 71 | + |
| 72 | + // create missing lists |
| 73 | + |
| 74 | + for list in cfg.mail.lists.iter() { |
| 75 | + log::info!("configuring mailing list: {} [{}]", list.id, list.address); |
| 76 | + // TODO: confused on distinciton between id and name |
| 77 | + if !lists_by_name.contains_key(&list.id) { |
| 78 | + log::info!("creating mailing list: {}", list.address); |
| 79 | + let db_list = db.create_list(MailingList { |
| 80 | + pk: -1, |
| 81 | + name: list.name.clone().unwrap_or(list.id.clone()), |
| 82 | + id: list.id.clone(), |
| 83 | + address: list.address.clone(), |
| 84 | + topics: list.topics.clone(), |
| 85 | + description: list.description.clone(), |
| 86 | + archive_url: None, |
| 87 | + })?; |
| 88 | + lists_by_name.insert(db_list.id.clone(), db_list.pk); |
| 89 | + } |
| 90 | + managed_lists.push(list.id.clone()); |
| 91 | + } |
| 92 | + |
| 93 | + // set policies |
| 94 | + |
| 95 | + for list in cfg.mail.lists.iter() { |
| 96 | + let list_pk = *lists_by_name.get(&list.id).unwrap(); |
| 97 | + let post_policy = PostPolicy { |
| 98 | + pk: 0, // TODO ? |
| 99 | + list: list_pk, |
| 100 | + announce_only: matches!(list.post_policy, config::PostPolicy::AnnounceOnly), |
| 101 | + subscription_only: matches!(list.post_policy, config::PostPolicy::SubscriptionOnly), |
| 102 | + approval_needed: matches!(list.post_policy, config::PostPolicy::ApprovalNeeded), |
| 103 | + open: matches!(list.post_policy, config::PostPolicy::Open), |
| 104 | + custom: matches!(list.post_policy, config::PostPolicy::Custom), |
| 105 | + }; |
| 106 | + log::info!("setting post policy: {:?}", list.post_policy); |
| 107 | + db.set_list_post_policy(post_policy)?; |
| 108 | + |
| 109 | + let sub_policy = SubscriptionPolicy { |
| 110 | + pk: 0, |
| 111 | + list: list_pk, |
| 112 | + send_confirmation: list.subscription_policy.send_confirmation, |
| 113 | + open: matches!( |
| 114 | + list.subscription_policy.kind, |
| 115 | + config::SubscriptionPolicyKind::Open |
| 116 | + ), |
| 117 | + manual: matches!( |
| 118 | + list.subscription_policy.kind, |
| 119 | + config::SubscriptionPolicyKind::Manual |
| 120 | + ), |
| 121 | + request: matches!( |
| 122 | + list.subscription_policy.kind, |
| 123 | + config::SubscriptionPolicyKind::Request |
| 124 | + ), |
| 125 | + custom: matches!( |
| 126 | + list.subscription_policy.kind, |
| 127 | + config::SubscriptionPolicyKind::Custom |
| 128 | + ), |
| 129 | + }; |
| 130 | + |
| 131 | + log::info!( |
| 132 | + "setting subscription policy: {:?}", |
| 133 | + list.subscription_policy |
| 134 | + ); |
| 135 | + |
| 136 | + db.set_list_subscription_policy(sub_policy)?; |
| 137 | + } |
| 138 | + |
| 139 | + let extras: Vec<&String> = lists_by_name |
| 140 | + .keys() |
| 141 | + .into_iter() |
| 142 | + .filter(|name| managed_lists.contains(name) == false) |
| 143 | + .collect(); |
| 144 | + |
| 145 | + if extras.len() > 0 { |
| 146 | + // TODO: there is no way to delete lists currently |
| 147 | + log::info!("database contains the following superfluous lists:"); |
| 148 | + extras.iter().for_each(|extra| { |
| 149 | + log::info!("List: {}", extra); |
| 150 | + }); |
| 151 | + } |
| 152 | + |
| 153 | + Ok(()) |
| 154 | + } |
| 155 | diff --git a/ayllu-mail/src/main.rs b/ayllu-mail/src/main.rs |
| 156 | index 0a33215..d0a90bc 100644 |
| 157 | --- a/ayllu-mail/src/main.rs |
| 158 | +++ b/ayllu-mail/src/main.rs |
| 159 | @@ -1,45 +1,12 @@ |
| 160 | use std::path::Path; |
| 161 | |
| 162 | use clap::{arg, value_parser, Command}; |
| 163 | - use mailpot::{models::MailingList, Connection, Result as MpResult}; |
| 164 | use tokio::{runtime::Builder as TokioBuilder, task::LocalSet}; |
| 165 | |
| 166 | - use tracing::log; |
| 167 | - |
| 168 | - |
| 169 | mod config; |
| 170 | + mod declarative; |
| 171 | mod server; |
| 172 | |
| 173 | - /// ensure the mailing lists in the global config are available |
| 174 | - pub fn initialize(cfg: &config::Config) -> MpResult<()> { |
| 175 | - let db = Connection::open_or_create_db(cfg.mailpot_config())?.trusted(); |
| 176 | - // initialize any mailing list which hasn't been created |
| 177 | - let existing_lists: Vec<String> = db |
| 178 | - .lists()? |
| 179 | - .iter() |
| 180 | - .map(|list| list.address.clone()) |
| 181 | - .collect(); |
| 182 | - |
| 183 | - for mailing_list in cfg.mail.lists.clone() { |
| 184 | - if !existing_lists |
| 185 | - .iter().any(|address| *address == mailing_list.address) |
| 186 | - { |
| 187 | - log::info!("creating mailing list: {}", mailing_list.address); |
| 188 | - db.create_list(MailingList { |
| 189 | - pk: -1, |
| 190 | - name: mailing_list.name.unwrap_or(mailing_list.id.clone()), |
| 191 | - id: mailing_list.id, |
| 192 | - address: mailing_list.address, |
| 193 | - topics: mailing_list.topics, |
| 194 | - description: mailing_list.description, |
| 195 | - archive_url: None, |
| 196 | - })?; |
| 197 | - } |
| 198 | - } |
| 199 | - |
| 200 | - Ok(()) |
| 201 | - } |
| 202 | - |
| 203 | fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 204 | let command = Command::new("ayllu-mail") |
| 205 | .about("ayllu email service") |
| 206 | @@ -56,12 +23,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 207 | .value_parser(value_parser!(String)), |
| 208 | ); |
| 209 | let matches = command.get_matches(); |
| 210 | - let config_path = matches |
| 211 | - .get_one::<String>("config") |
| 212 | - .map(Path::new); |
| 213 | + let config_path = matches.get_one::<String>("config").map(Path::new); |
| 214 | let ayllu_config = config::load(config_path)?; |
| 215 | let log_level = matches |
| 216 | - .get_one::<String>("level").cloned() |
| 217 | + .get_one::<String>("level") |
| 218 | + .cloned() |
| 219 | .unwrap_or(ayllu_config.log_level.clone()); |
| 220 | tracing_subscriber::fmt() |
| 221 | .compact() |
| 222 | @@ -74,7 +40,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 223 | )) |
| 224 | .init(); |
| 225 | tracing::info!("logger initialized"); |
| 226 | - initialize(&ayllu_config)?; |
| 227 | + declarative::initialize(&ayllu_config)?; |
| 228 | TokioBuilder::new_current_thread() |
| 229 | .enable_all() |
| 230 | .build() |
| 231 | diff --git a/ayllu-mail/src/server.rs b/ayllu-mail/src/server.rs |
| 232 | index c98cf03..f21b111 100644 |
| 233 | --- a/ayllu-mail/src/server.rs |
| 234 | +++ b/ayllu-mail/src/server.rs |
| 235 | @@ -2,7 +2,7 @@ use std::collections::HashMap; |
| 236 | use std::error::Error as StdError; |
| 237 | use std::sync::{Arc, RwLock}; |
| 238 | |
| 239 | - use capnp::capability::Promise; |
| 240 | + use capnp::{capability::Promise, Error as CapnpError}; |
| 241 | use capnp_rpc::pry; |
| 242 | use mailpot::{models::Post, Connection}; |
| 243 | use melib::{thread::Threads, Envelope, EnvelopeHash}; |
| 244 | @@ -10,8 +10,7 @@ use tracing::log; |
| 245 | |
| 246 | use crate::config::Config; |
| 247 | use ayllu_api::mail_capnp::server::{ |
| 248 | - Client, ListThreadsParams, ListThreadsResults, PostMessageParams, PostMessageResults, |
| 249 | - ReadThreadParams, ReadThreadResults, Server, |
| 250 | + Client, ListThreadsParams, ListThreadsResults, ReadThreadParams, ReadThreadResults, Server, |
| 251 | }; |
| 252 | use ayllu_rpc::Server as RpcHelper; |
| 253 | |
| 254 | @@ -24,7 +23,7 @@ impl Server for ServerImpl { |
| 255 | &mut self, |
| 256 | params: ListThreadsParams, |
| 257 | mut results: ListThreadsResults, |
| 258 | - ) -> ::capnp::capability::Promise<(), ::capnp::Error> { |
| 259 | + ) -> Promise<(), CapnpError> { |
| 260 | let mailing_list_name = pry!(pry!(pry!(params.get()).get_name()).to_string()); |
| 261 | log::info!("looking up threads: {}", mailing_list_name); |
| 262 | for list in self.db.lists().unwrap() { |
| 263 | @@ -71,23 +70,15 @@ impl Server for ServerImpl { |
| 264 | |
| 265 | fn read_thread( |
| 266 | &mut self, |
| 267 | - _: ReadThreadParams, |
| 268 | - _: ReadThreadResults, |
| 269 | - ) -> ::capnp::capability::Promise<(), ::capnp::Error> { |
| 270 | + params: ReadThreadParams, |
| 271 | + mut results: ReadThreadResults, |
| 272 | + ) -> Promise<(), CapnpError> { |
| 273 | + let params = pry!(params.get()); |
| 274 | + let (thread_id, offset, limit) = (params.get_id(), params.get_offset(), params.get_limit()); |
| 275 | ::capnp::capability::Promise::err(::capnp::Error::unimplemented( |
| 276 | "method server::Server::read_thread not implemented".to_string(), |
| 277 | )) |
| 278 | } |
| 279 | - |
| 280 | - fn post_message( |
| 281 | - &mut self, |
| 282 | - _: PostMessageParams, |
| 283 | - _: PostMessageResults, |
| 284 | - ) -> ::capnp::capability::Promise<(), ::capnp::Error> { |
| 285 | - ::capnp::capability::Promise::err(::capnp::Error::unimplemented( |
| 286 | - "method server::Server::post_message not implemented".to_string(), |
| 287 | - )) |
| 288 | - } |
| 289 | } |
| 290 | |
| 291 | pub async fn serve(cfg: &Config) -> Result<(), Box<dyn StdError>> { |
| 292 | diff --git a/crates/api/v1/mail.capnp b/crates/api/v1/mail.capnp |
| 293 | index b87cd64..797f45d 100644 |
| 294 | --- a/crates/api/v1/mail.capnp |
| 295 | +++ b/crates/api/v1/mail.capnp |
| 296 | @@ -23,6 +23,4 @@ interface Server { |
| 297 | listThreads @0 (name: Text, offset: Int64, limit: Int64) -> (threads: List(Thread)); |
| 298 | # list all of the responses associated with a post |
| 299 | readThread @1 (id: Int64, offset: Int64, limit: Int64) -> (thread: List(Message)); |
| 300 | - # post a new message into mailpot |
| 301 | - postMessage @2 (message: Data) -> (id: Int64); |
| 302 | } |