2025-03-24 20:19:12 -04:00
use super ::* ;
use crate ::cache ::Cache ;
2025-03-29 00:26:56 -04:00
use crate ::model ::communities ::Community ;
2025-04-12 22:25:54 -04:00
use crate ::model ::requests ::{ ActionRequest , ActionType } ;
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 ( ) ;
2025-04-27 23:37:48 -04:00
2025-03-29 00:26:56 -04:00
for membership in & list {
2025-04-27 23:37:48 -04:00
if membership . community = = 0 {
continue ;
}
2025-03-29 00:26:56 -04:00
communities . push ( self . get_community_by_id ( membership . community ) . await ? ) ;
}
2025-04-27 23:37:48 -04:00
2025-03-29 00:26:56 -04:00
Ok ( communities )
}
2025-04-03 20:05:21 -04:00
/// Replace a list of community memberships with the proper user.
pub async fn fill_users (
& self ,
list : Vec < CommunityMembership > ,
) -> Result < Vec < ( CommunityMembership , User ) > > {
let mut users : Vec < ( CommunityMembership , User ) > = Vec ::new ( ) ;
for membership in list {
2025-04-10 18:16:52 -04:00
let owner = membership . owner ;
2025-04-03 20:05:21 -04:00
users . push ( ( membership , self . get_user_by_id ( owner ) . await ? ) ) ;
}
Ok ( users )
}
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-04-09 21:39:30 -04:00
// return Err(Error::GeneralNotFound("community membership".to_string()));
return Ok ( CommunityMembership ::new (
owner ,
community ,
CommunityPermission ::DEFAULT ,
) ) ;
2025-03-24 20:19:12 -04:00
}
Ok ( res . unwrap ( ) )
}
2025-04-09 21:48:25 -04:00
/// Get a community membership by `owner` and `community`.
pub async fn get_membership_by_owner_community_no_void (
& self ,
owner : usize ,
community : usize ,
) -> Result < CommunityMembership > {
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 memberships WHERE owner = $1 AND community = $2 " ,
& [ & ( owner as i64 ) , & ( community as i64 ) ] ,
| x | { Ok ( Self ::get_membership_from_row ( x ) ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " community membership " . to_string ( ) ) ) ;
}
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 ( ) )
}
2025-04-03 20:05:21 -04:00
/// Get all community memberships by `community`.
pub async fn get_memberships_by_community (
& self ,
community : usize ,
2025-04-09 21:17:42 -04:00
community_owner : usize , // the owner is always shown at the top of the first page
2025-04-03 20:05:21 -04:00
batch : usize ,
page : 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 ,
// 33 = banned, 65 = pending membership
2025-04-09 21:17:42 -04:00
" SELECT * FROM memberships WHERE community = $1 AND NOT owner = $2 AND NOT role = 33 AND NOT role = 65 ORDER BY created DESC LIMIT $3 OFFSET $4 " ,
2025-04-03 20:05:21 -04:00
& [
& ( community as i64 ) ,
2025-04-09 21:17:42 -04:00
& ( community_owner as i64 ) ,
2025-04-03 20:05:21 -04:00
& ( batch as i64 ) ,
& ( ( page * batch ) as i64 )
] ,
| x | { Self ::get_membership_from_row ( x ) }
) ;
if res . is_err ( ) {
return Err ( Error ::GeneralNotFound ( " community membership " . to_string ( ) ) ) ;
}
Ok ( res . unwrap ( ) )
}
2025-03-27 18:10:47 -04:00
/// 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
2025-04-09 21:48:25 -04:00
. get_membership_by_owner_community_no_void ( data . owner , data . community )
2025-03-31 15:39:49 -04:00
. 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 ;
2025-04-12 22:25:54 -04:00
// create join request
self . create_request ( ActionRequest ::with_id (
data . owner ,
2025-04-01 15:03:56 -04:00
community . owner ,
2025-04-12 22:25:54 -04:00
ActionType ::CommunityJoin ,
community . id ,
2025-04-01 15:03:56 -04:00
) )
. 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
2025-04-09 21:48:25 -04:00
. get_membership_by_owner_community_no_void ( user . id , y . community )
2025-04-01 15:03:56 -04:00
. 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
}