2025-06-15 11:52:44 -04:00
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.
2025-06-15 12:19:58 -04:00
pub ( crate ) fn get_stackblock_from_row ( x : & PostgresRow ) -> StackBlock {
2025-06-15 11:52:44 -04:00
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 < usize > {
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 < Vec < usize > > {
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 < StackBlock > {
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 < StackBlock > {
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 ( ) )
}
2025-06-15 11:58:07 -04:00
const MAXIMUM_FREE_STACKBLOCKS : usize = 5 ;
const MAXIMUM_SUPPORTER_STACKBLOCKS : usize = 10 ;
2025-06-15 11:52:44 -04:00
/// 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 ? ;
2025-06-15 11:58:07 -04:00
// 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 ( ) ,
) ) ;
}
}
// ...
2025-06-15 11:52:44 -04:00
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 ( ( ) )
}
}