Commit

Author:

Hash:

Timestamp:

+144 -58 +/-5 browse

Kevin Schoon [me@kevinschoon.com]

7f02c9a31e23d5743577cfa2592e72f8a7ce0cec

Sun, 31 Dec 2023 15:20:06 +0000 (1.5 years ago)

make ayllu-mail more declarative
1diff --git a/ayllu-mail/src/config.rs b/ayllu-mail/src/config.rs
2index 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
46new file mode 100644
47index 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
156index 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
232index 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
293index 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 }