2025-03-24 20:19:12 -04:00
use super ::* ;
use crate ::cache ::Cache ;
2025-04-01 15:03:56 -04:00
use crate ::model ::auth ::Notification ;
2025-03-29 00:26:56 -04:00
use crate ::model ::communities ::Community ;
2025-03-24 20:19:12 -04:00
use crate ::model ::{
2025-04-01 15:03:56 -04:00
Error , Result ,
auth ::User ,
communities ::{ CommunityJoinAccess , CommunityMembership } ,
communities_permissions ::CommunityPermission ,
permissions ::FinePermission ,
2025-03-24 20:19:12 -04:00
} ;
2025-04-03 15:07:57 -04:00
use crate ::{ auto_method , execute , get , query_row , query_rows , params } ;
2025-03-24 20:19:12 -04:00
#[ cfg(feature = " sqlite " ) ]
use rusqlite ::Row ;
#[ cfg(feature = " postgres " ) ]
use tokio_postgres ::Row ;
impl DataManager {
/// Get a [`JournalEntry`] from an SQL row.
pub ( crate ) fn get_membership_from_row (
#[ cfg(feature = " sqlite " ) ] x : & Row < '_ > ,
#[ cfg(feature = " postgres " ) ] x : & Row ,
2025-03-27 18:10:47 -04:00
) -> CommunityMembership {
CommunityMembership {
2025-04-03 13:52:29 -04:00
id : get ! ( x ->0 ( i64 ) ) as usize ,
created : get ! ( x ->1 ( i64 ) ) as usize ,
owner : get ! ( x ->2 ( i64 ) ) as usize ,
community : get ! ( x ->3 ( i64 ) ) as usize ,
2025-04-03 15:56:44 -04:00
role : CommunityPermission ::from_bits ( get! ( x ->4 ( i32 ) ) as u32 ) . unwrap ( ) ,
2025-03-24 20:19:12 -04:00
}
}
2025-03-31 15:39:49 -04:00
auto_method! ( get_membership_by_id ( ) @ get_membership_from_row -> " SELECT * FROM memberships WHERE id = $1 " - - name = " community membership " - - returns = CommunityMembership - - cache - key - tmpl = " atto.membership:{} " ) ;
2025-03-24 20:19:12 -04:00
2025-03-29 00:26:56 -04:00
/// Replace a list of community memberships with the proper community.
pub async fn fill_communities ( & self , list : Vec < CommunityMembership > ) -> Result < Vec < Community > > {
let mut communities : Vec < Community > = Vec ::new ( ) ;
for membership in & list {
communities . push ( self . get_community_by_id ( membership . community ) . await ? ) ;
}
Ok ( communities )
}
2025-03-27 18:10:47 -04:00
/// Get a community membership by `owner` and `community`.
pub async fn get_membership_by_owner_community (
2025-03-24 20:19:12 -04:00
& self ,
owner : usize ,
2025-03-27 18:10:47 -04:00
community : usize ,
) -> Result < CommunityMembership > {
2025-03-24 20:19:12 -04:00
let conn = match self . connect ( ) . await {
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 memberships WHERE owner = $1 AND community = $2 " ,
2025-04-03 13:52:29 -04:00
& [ & ( owner as i64 ) , & ( community as i64 ) ] ,
2025-03-24 20:19:12 -04:00
| x | { Ok ( Self ::get_membership_from_row ( x ) ) }
) ;
if res . is_err ( ) {
2025-03-27 18:10:47 -04:00
return Err ( Error ::GeneralNotFound ( " community membership " . to_string ( ) ) ) ;
2025-03-24 20:19:12 -04:00
}
Ok ( res . unwrap ( ) )
}
2025-03-27 18:10:47 -04:00
/// Get all community memberships by `owner`.
pub async fn get_memberships_by_owner ( & self , owner : usize ) -> Result < Vec < CommunityMembership > > {
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = query_rows! (
& conn ,
2025-04-01 15:03:56 -04:00
// 33 = banned, 65 = pending membership
2025-04-03 15:07:57 -04:00
" SELECT * FROM memberships WHERE owner = $1 AND NOT role = 33 AND NOT role = 65 ORDER BY created DESC " ,
2025-04-03 13:52:29 -04:00
& [ & ( owner as i64 ) ] ,
2025-03-27 18:10:47 -04:00
| x | { Self ::get_membership_from_row ( x ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " community membership " . to_string ( ) ) ) ;
}
Ok ( res . unwrap ( ) )
}
/// Create a new community membership in the database.
2025-03-24 20:19:12 -04:00
///
/// # Arguments
2025-03-27 18:10:47 -04:00
/// * `data` - a mock [`CommunityMembership`] object to insert
2025-04-01 15:03:56 -04:00
#[ async_recursion::async_recursion ]
pub async fn create_membership ( & self , data : CommunityMembership ) -> Result < String > {
2025-03-31 15:39:49 -04:00
// make sure membership doesn't already exist
if self
. get_membership_by_owner_community ( data . owner , data . community )
. await
. is_ok ( )
{
return Err ( Error ::MiscError ( " Already joined community " . to_string ( ) ) ) ;
}
2025-04-01 15:03:56 -04:00
// check permission
let community = self . get_community_by_id ( data . community ) . await ? ;
match community . join_access {
CommunityJoinAccess ::Nobody = > return Err ( Error ::NotAllowed ) ,
CommunityJoinAccess ::Request = > {
if ! data . role . check ( CommunityPermission ::REQUESTED ) {
let mut data = data . clone ( ) ;
data . role = CommunityPermission ::DEFAULT | CommunityPermission ::REQUESTED ;
// send notification to the owner
self . create_notification ( Notification ::new (
" You've received a community join request! " . to_string ( ) ,
format! (
" [Somebody](/api/v1/auth/profile/find/{}) is asking to join your [community](/community/{}). \n \n [Click here to review their request](/community/{}/manage?uid={}#/members). " ,
data . owner , data . community , data . community , data . owner
) ,
community . owner ,
) )
. await ? ;
// ...
return self . create_membership ( data ) . await ;
}
}
_ = > ( ) ,
}
2025-03-31 15:39:49 -04:00
// ...
2025-03-24 20:19:12 -04:00
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! (
& conn ,
2025-03-29 00:26:56 -04:00
" INSERT INTO memberships VALUES ($1, $2, $3, $4, $5) " ,
2025-04-03 15:07:57 -04:00
params! [
& ( data . id as i64 ) ,
& ( data . created as i64 ) ,
& ( data . owner as i64 ) ,
& ( data . community as i64 ) ,
2025-04-03 15:56:44 -04:00
& ( data . role . bits ( ) as i32 ) ,
2025-03-24 20:19:12 -04:00
]
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
2025-04-01 15:03:56 -04:00
if ! data . role . check ( CommunityPermission ::REQUESTED ) {
// users who are just a requesting to join do not count towards the member count
self . incr_community_member_count ( data . community )
. await
. unwrap ( ) ;
}
2025-03-29 00:26:56 -04:00
2025-04-01 15:03:56 -04:00
Ok ( if data . role . check ( CommunityPermission ::REQUESTED ) {
" Join request sent " . to_string ( )
} else {
" Community joined " . to_string ( )
} )
2025-03-24 20:19:12 -04:00
}
2025-03-25 22:52:47 -04:00
/// Delete a membership given its `id`
pub async fn delete_membership ( & self , id : usize , user : User ) -> Result < ( ) > {
let y = self . get_membership_by_id ( id ) . await ? ;
if user . id ! = y . owner {
// pull other user's membership status
2025-04-01 15:03:56 -04:00
if let Ok ( z ) = self
. get_membership_by_owner_community ( user . id , y . community )
. await
{
2025-03-25 22:52:47 -04:00
// somebody with MANAGE_ROLES _and_ a higher role number can remove us
2025-03-27 18:10:47 -04:00
if ( ! z . role . check ( CommunityPermission ::MANAGE_ROLES ) | ( z . role < y . role ) )
& & ! z . role . check ( CommunityPermission ::ADMINISTRATOR )
2025-03-25 22:52:47 -04:00
{
return Err ( Error ::NotAllowed ) ;
}
} else if ! user . permissions . check ( FinePermission ::MANAGE_MEMBERSHIPS ) {
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 memberships WHERE id = $1 " ,
2025-04-03 15:07:57 -04:00
& [ & ( id as i64 ) ]
2025-03-25 22:52:47 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
self . 2. remove ( format! ( " atto.membership: {} " , id ) ) . await ;
2025-03-29 00:26:56 -04:00
self . decr_community_member_count ( y . community ) . await . unwrap ( ) ;
2025-03-25 22:52:47 -04:00
Ok ( ( ) )
}
/// Update a membership's role given its `id`
pub async fn update_membership_role (
& self ,
id : usize ,
2025-03-27 18:10:47 -04:00
new_role : CommunityPermission ,
2025-03-25 22:52:47 -04:00
) -> Result < ( ) > {
let conn = match self . connect ( ) . await {
Ok ( c ) = > c ,
Err ( e ) = > return Err ( Error ::DatabaseConnection ( e . to_string ( ) ) ) ,
} ;
let res = execute! (
& conn ,
" UPDATE memberships SET role = $1 WHERE id = $2 " ,
2025-04-03 17:20:50 -04:00
params! [ & ( new_role . bits ( ) as i32 ) , & ( id as i64 ) ]
2025-03-25 22:52:47 -04:00
) ;
if let Err ( e ) = res {
return Err ( Error ::DatabaseError ( e . to_string ( ) ) ) ;
}
self . 2. remove ( format! ( " atto.membership: {} " , id ) ) . await ;
Ok ( ( ) )
}
2025-03-24 20:19:12 -04:00
}