2023-03-01 17:19:46 +00:00
use crate ::structs ::{ CommunityModeratorView , CommunityView , PersonView } ;
2022-11-09 10:05:00 +00:00
use diesel ::{
2023-07-28 08:36:50 +00:00
pg ::Pg ,
2022-11-09 10:05:00 +00:00
result ::Error ,
BoolExpressionMethods ,
ExpressionMethods ,
JoinOnDsl ,
NullableExpressionMethods ,
PgTextExpressionMethods ,
QueryDsl ,
} ;
use diesel_async ::RunQueryDsl ;
2021-10-16 13:33:38 +00:00
use lemmy_db_schema ::{
newtypes ::{ CommunityId , PersonId } ,
2023-09-20 09:56:13 +00:00
schema ::{
community ,
community_aggregates ,
community_block ,
community_follower ,
instance_block ,
local_user ,
} ,
2024-02-16 12:24:35 +00:00
source ::{ community ::CommunityFollower , local_user ::LocalUser , site ::Site } ,
2023-07-28 08:36:50 +00:00
utils ::{ fuzzy_search , limit_and_offset , DbConn , DbPool , ListFn , Queries , ReadFn } ,
2024-01-25 16:04:25 +00:00
CommunityVisibility ,
2022-05-06 20:55:07 +00:00
ListingType ,
SortType ,
2020-12-18 16:17:21 +00:00
} ;
2020-12-04 16:29:44 +00:00
2023-07-28 08:36:50 +00:00
fn queries < ' a > ( ) -> Queries <
2023-08-11 09:13:14 +00:00
impl ReadFn < ' a , CommunityView , ( CommunityId , Option < PersonId > , bool ) > ,
2024-02-16 12:24:35 +00:00
impl ListFn < ' a , CommunityView , ( CommunityQuery < ' a > , & ' a Site ) > ,
2023-07-28 08:36:50 +00:00
> {
let all_joins = | query : community ::BoxedQuery < ' a , Pg > , my_person_id : Option < PersonId > | {
2020-12-06 03:49:15 +00:00
// The left join below will return None in this case
2021-03-18 20:25:21 +00:00
let person_id_join = my_person_id . unwrap_or ( PersonId ( - 1 ) ) ;
2020-12-04 16:29:44 +00:00
2023-07-28 08:36:50 +00:00
query
2020-12-04 21:35:46 +00:00
. inner_join ( community_aggregates ::table )
2020-12-06 03:49:15 +00:00
. left_join (
community_follower ::table . on (
community ::id
. eq ( community_follower ::community_id )
2021-03-10 22:33:55 +00:00
. and ( community_follower ::person_id . eq ( person_id_join ) ) ,
2020-12-06 03:49:15 +00:00
) ,
)
2023-09-20 09:56:13 +00:00
. left_join (
instance_block ::table . on (
community ::instance_id
. eq ( instance_block ::instance_id )
. and ( instance_block ::person_id . eq ( person_id_join ) ) ,
) ,
)
2021-08-19 20:54:15 +00:00
. left_join (
community_block ::table . on (
community ::id
. eq ( community_block ::community_id )
. and ( community_block ::person_id . eq ( person_id_join ) ) ,
) ,
)
2023-07-28 08:36:50 +00:00
} ;
let selection = (
community ::all_columns ,
2023-08-08 09:41:10 +00:00
CommunityFollower ::select_subscribed_type ( ) ,
Remove id column and use different primary key on some tables (#4093)
* post_saved
* fmt
* remove unique and not null
* put person_id first in primary key and remove index
* use post_saved.find
* change captcha_answer
* remove removal of not null
* comment_aggregates
* comment_like
* comment_saved
* aggregates
* remove "\"
* deduplicate site_aggregates
* person_post_aggregates
* community_moderator
* community_block
* community_person_ban
* custom_emoji_keyword
* federation allow/block list
* federation_queue_state
* instance_block
* local_site_rate_limit, local_user_language, login_token
* person_ban, person_block, person_follower, post_like, post_read, received_activity
* community_follower, community_language, site_language
* fmt
* image_upload
* remove unused newtypes
* remove more indexes
* use .find
* merge
* fix site_aggregates_site function
* fmt
* Primary keys dess (#17)
* Also order reports by oldest first (ref #4123) (#4129)
* Support signed fetch for federation (fixes #868) (#4125)
* Support signed fetch for federation (fixes #868)
* taplo
* add federation queue state to get_federated_instances api (#4104)
* add federation queue state to get_federated_instances api
* feature gate
* move retry sleep function
* move stuff around
* Add UI setting for collapsing bot comments. Fixes #3838 (#4098)
* Add UI setting for collapsing bot comments. Fixes #3838
* Fixing clippy check.
* Only keep sent and received activities for 7 days (fixes #4113, fixes #4110) (#4131)
* Only check auth secure on release mode. (#4127)
* Only check auth secure on release mode.
* Fixing wrong js-client.
* Adding is_debug_mode var.
* Fixing the desktop image on the README. (#4135)
* Delete dupes and add possibly missing unique constraint on person_aggregates.
* Fixing clippy lints.
---------
Co-authored-by: Nutomic <me@nutomic.com>
Co-authored-by: phiresky <phireskyde+git@gmail.com>
* fmt
* Update community_block.rs
* Update instance_block.rs
* Update person_block.rs
* Update person_block.rs
---------
Co-authored-by: Dessalines <dessalines@users.noreply.github.com>
Co-authored-by: Nutomic <me@nutomic.com>
Co-authored-by: phiresky <phireskyde+git@gmail.com>
2023-11-13 13:14:07 +00:00
community_block ::community_id . nullable ( ) . is_not_null ( ) ,
2023-08-31 13:26:10 +00:00
community_aggregates ::all_columns ,
2023-07-28 08:36:50 +00:00
) ;
let not_removed_or_deleted = community ::removed
. eq ( false )
. and ( community ::deleted . eq ( false ) ) ;
let read = move | mut conn : DbConn < ' a > ,
( community_id , my_person_id , is_mod_or_admin ) : (
CommunityId ,
Option < PersonId > ,
2023-08-11 09:13:14 +00:00
bool ,
2023-07-28 08:36:50 +00:00
) | async move {
let mut query = all_joins (
community ::table . find ( community_id ) . into_boxed ( ) ,
my_person_id ,
)
. select ( selection ) ;
2023-03-01 03:46:15 +00:00
// Hide deleted and removed for non-admins or mods
2023-08-11 09:13:14 +00:00
if ! is_mod_or_admin {
2023-07-28 08:36:50 +00:00
query = query . filter ( not_removed_or_deleted ) ;
2023-03-01 03:46:15 +00:00
}
2024-01-25 16:04:25 +00:00
// Hide local only communities from unauthenticated users
if my_person_id . is_none ( ) {
query = query . filter ( community ::visibility . eq ( CommunityVisibility ::Public ) ) ;
}
2023-08-31 13:26:10 +00:00
query . first ::< CommunityView > ( & mut conn ) . await
2023-07-28 08:36:50 +00:00
} ;
2020-12-06 03:49:15 +00:00
2024-02-16 12:24:35 +00:00
let list = move | mut conn : DbConn < ' a > , ( options , site ) : ( CommunityQuery < ' a > , & ' a Site ) | async move {
2023-07-03 17:09:15 +00:00
use SortType ::* ;
2023-07-28 08:36:50 +00:00
let my_person_id = options . local_user . map ( | l | l . person_id ) ;
2022-11-09 10:05:00 +00:00
2020-12-16 18:59:43 +00:00
// The left join below will return None in this case
2023-07-28 08:36:50 +00:00
let person_id_join = my_person_id . unwrap_or ( PersonId ( - 1 ) ) ;
2020-12-16 18:59:43 +00:00
2023-07-28 08:36:50 +00:00
let mut query = all_joins ( community ::table . into_boxed ( ) , my_person_id )
2022-03-02 17:39:27 +00:00
. left_join ( local_user ::table . on ( local_user ::person_id . eq ( person_id_join ) ) )
2023-07-28 08:36:50 +00:00
. select ( selection ) ;
if let Some ( search_term ) = options . search_term {
2020-12-06 03:49:15 +00:00
let searcher = fuzzy_search ( & search_term ) ;
query = query
2022-11-19 04:33:54 +00:00
. filter ( community ::name . ilike ( searcher . clone ( ) ) )
2023-07-28 08:36:50 +00:00
. or_filter ( community ::title . ilike ( searcher ) )
}
2020-12-06 03:49:15 +00:00
2023-03-01 03:46:15 +00:00
// Hide deleted and removed for non-admins or mods
2023-08-11 09:13:14 +00:00
if ! options . is_mod_or_admin {
2023-07-28 08:36:50 +00:00
query = query . filter ( not_removed_or_deleted ) . filter (
community ::hidden
. eq ( false )
. or ( community_follower ::person_id . eq ( person_id_join ) ) ,
) ;
2023-03-01 03:46:15 +00:00
}
2023-07-28 08:36:50 +00:00
match options . sort . unwrap_or ( Hot ) {
2023-09-06 17:43:27 +00:00
Hot | Active | Scaled = > query = query . order_by ( community_aggregates ::hot_rank . desc ( ) ) ,
2023-07-03 17:09:15 +00:00
NewComments | TopDay | TopTwelveHour | TopSixHour | TopHour = > {
query = query . order_by ( community_aggregates ::users_active_day . desc ( ) )
}
New = > query = query . order_by ( community ::published . desc ( ) ) ,
Old = > query = query . order_by ( community ::published . asc ( ) ) ,
2023-07-26 17:07:05 +00:00
// Controversial is temporary until a CommentSortType is created
MostComments | Controversial = > query = query . order_by ( community_aggregates ::comments . desc ( ) ) ,
2023-07-03 17:09:15 +00:00
TopAll | TopYear | TopNineMonths = > {
query = query . order_by ( community_aggregates ::subscribers . desc ( ) )
}
TopSixMonths | TopThreeMonths = > {
query = query . order_by ( community_aggregates ::users_active_half_year . desc ( ) )
}
TopMonth = > query = query . order_by ( community_aggregates ::users_active_month . desc ( ) ) ,
TopWeek = > query = query . order_by ( community_aggregates ::users_active_week . desc ( ) ) ,
2020-12-06 03:49:15 +00:00
} ;
2023-07-28 08:36:50 +00:00
if let Some ( listing_type ) = options . listing_type {
2021-04-15 03:37:51 +00:00
query = match listing_type {
2023-08-08 09:41:10 +00:00
ListingType ::Subscribed = > query . filter ( community_follower ::pending . is_not_null ( ) ) , // TODO could be this: and(community_follower::person_id.eq(person_id_join)),
2021-04-15 03:37:51 +00:00
ListingType ::Local = > query . filter ( community ::local . eq ( true ) ) ,
_ = > query ,
} ;
}
2021-01-26 17:18:01 +00:00
2023-09-20 09:56:13 +00:00
// Don't show blocked communities and communities on blocked instances. nsfw communities are
// also hidden (based on profile setting)
2023-07-28 08:36:50 +00:00
if options . local_user . is_some ( ) {
2023-09-20 09:56:13 +00:00
query = query . filter ( instance_block ::person_id . is_null ( ) ) ;
2021-08-19 20:54:15 +00:00
query = query . filter ( community_block ::person_id . is_null ( ) ) ;
2022-03-02 17:39:27 +00:00
query = query . filter ( community ::nsfw . eq ( false ) . or ( local_user ::show_nsfw . eq ( true ) ) ) ;
} else {
2024-02-16 12:24:35 +00:00
// No person in request, only show nsfw communities if show_nsfw is passed into request or if
// site has content warning.
let has_content_warning = site . content_warning . is_some ( ) ;
if ! options . show_nsfw & & ! has_content_warning {
2022-03-02 17:39:27 +00:00
query = query . filter ( community ::nsfw . eq ( false ) ) ;
}
2024-01-25 16:04:25 +00:00
// Hide local only communities from unauthenticated users
query = query . filter ( community ::visibility . eq ( CommunityVisibility ::Public ) ) ;
2021-08-19 20:54:15 +00:00
}
2023-07-28 08:36:50 +00:00
let ( limit , offset ) = limit_and_offset ( options . page , options . limit ) ? ;
query
2020-12-06 03:49:15 +00:00
. limit ( limit )
. offset ( offset )
2023-08-31 13:26:10 +00:00
. load ::< CommunityView > ( & mut conn )
2023-07-28 08:36:50 +00:00
. await
} ;
Queries ::new ( read , list )
}
impl CommunityView {
pub async fn read (
pool : & mut DbPool < '_ > ,
community_id : CommunityId ,
my_person_id : Option < PersonId > ,
2023-08-11 09:13:14 +00:00
is_mod_or_admin : bool ,
2023-07-28 08:36:50 +00:00
) -> Result < Self , Error > {
queries ( )
. read ( pool , ( community_id , my_person_id , is_mod_or_admin ) )
. await
}
pub async fn is_mod_or_admin (
pool : & mut DbPool < '_ > ,
person_id : PersonId ,
community_id : CommunityId ,
) -> Result < bool , Error > {
let is_mod =
CommunityModeratorView ::is_community_moderator ( pool , community_id , person_id ) . await ? ;
if is_mod {
2023-11-21 16:20:24 +00:00
Ok ( true )
} else {
let is_admin = PersonView ::read ( pool , person_id ) . await ? . is_admin ;
Ok ( is_admin )
2023-07-28 08:36:50 +00:00
}
}
2023-11-21 15:33:49 +00:00
/// Checks if a person is an admin, or moderator of any community.
pub async fn is_mod_of_any_or_admin (
pool : & mut DbPool < '_ > ,
person_id : PersonId ,
) -> Result < bool , Error > {
let is_mod_of_any =
CommunityModeratorView ::is_community_moderator_of_any ( pool , person_id ) . await ? ;
if is_mod_of_any {
return Ok ( true ) ;
}
2023-11-21 16:20:24 +00:00
let is_admin = PersonView ::read ( pool , person_id ) . await ? . is_admin ;
Ok ( is_admin )
2023-11-21 15:33:49 +00:00
}
2023-07-28 08:36:50 +00:00
}
#[ derive(Default) ]
pub struct CommunityQuery < ' a > {
pub listing_type : Option < ListingType > ,
pub sort : Option < SortType > ,
pub local_user : Option < & ' a LocalUser > ,
pub search_term : Option < String > ,
2023-08-11 09:13:14 +00:00
pub is_mod_or_admin : bool ,
pub show_nsfw : bool ,
2023-07-28 08:36:50 +00:00
pub page : Option < i64 > ,
pub limit : Option < i64 > ,
}
impl < ' a > CommunityQuery < ' a > {
2024-02-16 12:24:35 +00:00
pub async fn list ( self , site : & Site , pool : & mut DbPool < '_ > ) -> Result < Vec < CommunityView > , Error > {
queries ( ) . list ( pool , ( self , site ) ) . await
2020-12-06 03:49:15 +00:00
}
}
2024-01-25 16:04:25 +00:00
#[ cfg(test) ]
mod tests {
#![ allow(clippy::unwrap_used) ]
#![ allow(clippy::indexing_slicing) ]
use crate ::{ community_view ::CommunityQuery , structs ::CommunityView } ;
use lemmy_db_schema ::{
source ::{
community ::{ Community , CommunityInsertForm , CommunityUpdateForm } ,
instance ::Instance ,
local_user ::{ LocalUser , LocalUserInsertForm } ,
person ::{ Person , PersonInsertForm } ,
2024-02-16 12:24:35 +00:00
site ::Site ,
2024-01-25 16:04:25 +00:00
} ,
traits ::Crud ,
utils ::{ build_db_pool_for_tests , DbPool } ,
CommunityVisibility ,
} ;
use serial_test ::serial ;
2024-02-16 12:24:35 +00:00
use url ::Url ;
2024-01-25 16:04:25 +00:00
struct Data {
inserted_instance : Instance ,
local_user : LocalUser ,
inserted_community : Community ,
2024-02-16 12:24:35 +00:00
site : Site ,
2024-01-25 16:04:25 +00:00
}
async fn init_data ( pool : & mut DbPool < '_ > ) -> Data {
let inserted_instance = Instance ::read_or_create ( pool , " my_domain.tld " . to_string ( ) )
. await
. unwrap ( ) ;
let person_name = " tegan " . to_string ( ) ;
let new_person = PersonInsertForm ::builder ( )
. name ( person_name . clone ( ) )
. public_key ( " pubkey " . to_string ( ) )
. instance_id ( inserted_instance . id )
. build ( ) ;
let inserted_person = Person ::create ( pool , & new_person ) . await . unwrap ( ) ;
let local_user_form = LocalUserInsertForm ::builder ( )
. person_id ( inserted_person . id )
. password_encrypted ( String ::new ( ) )
. build ( ) ;
let local_user = LocalUser ::create ( pool , & local_user_form ) . await . unwrap ( ) ;
let new_community = CommunityInsertForm ::builder ( )
. name ( " test_community_3 " . to_string ( ) )
. title ( " nada " . to_owned ( ) )
. public_key ( " pubkey " . to_string ( ) )
. instance_id ( inserted_instance . id )
. build ( ) ;
let inserted_community = Community ::create ( pool , & new_community ) . await . unwrap ( ) ;
2024-02-16 12:24:35 +00:00
let url = Url ::parse ( " http://example.com " ) . unwrap ( ) ;
let site = Site {
id : Default ::default ( ) ,
name : String ::new ( ) ,
sidebar : None ,
published : Default ::default ( ) ,
updated : None ,
icon : None ,
banner : None ,
description : None ,
actor_id : url . clone ( ) . into ( ) ,
last_refreshed_at : Default ::default ( ) ,
inbox_url : url . into ( ) ,
private_key : None ,
public_key : String ::new ( ) ,
instance_id : Default ::default ( ) ,
content_warning : None ,
} ;
2024-01-25 16:04:25 +00:00
Data {
inserted_instance ,
local_user ,
inserted_community ,
2024-02-16 12:24:35 +00:00
site ,
2024-01-25 16:04:25 +00:00
}
}
async fn cleanup ( data : Data , pool : & mut DbPool < '_ > ) {
Community ::delete ( pool , data . inserted_community . id )
. await
. unwrap ( ) ;
Person ::delete ( pool , data . local_user . person_id )
. await
. unwrap ( ) ;
Instance ::delete ( pool , data . inserted_instance . id )
. await
. unwrap ( ) ;
}
#[ tokio::test ]
#[ serial ]
async fn local_only_community ( ) {
let pool = & build_db_pool_for_tests ( ) . await ;
let pool = & mut pool . into ( ) ;
let data = init_data ( pool ) . await ;
Community ::update (
pool ,
data . inserted_community . id ,
& CommunityUpdateForm {
visibility : Some ( CommunityVisibility ::LocalOnly ) ,
.. Default ::default ( )
} ,
)
. await
. unwrap ( ) ;
let unauthenticated_query = CommunityQuery {
.. Default ::default ( )
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-25 16:04:25 +00:00
. await
. unwrap ( ) ;
assert_eq! ( 0 , unauthenticated_query . len ( ) ) ;
let authenticated_query = CommunityQuery {
local_user : Some ( & data . local_user ) ,
.. Default ::default ( )
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-25 16:04:25 +00:00
. await
. unwrap ( ) ;
assert_eq! ( 1 , authenticated_query . len ( ) ) ;
let unauthenticated_community =
CommunityView ::read ( pool , data . inserted_community . id , None , false ) . await ;
assert! ( unauthenticated_community . is_err ( ) ) ;
let authenticated_community = CommunityView ::read (
pool ,
data . inserted_community . id ,
Some ( data . local_user . person_id ) ,
false ,
)
. await ;
assert! ( authenticated_community . is_ok ( ) ) ;
cleanup ( data , pool ) . await ;
}
}