diff --git a/crates/core/src/database/auth.rs b/crates/core/src/database/auth.rs index 53c05d1..bd58f91 100644 --- a/crates/core/src/database/auth.rs +++ b/crates/core/src/database/auth.rs @@ -4,8 +4,7 @@ use crate::model::{ auth::{Token, User}, permissions::FinePermission, }; -use crate::{execute, get, query_row}; - +use crate::{auto_method, execute, get, query_row}; use tetratto_shared::hash::hash_salted; #[cfg(feature = "sqlite")] @@ -32,50 +31,8 @@ impl DataManager { } } - /// Get a user given just their `id`. - /// - /// # Arguments - /// * `id` - the ID of the user - pub async fn get_user_by_id(&self, id: &str) -> Result { - let conn = match self.connect().await { - Ok(c) => c, - Err(e) => return Err(Error::DatabaseConnection(e.to_string())), - }; - - let res = query_row!(&conn, "SELECT * FROM users WHERE id = $1", &[&id], |x| { - Ok(Self::get_user_from_row(x)) - }); - - if res.is_err() { - return Err(Error::UserNotFound); - } - - Ok(res.unwrap()) - } - - /// Get a user given just their `username`. - /// - /// # Arguments - /// * `username` - the username of the user - pub async fn get_user_by_username(&self, username: &str) -> Result { - let conn = match self.connect().await { - Ok(c) => c, - Err(e) => return Err(Error::DatabaseConnection(e.to_string())), - }; - - let res = query_row!( - &conn, - "SELECT * FROM users WHERE username = $1", - &[&username], - |x| Ok(Self::get_user_from_row(x)) - ); - - if res.is_err() { - return Err(Error::UserNotFound); - } - - Ok(res.unwrap()) - } + auto_method!(get_user_by_id(&str)@get_user_from_row -> "SELECT * FROM users WHERE id = $1" --name="user" --returns=User); + auto_method!(get_user_by_username(&str)@get_user_from_row -> "SELECT * FROM users WHERE username = $1" --name="user" --returns=User); /// Get a user given just their auth token. /// @@ -176,27 +133,5 @@ impl DataManager { Ok(()) } - /// Update the tokens of the the specified account (by `id`). - /// - /// # Arguments - /// * `id` - the ID of the user - /// * `tokens` - the **new** tokens vector for the user - pub async fn update_user_tokens(&self, id: usize, tokens: Vec) -> Result<()> { - let conn = match self.connect().await { - Ok(c) => c, - Err(e) => return Err(Error::DatabaseConnection(e.to_string())), - }; - - let res = execute!( - &conn, - "UPDATE users SET tokens = $1 WHERE id = $2", - &[&serde_json::to_string(&tokens).unwrap(), &id.to_string()] - ); - - if let Err(e) = res { - return Err(Error::DatabaseError(e.to_string())); - } - - Ok(()) - } + auto_method!(update_user_tokens(Vec) -> "UPDATE users SET tokens = $1 WHERE id = $2" --serde); } diff --git a/crates/core/src/database/common.rs b/crates/core/src/database/common.rs index 1ff2615..da6ced3 100644 --- a/crates/core/src/database/common.rs +++ b/crates/core/src/database/common.rs @@ -19,3 +19,157 @@ impl DataManager { Ok(()) } } + +#[macro_export] +macro_rules! auto_method { + ($name:ident()@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt) => { + pub async fn $name(&self, id: usize) -> Result<$returns_> { + let conn = match self.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = query_row!(&conn, $query, &[&id], |x| { Ok(Self::$select_fn(x)) }); + + if res.is_err() { + return Err(Error::GeneralNotFound($name_.to_string())); + } + + Ok(res.unwrap()) + } + }; + + ($name:ident($selector_t:ty)@$select_fn:ident -> $query:literal --name=$name_:literal --returns=$returns_:tt) => { + pub async fn $name(&self, selector: $selector_t) -> Result<$returns_> { + let conn = match self.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = query_row!(&conn, $query, &[&selector], |x| { Ok(Self::$select_fn(x)) }); + + if res.is_err() { + return Err(Error::GeneralNotFound($name_.to_string())); + } + + Ok(res.unwrap()) + } + }; + + ($name:ident()@$select_fn:ident:$permission:ident -> $query:literal) => { + pub async fn $name(&self, id: usize, user: User) -> Result<()> { + let page = self.$select_fn(id).await?; + + if user.id != page.owner { + if !user.permissions.check(FinePermission::$permission) { + return Err(Error::NotAllowed); + } + } + + let conn = match self.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = execute!(&conn, $query, &[&id.to_string()]); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + + Ok(()) + } + }; + + ($name:ident($x:ty)@$select_fn:ident:$permission:ident -> $query:literal) => { + pub async fn $name(&self, id: usize, user: User, x: $x) -> Result<()> { + let y = self.$select_fn(id).await?; + + if user.id != y.owner { + if !user.permissions.check(FinePermission::$permission) { + return Err(Error::NotAllowed); + } + } + + let conn = match self.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = execute!(&conn, $query, &[&x, &id.to_string()]); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + + Ok(()) + } + }; + + ($name:ident($x:ty)@$select_fn:ident:$permission:ident -> $query:literal --serde) => { + pub async fn $name(&self, id: usize, user: User, x: $x) -> Result<()> { + let y = self.$select_fn(id).await?; + + if user.id != y.owner { + if !user.permissions.check(FinePermission::$permission) { + return Err(Error::NotAllowed); + } + } + + let conn = match self.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = execute!( + &conn, + $query, + &[&serde_json::to_string(&x).unwrap(), &id.to_string()] + ); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + + Ok(()) + } + }; + + ($name:ident($x:ty) -> $query:literal) => { + pub async fn $name(&self, id: usize, x: $x) -> Result<()> { + let conn = match self.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = execute!(&conn, $query, &[&x, &id.to_string()]); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + + Ok(()) + } + }; + + ($name:ident($x:ty) -> $query:literal --serde) => { + pub async fn $name(&self, id: usize, x: $x) -> Result<()> { + let conn = match self.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = execute!( + &conn, + $query, + &[&serde_json::to_string(&x).unwrap(), &id.to_string()] + ); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + + Ok(()) + } + }; +} diff --git a/crates/core/src/database/pages.rs b/crates/core/src/database/pages.rs index b1961ab..b258d81 100644 --- a/crates/core/src/database/pages.rs +++ b/crates/core/src/database/pages.rs @@ -1,7 +1,7 @@ use super::*; use crate::model::auth::User; use crate::model::{Error, Result, journal::JournalPage, permissions::FinePermission}; -use crate::{execute, get, query_row}; +use crate::{auto_method, execute, get, query_row}; #[cfg(feature = "sqlite")] use rusqlite::Row; @@ -26,26 +26,7 @@ impl DataManager { } } - /// Get a journal page given just its `id`. - /// - /// # Arguments - /// * `id` - the ID of the page - pub async fn get_page_by_id(&self, id: &str) -> Result { - let conn = match self.connect().await { - Ok(c) => c, - Err(e) => return Err(Error::DatabaseConnection(e.to_string())), - }; - - let res = query_row!(&conn, "SELECT * FROM pages WHERE id = $1", &[&id], |x| { - Ok(Self::get_page_from_row(x)) - }); - - if res.is_err() { - return Err(Error::GeneralNotFound("journal page".to_string())); - } - - Ok(res.unwrap()) - } + auto_method!(get_page_by_id()@get_page_from_row -> "SELECT * FROM pages WHERE id = $1" --name="journal page" --returns=JournalPage); /// Create a new journal page in the database. /// @@ -96,31 +77,9 @@ impl DataManager { Ok(()) } - /// Create a new user in the database. - /// - /// # Arguments - /// * `id` - the ID of the page - /// * `user` - the user deleting the page - pub async fn delete_page(&self, id: &str, user: User) -> Result<()> { - let page = self.get_page_by_id(id).await?; - - if user.id != page.owner { - if !user.permissions.check(FinePermission::MANAGE_JOURNAL_PAGES) { - return Err(Error::NotAllowed); - } - } - - let conn = match self.connect().await { - Ok(c) => c, - Err(e) => return Err(Error::DatabaseConnection(e.to_string())), - }; - - let res = execute!(&conn, "DELETE FROM pages WHERE id = $1", &[&id]); - - if let Err(e) = res { - return Err(Error::DatabaseError(e.to_string())); - } - - Ok(()) - } + auto_method!(delete_page()@get_page_by_id:MANAGE_JOURNAL_PAGES -> "DELETE FROM pages WHERE id = $1"); + auto_method!(update_page_title(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET title = $1 WHERE id = $2"); + auto_method!(update_page_prompt(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET prompt = $1 WHERE id = $2"); + auto_method!(update_page_read_access(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET read_access = $1 WHERE id = $2" --serde); + auto_method!(update_page_write_access(String)@get_page_by_id:MANAGE_JOURNAL_PAGES -> "UPDATE pages SET write_access = $1 WHERE id = $2" --serde); } diff --git a/crates/core/src/model/journal.rs b/crates/core/src/model/journal.rs index 7c1393a..480aec8 100644 --- a/crates/core/src/model/journal.rs +++ b/crates/core/src/model/journal.rs @@ -1,4 +1,5 @@ use serde::{Deserialize, Serialize}; +use tetratto_shared::{snow::AlmostSnowflake, unix_epoch_timestamp}; #[derive(Serialize, Deserialize)] pub struct JournalPage { @@ -17,6 +18,24 @@ pub struct JournalPage { pub write_access: JournalPageWriteAccess, } +impl JournalPage { + /// Create a new [`JournalPage`]. + pub fn new(title: String, prompt: String, owner: usize) -> Self { + Self { + id: AlmostSnowflake::new(1234567890) + .to_string() + .parse::() + .unwrap(), + created: unix_epoch_timestamp() as usize, + title, + prompt, + owner, + read_access: JournalPageReadAccess::default(), + write_access: JournalPageWriteAccess::default(), + } + } +} + /// Who can read a [`JournalPage`]. #[derive(Serialize, Deserialize)] pub enum JournalPageReadAccess {