@ -2,12 +2,14 @@ use crate::structs::PostView;
use diesel ::{ dsl ::* , pg ::Pg , result ::Error , * } ;
use lemmy_db_schema ::{
aggregates ::structs ::PostAggregates ,
newtypes ::{ CommunityId , DbUrl , PersonId, PostId } ,
newtypes ::{ CommunityId , DbUrl , LocalUserId, PersonId, PostId } ,
schema ::{
community ,
community_block ,
community_follower ,
community_person_ban ,
language ,
local_user_language ,
person ,
person_block ,
post ,
@ -18,6 +20,7 @@ use lemmy_db_schema::{
} ,
source ::{
community ::{ Community , CommunityFollower , CommunityPersonBan , CommunitySafe } ,
language ::Language ,
person ::{ Person , PersonSafe } ,
person_block ::PersonBlock ,
post ::{ Post , PostRead , PostSaved } ,
@ -41,6 +44,7 @@ type PostViewTuple = (
Option < PostRead > ,
Option < PersonBlock > ,
Option < i16 > ,
Language ,
) ;
impl PostView {
@ -51,7 +55,6 @@ impl PostView {
) -> Result < Self , Error > {
// The left join below will return None in this case
let person_id_join = my_person_id . unwrap_or ( PersonId ( - 1 ) ) ;
let (
post ,
creator ,
@ -63,6 +66,7 @@ impl PostView {
read ,
creator_blocked ,
post_like ,
language ,
) = post ::table
. find ( post_id )
. inner_join ( person ::table )
@ -115,6 +119,7 @@ impl PostView {
. and ( post_like ::person_id . eq ( person_id_join ) ) ,
) ,
)
. inner_join ( language ::table )
. select ( (
post ::all_columns ,
Person ::safe_columns_tuple ( ) ,
@ -126,6 +131,7 @@ impl PostView {
post_read ::all_columns . nullable ( ) ,
person_block ::all_columns . nullable ( ) ,
post_like ::score . nullable ( ) ,
language ::all_columns ,
) )
. first ::< PostViewTuple > ( conn ) ? ;
@ -148,6 +154,7 @@ impl PostView {
read : read . is_some ( ) ,
creator_blocked : creator_blocked . is_some ( ) ,
my_vote ,
language ,
} )
}
}
@ -163,6 +170,7 @@ pub struct PostQuery<'a> {
community_id : Option < CommunityId > ,
community_actor_id : Option < DbUrl > ,
my_person_id : Option < PersonId > ,
my_local_user_id : Option < LocalUserId > ,
search_term : Option < String > ,
url_search : Option < String > ,
show_nsfw : Option < bool > ,
@ -179,6 +187,7 @@ impl<'a> PostQuery<'a> {
// The left join below will return None in this case
let person_id_join = self . my_person_id . unwrap_or ( PersonId ( - 1 ) ) ;
let local_user_id_join = self . my_local_user_id . unwrap_or ( LocalUserId ( - 1 ) ) ;
let mut query = post ::table
. inner_join ( person ::table )
@ -238,6 +247,14 @@ impl<'a> PostQuery<'a> {
. and ( post_like ::person_id . eq ( person_id_join ) ) ,
) ,
)
. inner_join ( language ::table )
. left_join (
local_user_language ::table . on (
post ::language_id
. eq ( local_user_language ::language_id )
. and ( local_user_language ::local_user_id . eq ( local_user_id_join ) ) ,
) ,
)
. select ( (
post ::all_columns ,
Person ::safe_columns_tuple ( ) ,
@ -249,6 +266,7 @@ impl<'a> PostQuery<'a> {
post_read ::all_columns . nullable ( ) ,
person_block ::all_columns . nullable ( ) ,
post_like ::score . nullable ( ) ,
language ::all_columns ,
) )
. into_boxed ( ) ;
@ -323,6 +341,11 @@ impl<'a> PostQuery<'a> {
query = query . filter ( post_read ::id . is_null ( ) ) ;
}
// Filter out the rows with missing languages
if self . my_local_user_id . is_some ( ) {
query = query . filter ( local_user_language ::id . is_not_null ( ) ) ;
}
// Don't show blocked communities or persons
if self . my_person_id . is_some ( ) {
query = query . filter ( community_block ::person_id . is_null ( ) ) ;
@ -403,6 +426,7 @@ impl ViewToVec for PostView {
read : a . 7. is_some ( ) ,
creator_blocked : a . 8. is_some ( ) ,
my_vote : a . 9 ,
language : a . 10 ,
} )
. collect ::< Vec < Self > > ( )
}
@ -411,11 +435,16 @@ impl ViewToVec for PostView {
#[ cfg(test) ]
mod tests {
use crate ::post_view ::{ PostQuery , PostView } ;
use diesel ::PgConnection ;
use lemmy_db_schema ::{
aggregates ::structs ::PostAggregates ,
newtypes ::LanguageId ,
source ::{
community ::* ,
community_block ::{ CommunityBlock , CommunityBlockForm } ,
language ::Language ,
local_user ::{ LocalUser , LocalUserForm } ,
local_user_language ::LocalUserLanguage ,
person ::* ,
person_block ::{ PersonBlock , PersonBlockForm } ,
post ::* ,
@ -427,15 +456,16 @@ mod tests {
} ;
use serial_test ::serial ;
#[ test ]
#[ serial ]
fn test_crud ( ) {
let conn = establish_unpooled_connection ( ) ;
struct Data {
inserted_person : Person ,
inserted_blocked_person : Person ,
inserted_bot : Person ,
inserted_community : Community ,
inserted_post : Post ,
}
fn init_data ( conn : & PgConnection ) -> Data {
let person_name = "tegan" . to_string ( ) ;
let community_name = "test_community_3" . to_string ( ) ;
let post_name = "test post 3" . to_string ( ) ;
let bot_post_name = "test bot post" . to_string ( ) ;
let new_person = PersonForm {
name : person_name . to_owned ( ) ,
@ -443,43 +473,44 @@ mod tests {
.. PersonForm ::default ( )
} ;
let inserted_person = Person ::create ( & conn , & new_person ) . unwrap ( ) ;
let inserted_person = Person ::create ( conn , & new_person ) . unwrap ( ) ;
let new_bot = PersonForm {
name : person_name . to_owned ( ) ,
name : "mybot" . to_string ( ) ,
bot_account : Some ( true ) ,
public_key : Some ( "pubkey" . to_string ( ) ) ,
.. PersonForm ::default ( )
} ;
let inserted_bot = Person ::create ( & conn , & new_bot ) . unwrap ( ) ;
let inserted_bot = Person ::create ( conn , & new_bot ) . unwrap ( ) ;
let new_community = CommunityForm {
name : community_name . to_owned ( ) ,
name : "test_community_3" . to_string ( ) ,
title : "nada" . to_owned ( ) ,
public_key : Some ( "pubkey" . to_string ( ) ) ,
.. CommunityForm ::default ( )
} ;
let inserted_community = Community ::create ( & conn , & new_community ) . unwrap ( ) ;
let inserted_community = Community ::create ( conn , & new_community ) . unwrap ( ) ;
// Test a person block, make sure the post query doesn't include their post
let blocked_person = PersonForm {
name : person_name .to_owned ( ) ,
name : person_name ,
public_key : Some ( "pubkey" . to_string ( ) ) ,
.. PersonForm ::default ( )
} ;
let inserted_blocked_person = Person ::create ( & conn , & blocked_person ) . unwrap ( ) ;
let inserted_blocked_person = Person ::create ( conn , & blocked_person ) . unwrap ( ) ;
let post_from_blocked_person = PostForm {
name : "blocked_person_post" . to_string ( ) ,
creator_id : inserted_blocked_person . id ,
community_id : inserted_community . id ,
language_id : Some ( LanguageId ( 1 ) ) ,
.. PostForm ::default ( )
} ;
Post ::create ( & conn , & post_from_blocked_person ) . unwrap ( ) ;
Post ::create ( conn , & post_from_blocked_person ) . unwrap ( ) ;
// block that person
let person_block = PersonBlockForm {
@ -487,72 +518,58 @@ mod tests {
target_id : inserted_blocked_person . id ,
} ;
PersonBlock ::block ( & conn , & person_block ) . unwrap ( ) ;
PersonBlock ::block ( conn , & person_block ) . unwrap ( ) ;
// A sample post
let new_post = PostForm {
name : post_name . to_owned ( ) ,
name : "test post 3" . to_string ( ) ,
creator_id : inserted_person . id ,
community_id : inserted_community . id ,
language_id : Some ( LanguageId ( 47 ) ) ,
.. PostForm ::default ( )
} ;
let inserted_post = Post ::create ( & conn , & new_post ) . unwrap ( ) ;
let inserted_post = Post ::create ( conn , & new_post ) . unwrap ( ) ;
let new_bot_post = PostForm {
name : bot_post_name ,
name : "test bot post" . to_string ( ) ,
creator_id : inserted_bot . id ,
community_id : inserted_community . id ,
.. PostForm ::default ( )
} ;
let _inserted_bot_post = Post ::create ( & conn , & new_bot_post ) . unwrap ( ) ;
let post_like_form = PostLikeForm {
post_id : inserted_post . id ,
person_id : inserted_person . id ,
score : 1 ,
} ;
let inserted_post_like = PostLike ::like ( & conn , & post_like_form ) . unwrap ( ) ;
let _inserted_bot_post = Post ::create ( conn , & new_bot_post ) . unwrap ( ) ;
let expected_post_like = PostLike {
id : inserted_post_like . id ,
post_id : inserted_post . id ,
person_id : inserted_person . id ,
published : inserted_post_like . published ,
score : 1 ,
} ;
let read_post_listings_with_person = PostQuery ::builder ( )
. conn ( & conn )
. sort ( Some ( SortType ::New ) )
. show_bot_accounts ( Some ( false ) )
. community_id ( Some ( inserted_community . id ) )
. my_person_id ( Some ( inserted_person . id ) )
. build ( )
. list ( )
. unwrap ( ) ;
let read_post_listings_no_person = PostQuery ::builder ( )
. conn ( & conn )
. sort ( Some ( SortType ::New ) )
. community_id ( Some ( inserted_community . id ) )
. build ( )
. list ( )
. unwrap ( ) ;
Data {
inserted_person ,
inserted_blocked_person ,
inserted_bot ,
inserted_community ,
inserted_post ,
}
}
let read_post_listing_no_person = PostView ::read ( & conn , inserted_post . id , None ) . unwrap ( ) ;
let read_post_listing_with_person =
PostView ::read ( & conn , inserted_post . id , Some ( inserted_person . id ) ) . unwrap ( ) ;
fn cleanup ( data : Data , conn : & PgConnection ) {
let num_deleted = Post ::delete ( conn , data . inserted_post . id ) . unwrap ( ) ;
Community ::delete ( conn , data . inserted_community . id ) . unwrap ( ) ;
Person ::delete ( conn , data . inserted_person . id ) . unwrap ( ) ;
Person ::delete ( conn , data . inserted_bot . id ) . unwrap ( ) ;
Person ::delete ( conn , data . inserted_blocked_person . id ) . unwrap ( ) ;
assert_eq! ( 1 , num_deleted ) ;
}
let agg = PostAggregates ::read ( & conn , inserted_post . id ) . unwrap ( ) ;
fn expected_post_listing ( data : & Data , conn : & PgConnection ) -> PostView {
let ( inserted_person , inserted_community , inserted_post ) = (
& data . inserted_person ,
& data . inserted_community ,
& data . inserted_post ,
) ;
let agg = PostAggregates ::read ( conn , inserted_post . id ) . unwrap ( ) ;
// the non person version
let expected_post_listing_no_person = PostView {
PostView {
post : Post {
id : inserted_post . id ,
name : post_name ,
name : inserted_post. name . clone ( ) ,
creator_id : inserted_person . id ,
url : None ,
body : None ,
@ -570,11 +587,12 @@ mod tests {
thumbnail_url : None ,
ap_id : inserted_post . ap_id . to_owned ( ) ,
local : true ,
language_id : LanguageId ( 47 ) ,
} ,
my_vote : None ,
creator : PersonSafe {
id : inserted_person . id ,
name : person_name ,
name : inserted_person. name . clone ( ) ,
display_name : None ,
published : inserted_person . published ,
avatar : None ,
@ -595,7 +613,7 @@ mod tests {
creator_banned_from_community : false ,
community : CommunitySafe {
id : inserted_community . id ,
name : community_name ,
name : inserted_community. name . clone ( ) ,
icon : None ,
removed : false ,
deleted : false ,
@ -614,8 +632,8 @@ mod tests {
id : agg . id ,
post_id : inserted_post . id ,
comments : 0 ,
score : 1 ,
upvotes : 1 ,
score : 0 ,
upvotes : 0 ,
downvotes : 0 ,
stickied : false ,
published : agg . published ,
@ -626,66 +644,239 @@ mod tests {
read : false ,
saved : false ,
creator_blocked : false ,
} ;
language : Language {
id : LanguageId ( 47 ) ,
code : "fr" . to_string ( ) ,
name : "Français" . to_string ( ) ,
} ,
}
}
// Test a community block
let community_block = CommunityBlockForm {
person_id : inserted_person . id ,
community_id : inserted_community . id ,
} ;
CommunityBlock ::block ( & conn , & community_block ) . unwrap ( ) ;
#[ test ]
#[ serial ]
fn post_listing_with_person ( ) {
let conn = establish_unpooled_connection ( ) ;
let data = init_data ( & conn ) ;
let read_post_listing s_with_person_after_block = PostQuery ::builder ( )
let read_post_listing = PostQuery ::builder ( )
. conn ( & conn )
. sort ( Some ( SortType ::New ) )
. community_id ( Some ( data . inserted_community . id ) )
. show_bot_accounts ( Some ( false ) )
. community_id ( Some ( inserted_community . id ) )
. my_person_id ( Some ( inserted_person . id ) )
. my_person_id ( Some ( data . inserted_person . id ) )
. build ( )
. list ( )
. unwrap ( ) ;
// TODO More needs to be added here
let mut expected_post_listing_with_user = expected_post_listing_no_person . to_owned ( ) ;
expected_post_listing_with_user . my_vote = Some ( 1 ) ;
let post_listing_single_with_person =
PostView ::read ( & conn , data . inserted_post . id , Some ( data . inserted_person . id ) ) . unwrap ( ) ;
let like_removed = PostLike ::remove ( & conn , inserted_person . id , inserted_post . id ) . unwrap ( ) ;
let num_deleted = Post ::delete ( & conn , inserted_post . id ) . unwrap ( ) ;
PersonBlock ::unblock ( & conn , & person_block ) . unwrap ( ) ;
CommunityBlock ::unblock ( & conn , & community_block ) . unwrap ( ) ;
Community ::delete ( & conn , inserted_community . id ) . unwrap ( ) ;
Person ::delete ( & conn , inserted_person . id ) . unwrap ( ) ;
Person ::delete ( & conn , inserted_bot . id ) . unwrap ( ) ;
Person ::delete ( & conn , inserted_blocked_person . id ) . unwrap ( ) ;
let mut expected_post_listing_with_user = expected_post_listing ( & data , & conn ) ;
// The with user
assert_eq! (
expected_post_listing_with_user ,
read_post_listings_with_person [ 0 ]
) ;
// Should be only one person, IE the bot post, and blocked should be missing
assert_eq! ( 1 , read_post_listing . len ( ) ) ;
assert_eq! ( expected_post_listing_with_user , read_post_listing [ 0 ] ) ;
expected_post_listing_with_user . my_vote = Some ( 0 ) ;
assert_eq! (
expected_post_listing_with_user ,
read_ post_listing_with_person
post_listing_single _with_person
) ;
// Should be only one person, IE the bot post, and blocked should be missing
assert_eq! ( 1 , read_post_listings_with_person . len ( ) ) ;
let post_listings_with_bots = PostQuery ::builder ( )
. conn ( & conn )
. sort ( Some ( SortType ::New ) )
. community_id ( Some ( data . inserted_community . id ) )
. show_bot_accounts ( Some ( true ) )
. my_person_id ( Some ( data . inserted_person . id ) )
. build ( )
. list ( )
. unwrap ( ) ;
// should include bot post which has "undetermined" language
assert_eq! ( 2 , post_listings_with_bots . len ( ) ) ;
cleanup ( data , & conn ) ;
}
#[ test ]
#[ serial ]
fn post_listing_no_person ( ) {
let conn = establish_unpooled_connection ( ) ;
let data = init_data ( & conn ) ;
let read_post_listing_multiple_no_person = PostQuery ::builder ( )
. conn ( & conn )
. sort ( Some ( SortType ::New ) )
. community_id ( Some ( data . inserted_community . id ) )
. build ( )
. list ( )
. unwrap ( ) ;
let read_post_listing_single_no_person =
PostView ::read ( & conn , data . inserted_post . id , None ) . unwrap ( ) ;
// Without the user
let expected_post_listing_no_person = expected_post_listing ( & data , & conn ) ;
// Should be 2 posts, with the bot post, and the blocked
assert_eq! ( 3 , read_post_listing_multiple_no_person . len ( ) ) ;
assert_eq! (
expected_post_listing_no_person ,
read_post_listing_multiple_no_person [ 1 ]
) ;
assert_eq! (
expected_post_listing_no_person ,
read_post_listings_no_person [ 1 ]
read_post_listing _ single _no_person
) ;
assert_eq! ( expected_post_listing_no_person , read_post_listing_no_person ) ;
// Should be 2 posts, with the bot post, and the blocked
assert_eq! ( 3 , read_post_listings_no_person . len ( ) ) ;
cleanup ( data , & conn ) ;
}
#[ test ]
#[ serial ]
fn post_listing_block_community ( ) {
let conn = establish_unpooled_connection ( ) ;
let data = init_data ( & conn ) ;
let community_block = CommunityBlockForm {
person_id : data . inserted_person . id ,
community_id : data . inserted_community . id ,
} ;
CommunityBlock ::block ( & conn , & community_block ) . unwrap ( ) ;
let read_post_listings_with_person_after_block = PostQuery ::builder ( )
. conn ( & conn )
. sort ( Some ( SortType ::New ) )
. community_id ( Some ( data . inserted_community . id ) )
. show_bot_accounts ( Some ( true ) )
. my_person_id ( Some ( data . inserted_person . id ) )
. build ( )
. list ( )
. unwrap ( ) ;
// Should be 0 posts after the community block
assert_eq! ( 0 , read_post_listings_with_person_after_block . len ( ) ) ;
CommunityBlock ::unblock ( & conn , & community_block ) . unwrap ( ) ;
cleanup ( data , & conn ) ;
}
#[ test ]
#[ serial ]
fn post_listing_like ( ) {
let conn = establish_unpooled_connection ( ) ;
let data = init_data ( & conn ) ;
let post_like_form = PostLikeForm {
post_id : data . inserted_post . id ,
person_id : data . inserted_person . id ,
score : 1 ,
} ;
let inserted_post_like = PostLike ::like ( & conn , & post_like_form ) . unwrap ( ) ;
let expected_post_like = PostLike {
id : inserted_post_like . id ,
post_id : data . inserted_post . id ,
person_id : data . inserted_person . id ,
published : inserted_post_like . published ,
score : 1 ,
} ;
assert_eq! ( expected_post_like , inserted_post_like ) ;
let like_removed =
PostLike ::remove ( & conn , data . inserted_person . id , data . inserted_post . id ) . unwrap ( ) ;
assert_eq! ( 1 , like_removed ) ;
assert_eq! ( 1 , num_deleted ) ;
cleanup ( data , & conn ) ;
}
#[ test ]
#[ serial ]
fn post_listing_person_language ( ) {
let conn = establish_unpooled_connection ( ) ;
let data = init_data ( & conn ) ;
let spanish_id = Language ::read_id_from_code ( & conn , "es" ) . unwrap ( ) ;
let post_spanish = PostForm {
name : "asffgdsc" . to_string ( ) ,
creator_id : data . inserted_person . id ,
community_id : data . inserted_community . id ,
language_id : Some ( spanish_id ) ,
.. PostForm ::default ( )
} ;
Post ::create ( & conn , & post_spanish ) . unwrap ( ) ;
let my_person_form = PersonForm {
name : "Reverie Toiba" . to_string ( ) ,
public_key : Some ( "pubkey" . to_string ( ) ) ,
.. PersonForm ::default ( )
} ;
let my_person = Person ::create ( & conn , & my_person_form ) . unwrap ( ) ;
let local_user_form = LocalUserForm {
person_id : Some ( my_person . id ) ,
password_encrypted : Some ( "" . to_string ( ) ) ,
.. Default ::default ( )
} ;
let local_user = LocalUser ::create ( & conn , & local_user_form ) . unwrap ( ) ;
// Update the users languages to all
LocalUserLanguage ::update_user_languages ( & conn , None , local_user . id ) . unwrap ( ) ;
let post_listings_all = PostQuery ::builder ( )
. conn ( & conn )
. sort ( Some ( SortType ::New ) )
. show_bot_accounts ( Some ( true ) )
. my_person_id ( Some ( my_person . id ) )
. my_local_user_id ( Some ( local_user . id ) )
. build ( )
. list ( )
. unwrap ( ) ;
// no language filters specified, all posts should be returned
assert_eq! ( 4 , post_listings_all . len ( ) ) ;
let french_id = Language ::read_id_from_code ( & conn , "fr" ) . unwrap ( ) ;
LocalUserLanguage ::update_user_languages ( & conn , Some ( vec! [ french_id ] ) , local_user . id ) . unwrap ( ) ;
let post_listing_french = PostQuery ::builder ( )
. conn ( & conn )
. sort ( Some ( SortType ::New ) )
. show_bot_accounts ( Some ( true ) )
. my_person_id ( Some ( my_person . id ) )
. my_local_user_id ( Some ( local_user . id ) )
. build ( )
. list ( )
. unwrap ( ) ;
// only one french language post should be returned
assert_eq! ( 1 , post_listing_french . len ( ) ) ;
assert_eq! ( french_id , post_listing_french [ 0 ] . post . language_id ) ;
let undetermined_id = Language ::read_id_from_code ( & conn , "und" ) . unwrap ( ) ;
LocalUserLanguage ::update_user_languages (
& conn ,
Some ( vec! [ french_id , undetermined_id ] ) ,
local_user . id ,
)
. unwrap ( ) ;
let post_listings_french_und = PostQuery ::builder ( )
. conn ( & conn )
. sort ( Some ( SortType ::New ) )
. show_bot_accounts ( Some ( true ) )
. my_person_id ( Some ( my_person . id ) )
. my_local_user_id ( Some ( local_user . id ) )
. build ( )
. list ( )
. unwrap ( ) ;
// french post and undetermined language post should be returned
assert_eq! ( 2 , post_listings_french_und . len ( ) ) ;
assert_eq! (
undetermined_id ,
post_listings_french_und [ 0 ] . post . language_id
) ;
assert_eq! ( french_id , post_listings_french_und [ 1 ] . post . language_id ) ;
cleanup ( data , & conn ) ;
}
}