2025-06-08 14:15:42 -04:00
use oiseau ::cache ::Cache ;
2025-04-14 17:21:52 -04:00
use crate ::model ::auth ::FollowResult ;
use crate ::model ::requests ::{ ActionRequest , ActionType } ;
2025-03-25 21:19:55 -04:00
use crate ::model ::{ Error , Result , auth ::User , auth ::UserFollow , permissions ::FinePermission } ;
2025-06-08 14:15:42 -04:00
use crate ::{ auto_method , DataManager } ;
2025-03-25 21:19:55 -04:00
2025-06-15 12:19:58 -04:00
use oiseau ::{ PostgresRow , execute , get , query_row , query_rows , params } ;
2025-03-25 21:19:55 -04:00
impl DataManager {
/// Get a [`UserFollow`] from an SQL row.
2025-06-15 12:19:58 -04:00
pub ( crate ) fn get_userfollow_from_row ( x : & PostgresRow ) -> UserFollow {
2025-03-25 21:19:55 -04:00
UserFollow {
2025-04-03 13:52:29 -04:00
id : get ! ( x ->0 ( i64 ) ) as usize ,
created : get ! ( x ->1 ( i64 ) ) as usize ,
initiator : get ! ( x ->2 ( i64 ) ) as usize ,
receiver : get ! ( x ->3 ( i64 ) ) as usize ,
2025-03-25 21:19:55 -04:00
}
}
auto_method! ( get_userfollow_by_id ( ) @ get_userfollow_from_row -> " SELECT * FROM userfollows WHERE id = $1 " - - name = " user follow " - - returns = UserFollow - - cache - key - tmpl = " atto.userfollow:{} " ) ;
2025-06-13 17:47:00 -04:00
/// Filter to update userfollows to clean their users for public APIs.
pub fn userfollows_user_filter ( & self , x : & Vec < ( UserFollow , User ) > ) -> Vec < ( UserFollow , User ) > {
let mut out : Vec < ( UserFollow , User ) > = Vec ::new ( ) ;
for mut y in x . clone ( ) {
y . 1. clean ( ) ;
out . push ( y ) ;
}
out
}
2025-03-25 21:19:55 -04:00
/// Get a user follow by `initiator` and `receiver` (in that order).
pub async fn get_userfollow_by_initiator_receiver (
& self ,
initiator : usize ,
receiver : usize ,
) -> Result < UserFollow > {
2025-06-08 14:15:42 -04:00
let conn = match self . 0. connect ( ) . await {
2025-03-25 21:19:55 -04:00
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = query_row! (
& conn ,
2025-03-27 18:10:47 -04:00
" SELECT * FROM userfollows WHERE initiator = $1 AND receiver = $2 " ,
2025-04-03 13:52:29 -04:00
& [ & ( initiator as i64 ) , & ( receiver as i64 ) ] ,
2025-03-25 21:19:55 -04:00
| x | { Ok ( Self ::get_userfollow_from_row ( x ) ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " user follow " . to_string ( ) ) ) ;
}
Ok ( res . unwrap ( ) )
}
/// Get a user follow by `receiver` and `initiator` (in that order).
pub async fn get_userfollow_by_receiver_initiator (
& self ,
receiver : usize ,
initiator : usize ,
) -> Result < UserFollow > {
2025-06-08 14:15:42 -04:00
let conn = match self . 0. connect ( ) . await {
2025-03-25 21:19:55 -04:00
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = query_row! (
& conn ,
" SELECT * FROM userfollows WHERE receiver = $1 AND initiator = $2 " ,
2025-04-03 13:52:29 -04:00
& [ & ( receiver as i64 ) , & ( initiator as i64 ) ] ,
2025-03-25 21:19:55 -04:00
| x | { Ok ( Self ::get_userfollow_from_row ( x ) ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " user follow " . to_string ( ) ) ) ;
}
Ok ( res . unwrap ( ) )
}
2025-03-31 19:31:36 -04:00
/// Get users the given user is following.
///
/// # Arguments
/// * `id` - the ID of the user
/// * `batch` - the limit of userfollows in each page
/// * `page` - the page number
pub async fn get_userfollows_by_initiator (
& self ,
id : usize ,
batch : usize ,
page : usize ,
) -> Result < Vec < UserFollow > > {
2025-06-08 14:15:42 -04:00
let conn = match self . 0. connect ( ) . await {
2025-03-31 19:31:36 -04:00
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = query_rows! (
& conn ,
" SELECT * FROM userfollows WHERE initiator = $1 ORDER BY created DESC LIMIT $2 OFFSET $3 " ,
2025-04-03 13:52:29 -04:00
& [ & ( id as i64 ) , & ( batch as i64 ) , & ( ( page * batch ) as i64 ) ] ,
2025-03-31 19:31:36 -04:00
| x | { Self ::get_userfollow_from_row ( x ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " user follow " . to_string ( ) ) ) ;
}
Ok ( res . unwrap ( ) )
}
2025-04-09 22:00:45 -04:00
/// Get users the given user is following.
///
/// # Arguments
/// * `id` - the ID of the user
pub async fn get_userfollows_by_initiator_all ( & self , id : usize ) -> Result < Vec < UserFollow > > {
2025-06-08 14:15:42 -04:00
let conn = match self . 0. connect ( ) . await {
2025-04-09 22:00:45 -04:00
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = query_rows! (
& conn ,
" SELECT * FROM userfollows WHERE initiator = $1 " ,
& [ & ( id as i64 ) ] ,
| x | { Self ::get_userfollow_from_row ( x ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " user follow " . to_string ( ) ) ) ;
}
Ok ( res . unwrap ( ) )
}
2025-03-31 19:31:36 -04:00
/// Get users following the given user.
///
/// # Arguments
/// * `id` - the ID of the user
/// * `batch` - the limit of userfollows in each page
/// * `page` - the page number
pub async fn get_userfollows_by_receiver (
& self ,
id : usize ,
batch : usize ,
page : usize ,
) -> Result < Vec < UserFollow > > {
2025-06-08 14:15:42 -04:00
let conn = match self . 0. connect ( ) . await {
2025-03-31 19:31:36 -04:00
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = query_rows! (
& conn ,
" SELECT * FROM userfollows WHERE receiver = $1 ORDER BY created DESC LIMIT $2 OFFSET $3 " ,
2025-04-03 13:52:29 -04:00
& [ & ( id as i64 ) , & ( batch as i64 ) , & ( ( page * batch ) as i64 ) ] ,
2025-03-31 19:31:36 -04:00
| x | { Self ::get_userfollow_from_row ( x ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " user follow " . to_string ( ) ) ) ;
}
Ok ( res . unwrap ( ) )
}
2025-04-09 22:00:45 -04:00
/// Get users following the given user.
///
/// # Arguments
/// * `id` - the ID of the user
pub async fn get_userfollows_by_receiver_all ( & self , id : usize ) -> Result < Vec < UserFollow > > {
2025-06-08 14:15:42 -04:00
let conn = match self . 0. connect ( ) . await {
2025-04-09 22:00:45 -04:00
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = query_rows! (
& conn ,
" SELECT * FROM userfollows WHERE receiver = $1 " ,
& [ & ( id as i64 ) ] ,
| x | { Self ::get_userfollow_from_row ( x ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " user follow " . to_string ( ) ) ) ;
}
Ok ( res . unwrap ( ) )
}
2025-03-31 22:35:11 -04:00
/// Complete a vector of just userfollows with their receiver as well.
pub async fn fill_userfollows_with_receiver (
& self ,
userfollows : Vec < UserFollow > ,
) -> Result < Vec < ( UserFollow , User ) > > {
let mut out : Vec < ( UserFollow , User ) > = Vec ::new ( ) ;
for userfollow in userfollows {
2025-04-10 18:16:52 -04:00
let receiver = userfollow . receiver ;
2025-04-17 21:48:45 -04:00
out . push ( (
userfollow ,
match self . get_user_by_id ( receiver ) . await {
Ok ( u ) = > u ,
Err ( _ ) = > continue ,
} ,
) ) ;
2025-03-31 22:35:11 -04:00
}
Ok ( out )
}
/// Complete a vector of just userfollows with their initiator as well.
pub async fn fill_userfollows_with_initiator (
& self ,
userfollows : Vec < UserFollow > ,
) -> Result < Vec < ( UserFollow , User ) > > {
let mut out : Vec < ( UserFollow , User ) > = Vec ::new ( ) ;
for userfollow in userfollows {
2025-04-10 18:16:52 -04:00
let initiator = userfollow . initiator ;
2025-04-17 21:48:45 -04:00
out . push ( (
userfollow ,
match self . get_user_by_id ( initiator ) . await {
Ok ( u ) = > u ,
Err ( _ ) = > continue ,
} ,
) ) ;
2025-03-31 22:35:11 -04:00
}
Ok ( out )
}
2025-03-25 21:19:55 -04:00
/// Create a new user follow in the database.
///
/// # Arguments
/// * `data` - a mock [`UserFollow`] object to insert
2025-04-14 17:21:52 -04:00
/// * `force` - if we should skip the request stage
pub async fn create_userfollow ( & self , data : UserFollow , force : bool ) -> Result < FollowResult > {
if ! force {
let other_user = self . get_user_by_id ( data . receiver ) . await ? ;
if other_user . settings . private_profile {
// send follow request instead
self . create_request ( ActionRequest ::with_id (
data . initiator ,
data . receiver ,
ActionType ::Follow ,
data . receiver ,
) )
. await ? ;
return Ok ( FollowResult ::Requested ) ;
}
}
// ...
2025-06-08 14:15:42 -04:00
let conn = match self . 0. connect ( ) . await {
2025-03-25 21:19:55 -04:00
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! (
& conn ,
" INSERT INTO userfollows VALUES ($1, $2, $3, $4) " ,
2025-04-03 15:07:57 -04:00
params! [
& ( data . id as i64 ) ,
& ( data . created as i64 ) ,
& ( data . initiator as i64 ) ,
& ( data . receiver as i64 )
2025-03-25 21:19:55 -04:00
]
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
// incr counts
self . incr_user_following_count ( data . initiator )
. await
. unwrap ( ) ;
self . incr_user_follower_count ( data . receiver ) . await . unwrap ( ) ;
// return
2025-04-14 17:21:52 -04:00
Ok ( FollowResult ::Followed )
2025-03-25 21:19:55 -04:00
}
2025-04-17 21:48:45 -04:00
pub async fn delete_userfollow (
& self ,
id : usize ,
user : & User ,
is_deleting_user : bool ,
) -> Result < ( ) > {
2025-03-25 21:19:55 -04:00
let follow = self . get_userfollow_by_id ( id ) . await ? ;
2025-04-14 17:21:52 -04:00
if ( user . id ! = follow . initiator )
& & ( user . id ! = follow . receiver )
& & ! user . permissions . check ( FinePermission ::MANAGE_FOLLOWS )
2025-04-17 21:48:45 -04:00
& & ! is_deleting_user
2025-04-14 17:21:52 -04:00
{
2025-04-10 18:16:52 -04:00
return Err ( Error ::NotAllowed ) ;
2025-03-25 21:19:55 -04:00
}
2025-06-08 14:15:42 -04:00
let conn = match self . 0. connect ( ) . await {
2025-03-25 21:19:55 -04:00
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! (
& conn ,
" DELETE FROM userfollows WHERE id = $1 " ,
2025-04-03 15:07:57 -04:00
& [ & ( id as i64 ) ]
2025-03-25 21:19:55 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
2025-06-08 14:15:42 -04:00
self . 0. 1. remove ( format! ( " atto.userfollow: {} " , id ) ) . await ;
2025-03-25 21:19:55 -04:00
2025-04-17 21:48:45 -04:00
// decr counts (if we aren't deleting the user OR the user id isn't the deleted user id)
if ! is_deleting_user | ( follow . initiator ! = user . id ) {
self . decr_user_following_count ( follow . initiator )
. await
. unwrap ( ) ;
}
2025-03-25 21:19:55 -04:00
2025-04-17 21:48:45 -04:00
if ! is_deleting_user | ( follow . receiver ! = user . id ) {
self . decr_user_follower_count ( follow . receiver )
. await
. unwrap ( ) ;
}
2025-03-25 21:19:55 -04:00
// return
Ok ( ( ) )
}
2025-06-15 11:52:44 -04:00
pub async fn delete_userfollow_sudo ( & self , id : usize , user_id : usize ) -> Result < ( ) > {
let follow = self . get_userfollow_by_id ( id ) . await ? ;
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 userfollows WHERE id = $1 " ,
& [ & ( id as i64 ) ]
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
self . 0. 1. remove ( format! ( " atto.userfollow: {} " , id ) ) . await ;
// decr counts
if follow . initiator ! = user_id {
self . decr_user_following_count ( follow . initiator )
. await
. unwrap ( ) ;
}
if follow . receiver ! = user_id {
self . decr_user_follower_count ( follow . receiver )
. await
. unwrap ( ) ;
}
// return
Ok ( ( ) )
}
2025-03-25 21:19:55 -04:00
}