diff options
| author | Mroik <mroik@delayed.space> | 2026-04-01 00:49:31 +0200 |
|---|---|---|
| committer | Mroik <mroik@delayed.space> | 2026-04-13 06:55:04 +0200 |
| commit | 7294e5944c2e5620c47d1ab014e217c5ee05b3a6 (patch) | |
| tree | 7cff945aef488565a18d7129e3af278a9332b596 /src/list.rs | |
| parent | 8f8fd10dc2b185ca0a8e8908229c4d4bbefd70b7 (diff) | |
Implement User model
The mailing list will need to save the data of the subscribers for them
to receive the emails.
Add User model with its DB interactions.
Signed-off-by: Mroik <mroik@delayed.space>
Diffstat (limited to 'src/list.rs')
| -rw-r--r-- | src/list.rs | 230 |
1 files changed, 230 insertions, 0 deletions
diff --git a/src/list.rs b/src/list.rs new file mode 100644 index 0000000..36cd492 --- /dev/null +++ b/src/list.rs @@ -0,0 +1,230 @@ +use anyhow::Result; +use rusqlite::fallible_iterator::FallibleIterator; + +use crate::database::{DBExecutable, QueryResult}; + +#[derive(PartialEq, Debug)] +struct User { + name: Option<String>, + email: String, +} + +impl User { + fn new(email: &str) -> Self { + Self { + name: None, + email: String::from(email), + } + } + + fn insert(&self) -> UserQuery<'_> { + let name = self.name.as_deref(); + UserQuery::Insert(name, self.email.as_str()) + } + + fn delete(&self) -> UserQuery<'_> { + UserQuery::Delete(&self.email) + } + + fn query_by_email(&self) -> UserQuery<'_> { + UserQuery::QueryByEmail(&self.email) + } + + fn query_all<'a>() -> UserQuery<'a> { + UserQuery::QueryAll + } +} + +enum UserQuery<'a> { + Insert(Option<&'a str>, &'a str), + Delete(&'a str), + QueryByEmail(&'a str), + QueryAll, +} + +impl DBExecutable for UserQuery<'_> { + type T = User; + fn execute(&self, tx: &rusqlite::Transaction) -> Result<QueryResult<Self::T>> { + match self { + UserQuery::Insert(_, _) => self.db_insert(tx), + UserQuery::Delete(_) => self.db_delete(tx), + UserQuery::QueryByEmail(_) => self.db_query_by_email(tx), + UserQuery::QueryAll => self.db_query_all(tx), + } + } +} + +impl UserQuery<'_> { + fn db_insert(&self, tx: &rusqlite::Transaction<'_>) -> Result<QueryResult<User>> { + let (name, email) = if let UserQuery::Insert(name, email) = self { + (name, email) + } else { + unreachable!("this should only be called by a UserQuery::Insert") + }; + + if let Some(name) = name { + let q = "INSERT INTO user (name, email) VALUES (?, ?)"; + tx.execute(q, [name, email])?; + return Ok(QueryResult::Empty); + } + + let q = "INSERT INTO user (email) VALUES (?)"; + tx.execute(q, [email])?; + Ok(QueryResult::Empty) + } + + fn db_delete(&self, tx: &rusqlite::Transaction<'_>) -> Result<QueryResult<User>> { + let email = if let UserQuery::Delete(email) = self { + email + } else { + unreachable!("this should only be called by a UserQuery::Delete") + }; + + let q = "DELETE FROM user WHERE email = ?"; + tx.execute(q, [email])?; + Ok(QueryResult::Empty) + } + + fn db_query_by_email(&self, tx: &rusqlite::Transaction<'_>) -> Result<QueryResult<User>> { + let email = if let UserQuery::QueryByEmail(email) = self { + email + } else { + unreachable!("this should only be called by a UserQuery::QueryByEmail") + }; + + let q = "SELECT * FROM user WHERE email LIKE ?"; + let mut stmt = tx.prepare(q)?; + let ris = stmt + .query([email])? + .map(|row| { + Ok(User { + name: row.get(1).unwrap(), + email: row.get(2).unwrap(), + }) + }) + .collect()?; + Ok(QueryResult::Vec(ris)) + } + + fn db_query_all(&self, tx: &rusqlite::Transaction<'_>) -> Result<QueryResult<User>> { + let q = "SELECT * FROM user"; + let mut stmt = tx.prepare(q)?; + let ris = stmt + .query(())? + .map(|row| { + Ok(User { + name: row.get(1).unwrap(), + email: row.get(2).unwrap(), + }) + }) + .collect()?; + Ok(QueryResult::Vec(ris)) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use crate::{ + database::{DB_NAME, Database}, + list::User, + }; + + fn setup() -> Result<Database> { + std::fs::create_dir("test")?; + Database::new(&format!("test/{}", DB_NAME)) + } + + fn cleanup() -> Result<()> { + std::fs::remove_dir_all("test")?; + Ok(()) + } + + #[test] + fn insert_wo_name() { + let mut database = setup().expect("Failed setup"); + + let user = User { + name: None, + email: String::from("mroik@poul.org"), + }; + database.execute(user.insert()).expect("Failed insert"); + let user2 = database + .execute(user.query_by_email()) + .expect("Failed query"); + match user2 { + crate::database::QueryResult::Empty => assert!(false), + crate::database::QueryResult::Vec(items) => { + assert_eq!(items.len(), 1); + assert_eq!(items[0], user); + } + } + + drop(database); + cleanup().expect("Failed cleanup"); + } + + #[test] + fn insert_with_name() { + let mut database = setup().expect("Failed setup"); + + let user = User { + name: Some(String::from("Mirko Faina")), + email: String::from("mroik@poul.org"), + }; + database.execute(user.insert()).expect("Failed insert"); + let user2 = database + .execute(user.query_by_email()) + .expect("Failed query"); + match &user2 { + crate::database::QueryResult::Empty => assert!(false), + crate::database::QueryResult::Vec(items) => { + assert_eq!(items.len(), 1); + assert_eq!(items[0], user); + } + } + + drop(database); + cleanup().expect("Failed cleanup"); + } + + #[test] + fn insert_twice() { + let mut database = setup().expect("Failed setup"); + + let user = User { + name: Some(String::from("Mirko Faina")), + email: String::from("mroik@poul.org"), + }; + database.execute(user.insert()).expect("Failed insert"); + let ris = database.execute(user.insert()); + assert!(ris.is_err()); + + drop(database); + cleanup().expect("Failed cleanup"); + } + + #[test] + fn delete() { + let mut database = setup().expect("Failed setup"); + + let user = User { + name: Some(String::from("Mirko Faina")), + email: String::from("mroik@poul.org"), + }; + database.execute(user.insert()).expect("Failed insert"); + database.execute(user.delete()).expect("Failed delete"); + + let ris = database + .execute(User::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"); + } +} |
