use oiseau::cache::Cache; use crate::model::stacks::StackPrivacy; use crate::model::{Error, Result, auth::User, stacks::StackBlock, permissions::FinePermission}; use crate::{auto_method, DataManager}; use oiseau::PostgresRow; use oiseau::{execute, get, params, query_row, query_rows}; impl DataManager { /// Get a [`StackBlock`] from an SQL row. pub(crate) fn get_stackblock_from_row(x: &PostgresRow) -> StackBlock { StackBlock { id: get!(x->0(i64)) as usize, created: get!(x->1(i64)) as usize, initiator: get!(x->2(i64)) as usize, stack: get!(x->3(i64)) as usize, } } auto_method!(get_stackblock_by_id()@get_stackblock_from_row -> "SELECT * FROM stackblocks WHERE id = $1" --name="stack block" --returns=StackBlock --cache-key-tmpl="atto.stackblock:{}"); pub async fn get_user_stack_blocked_users(&self, user_id: usize) -> Vec { let mut stack_block_users = Vec::new(); for block in self.get_stackblocks_by_initiator(user_id).await { for user in match self.fill_stackblocks_receivers(block.stack).await { Ok(ul) => ul, Err(_) => continue, } { stack_block_users.push(user); } } stack_block_users } /// Fill a vector of stack blocks with their receivers (by pulling the stack). pub async fn fill_stackblocks_receivers(&self, stack: usize) -> Result> { let stack = self.get_stack_by_id(stack).await?; let mut out = Vec::new(); for block in stack.users { out.push(block); } Ok(out) } /// Get all stack blocks created by the given `initiator`. pub async fn get_stackblocks_by_initiator(&self, initiator: usize) -> Vec { let conn = match self.0.connect().await { Ok(c) => c, Err(_) => return Vec::new(), }; let res = query_rows!( &conn, "SELECT * FROM stackblocks WHERE initiator = $1", &[&(initiator as i64)], |x| { Self::get_stackblock_from_row(x) } ); if res.is_err() { return Vec::new(); } // make sure all stacks still exist let list = res.unwrap(); for block in &list { if self.get_stack_by_id(block.stack).await.is_err() { if self.delete_stackblock_sudo(block.id).await.is_err() { continue; } } } // return list } /// Get a stack block by `initiator` and `stack` (in that order). pub async fn get_stackblock_by_initiator_stack( &self, initiator: usize, stack: usize, ) -> Result { let conn = match self.0.connect().await { Ok(c) => c, Err(e) => return Err(Error::DatabaseConnection(e.to_string())), }; let res = query_row!( &conn, "SELECT * FROM stackblocks WHERE initiator = $1 AND stack = $2", &[&(initiator as i64), &(stack as i64)], |x| { Ok(Self::get_stackblock_from_row(x)) } ); if res.is_err() { return Err(Error::GeneralNotFound("stack block".to_string())); } Ok(res.unwrap()) } const MAXIMUM_FREE_STACKBLOCKS: usize = 5; const MAXIMUM_SUPPORTER_STACKBLOCKS: usize = 10; /// Create a new stack block in the database. /// /// # Arguments /// * `data` - a mock [`StackBlock`] object to insert pub async fn create_stackblock(&self, data: StackBlock) -> Result<()> { let initiator = self.get_user_by_id(data.initiator).await?; // check number of stackblocks let stackblocks = self.get_stackblocks_by_initiator(data.initiator).await; if !initiator.permissions.check(FinePermission::SUPPORTER) { if stackblocks.len() >= Self::MAXIMUM_FREE_STACKBLOCKS { return Err(Error::MiscError( "You already have the maximum number of stack blocks you can have".to_string(), )); } } else { if stackblocks.len() >= Self::MAXIMUM_SUPPORTER_STACKBLOCKS { return Err(Error::MiscError( "You already have the maximum number of stack blocks you can have".to_string(), )); } } // ... let stack = self.get_stack_by_id(data.stack).await?; if initiator.id != stack.owner && stack.privacy == StackPrivacy::Private && !initiator.permissions.check(FinePermission::MANAGE_STACKS) { return Err(Error::NotAllowed); } // ... let conn = match self.0.connect().await { Ok(c) => c, Err(e) => return Err(Error::DatabaseConnection(e.to_string())), }; let res = execute!( &conn, "INSERT INTO stackblocks VALUES ($1, $2, $3, $4)", params![ &(data.id as i64), &(data.created as i64), &(data.initiator as i64), &(data.stack as i64) ] ); if let Err(e) = res { return Err(Error::DatabaseError(e.to_string())); } // unfollow/remove follower for user in stack.users { if let Ok(f) = self .get_userfollow_by_initiator_receiver(data.initiator, user) .await { self.delete_userfollow_sudo(f.id, data.initiator).await?; } if let Ok(f) = self .get_userfollow_by_receiver_initiator(data.initiator, user) .await { self.delete_userfollow_sudo(f.id, data.initiator).await?; } } // return Ok(()) } pub async fn delete_stackblock(&self, id: usize, user: User) -> Result<()> { let block = self.get_stackblock_by_id(id).await?; if user.id != block.initiator { // only the initiator (or moderators) can delete stack blocks! if !user.permissions.check(FinePermission::MANAGE_FOLLOWS) { return Err(Error::NotAllowed); } } let conn = match self.0.connect().await { Ok(c) => c, Err(e) => return Err(Error::DatabaseConnection(e.to_string())), }; let res = execute!( &conn, "DELETE FROM stackblocks WHERE id = $1", &[&(id as i64)] ); if let Err(e) = res { return Err(Error::DatabaseError(e.to_string())); } self.0.1.remove(format!("atto.stackblock:{}", id)).await; // return Ok(()) } pub async fn delete_stackblock_sudo(&self, id: usize) -> Result<()> { let conn = match self.0.connect().await { Ok(c) => c, Err(e) => return Err(Error::DatabaseConnection(e.to_string())), }; let res = execute!( &conn, "DELETE FROM stackblocks WHERE id = $1", &[&(id as i64)] ); if let Err(e) = res { return Err(Error::DatabaseError(e.to_string())); } self.0.1.remove(format!("atto.stackblock:{}", id)).await; // return Ok(()) } }