From 590ffd8dc5c34a88951c6c92e1a806caa8a785d8 Mon Sep 17 00:00:00 2001 From: Mroik Date: Thu, 2 Apr 2026 02:28:52 +0200 Subject: Add List model We ideally want to be able to handle multiple mailing lists without having to run a new instance for each one. To do this we need to be able to create new lists. Add List model with its DB interactions. Signed-off-by: Mroik --- src/list.rs | 205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 200 insertions(+), 5 deletions(-) diff --git a/src/list.rs b/src/list.rs index 36cd492..a015da5 100644 --- a/src/list.rs +++ b/src/list.rs @@ -122,13 +122,127 @@ impl UserQuery<'_> { } } +#[derive(Debug, PartialEq)] +struct List { + address: String, + desc: Option, +} + +impl List { + fn new(name: &str) -> Self { + List { + address: String::from(name), + desc: None, + } + } + + fn insert(&self) -> ListQuery<'_> { + ListQuery::Insert(&self.address, self.desc.as_deref()) + } + + fn delete(&self) -> ListQuery<'_> { + ListQuery::Delete(&self.address) + } + + fn query_all<'a>() -> ListQuery<'a> { + ListQuery::QueryAll + } +} + +enum ListQuery<'a> { + Insert(&'a str, Option<&'a str>), + Delete(&'a str), + QueryByAddress(&'a str), + QueryAll, +} + +impl DBExecutable for ListQuery<'_> { + type T = List; + + fn execute(&self, tx: &rusqlite::Transaction) -> Result> { + match self { + ListQuery::Insert(_, _) => self.db_insert(tx), + ListQuery::Delete(_) => self.db_delete(tx), + ListQuery::QueryByAddress(_) => self.db_query_by_address(tx), + ListQuery::QueryAll => self.db_query_all(tx), + } + } +} + +impl ListQuery<'_> { + fn db_insert(&self, tx: &rusqlite::Transaction) -> Result> { + let (name, desc) = if let ListQuery::Insert(name, desc) = self { + (name, desc) + } else { + unreachable!("this should only be called by ListQuery::Insert"); + }; + + if let Some(desc) = desc { + let q = "INSERT INTO list (name, description) VALUES (?, ?)"; + tx.execute(q, [name, desc])?; + } else { + let q = "INSERT INTO list (name) VALUES (?)"; + tx.execute(q, [name])?; + } + Ok(QueryResult::Empty) + } + + fn db_delete(&self, tx: &rusqlite::Transaction) -> Result> { + let name = if let ListQuery::Delete(name) = self { + name + } else { + unreachable!("this should only be called by ListQuery::Delete") + }; + + let q = "DELETE FROM list WHERE name = ?"; + tx.execute(q, [name])?; + Ok(QueryResult::Empty) + } + + fn db_query_by_address(&self, tx: &rusqlite::Transaction) -> Result> { + let name = if let ListQuery::QueryByAddress(name) = self { + name + } else { + unreachable!("this should only be called by ListQuery::QueryByName") + }; + + let q = "SELECT * FROM list WHERE name = ?"; + let mut stmt = tx.prepare(q)?; + let ris = stmt + .query([name])? + .map(|row| { + Ok(List { + address: row.get(1).unwrap(), + desc: row.get(2).unwrap(), + }) + }) + .collect()?; + Ok(QueryResult::Vec(ris)) + } + + fn db_query_all(&self, tx: &rusqlite::Transaction) -> Result> { + let q = "SELECT * FROM list"; + let mut stmt = tx.prepare(q)?; + let ris = stmt + .query(())? + .map(|row| { + Ok(List { + address: row.get(1).unwrap(), + desc: row.get(2).unwrap(), + }) + }) + .collect()?; + Ok(QueryResult::Vec(ris)) + } +} + #[cfg(test)] mod tests { use anyhow::Result; use crate::{ database::{DB_NAME, Database}, - list::User, + list::{List, User}, }; fn setup() -> Result { @@ -142,7 +256,7 @@ mod tests { } #[test] - fn insert_wo_name() { + fn user_insert_wo_name() { let mut database = setup().expect("Failed setup"); let user = User { @@ -166,7 +280,7 @@ mod tests { } #[test] - fn insert_with_name() { + fn user_insert_with_name() { let mut database = setup().expect("Failed setup"); let user = User { @@ -190,7 +304,7 @@ mod tests { } #[test] - fn insert_twice() { + fn user_insert_twice() { let mut database = setup().expect("Failed setup"); let user = User { @@ -206,7 +320,7 @@ mod tests { } #[test] - fn delete() { + fn user_delete() { let mut database = setup().expect("Failed setup"); let user = User { @@ -227,4 +341,85 @@ mod tests { drop(database); cleanup().expect("Failed cleanup"); } + + #[test] + fn list_insert_wo_desc() { + let mut database = setup().expect("failed setup"); + + let list = List { + address: String::from("poul"), + desc: None, + }; + database.execute(list.insert()).expect("failed insert"); + let ris = database.execute(List::query_all()).expect("failed query"); + match ris { + crate::database::QueryResult::Empty => assert!(false), + crate::database::QueryResult::Vec(items) => { + assert_eq!(items.len(), 1); + assert_eq!(items[0], list); + } + } + + cleanup().expect("failed cleanup"); + } + + #[test] + fn list_insert_with_desc() { + let mut database = setup().expect("failed setup"); + + let list = List { + address: String::from("poul"), + desc: Some(String::from("The mailing list of the POuL")), + }; + database.execute(list.insert()).expect("failed insert"); + let ris = database.execute(List::query_all()).expect("failed query"); + match ris { + crate::database::QueryResult::Empty => assert!(false), + crate::database::QueryResult::Vec(items) => { + assert_eq!(items.len(), 1); + assert_eq!(items[0], list); + } + } + + drop(database); + cleanup().expect("failed cleanup"); + } + + #[test] + fn list_insert_twice() { + let mut database = setup().expect("failed setup"); + + let list = List { + address: String::from("poul"), + desc: Some(String::from("The mailing list of the POuL")), + }; + database.execute(list.insert()).expect("failed insert"); + assert!(database.execute(list.insert()).is_err()); + + drop(database); + cleanup().expect("failed cleanup"); + } + + #[test] + fn list_delete() { + let mut database = setup().expect("failed setup"); + + let list = List { + address: String::from("poul"), + desc: Some(String::from("The mailing list of the POuL")), + }; + database.execute(list.insert()).expect("failed insert"); + database.execute(list.delete()).expect("failed delete"); + + let ris = database + .execute(List::query_all()) + .expect("Failed query all"); + match ris { + crate::database::QueryResult::Empty => assert!(false), + crate::database::QueryResult::Vec(items) => assert!(items.is_empty()), + } + + drop(database); + cleanup().expect("failed cleanup"); + } } -- cgit v1.3