2023-09-18 13:44:48 +00:00
use crate ::structs ::{ LocalUserView , PaginationCursor , PostView } ;
2024-03-04 13:19:51 +00:00
use chrono ::{ DateTime , Utc } ;
2022-11-09 10:05:00 +00:00
use diesel ::{
debug_query ,
2023-12-14 12:10:01 +00:00
dsl ::{ exists , not , IntervalDsl } ,
2022-11-09 10:05:00 +00:00
pg ::Pg ,
2024-01-24 15:50:11 +00:00
query_builder ::AsQuery ,
2022-11-09 10:05:00 +00:00
result ::Error ,
2023-12-14 12:10:01 +00:00
sql_types ,
2022-11-09 10:05:00 +00:00
BoolExpressionMethods ,
2023-09-04 09:05:00 +00:00
BoxableExpression ,
2022-11-09 10:05:00 +00:00
ExpressionMethods ,
2023-08-24 15:27:00 +00:00
IntoSql ,
2023-09-18 13:44:48 +00:00
JoinOnDsl ,
2022-11-09 10:05:00 +00:00
NullableExpressionMethods ,
2023-09-18 13:44:48 +00:00
OptionalExtension ,
2022-11-09 10:05:00 +00:00
PgTextExpressionMethods ,
QueryDsl ,
} ;
use diesel_async ::RunQueryDsl ;
2024-01-24 15:50:11 +00:00
use i_love_jesus ::PaginatedQueryBuilder ;
2021-10-16 13:33:38 +00:00
use lemmy_db_schema ::{
2024-01-24 15:50:11 +00:00
aggregates ::structs ::{ post_aggregates_keys as key , PostAggregates } ,
2023-04-12 14:40:59 +00:00
newtypes ::{ CommunityId , LocalUserId , PersonId , PostId } ,
2020-12-10 20:53:49 +00:00
schema ::{
community ,
2021-08-19 20:54:15 +00:00
community_block ,
2020-12-10 20:53:49 +00:00
community_follower ,
2023-07-26 17:51:11 +00:00
community_moderator ,
2021-03-10 22:33:55 +00:00
community_person_ban ,
2023-09-20 09:56:13 +00:00
instance_block ,
2023-11-21 16:20:24 +00:00
local_user ,
2022-08-18 19:11:19 +00:00
local_user_language ,
2021-03-11 04:43:11 +00:00
person ,
2021-08-19 20:54:15 +00:00
person_block ,
2022-09-27 16:45:46 +00:00
person_post_aggregates ,
2020-12-10 20:53:49 +00:00
post ,
2023-12-14 12:10:01 +00:00
post_aggregates ,
2024-02-29 15:42:34 +00:00
post_hide ,
2020-12-10 20:53:49 +00:00
post_like ,
post_read ,
post_saved ,
} ,
2024-02-16 12:24:35 +00:00
source ::site ::Site ,
2023-12-15 10:34:17 +00:00
utils ::{
functions ::coalesce ,
fuzzy_search ,
get_conn ,
limit_and_offset ,
now ,
2024-01-24 15:22:33 +00:00
Commented ,
2023-12-15 10:34:17 +00:00
DbConn ,
DbPool ,
ListFn ,
Queries ,
ReadFn ,
2024-01-24 15:50:11 +00:00
ReverseTimestampKey ,
2023-12-15 10:34:17 +00:00
} ,
2024-01-25 16:04:25 +00:00
CommunityVisibility ,
2022-05-06 20:55:07 +00:00
ListingType ,
SortType ,
2020-12-10 20:53:49 +00:00
} ;
2021-11-23 12:16:47 +00:00
use tracing ::debug ;
2020-12-10 20:53:49 +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 , PostView , ( PostId , Option < PersonId > , bool ) > ,
2024-02-16 12:24:35 +00:00
impl ListFn < ' a , PostView , ( PostQuery < ' a > , & ' a Site ) > ,
2023-07-28 08:36:50 +00:00
> {
2023-09-04 09:05:00 +00:00
let is_creator_banned_from_community = exists (
community_person_ban ::table . filter (
post_aggregates ::community_id
. eq ( community_person_ban ::community_id )
. and ( community_person_ban ::person_id . eq ( post_aggregates ::creator_id ) ) ,
) ,
) ;
2024-03-22 22:31:08 +00:00
let is_local_user_banned_from_community = | person_id | {
exists (
community_person_ban ::table . filter (
post_aggregates ::community_id
. eq ( community_person_ban ::community_id )
. and ( community_person_ban ::person_id . eq ( person_id ) ) ,
) ,
)
} ;
2023-10-24 12:37:03 +00:00
let creator_is_moderator = exists (
community_moderator ::table . filter (
post_aggregates ::community_id
. eq ( community_moderator ::community_id )
. and ( community_moderator ::person_id . eq ( post_aggregates ::creator_id ) ) ,
) ,
) ;
2023-09-04 09:05:00 +00:00
2023-11-21 16:20:24 +00:00
let creator_is_admin = exists (
local_user ::table . filter (
post_aggregates ::creator_id
. eq ( local_user ::person_id )
. and ( local_user ::admin . eq ( true ) ) ,
) ,
) ;
2023-09-04 09:05:00 +00:00
let is_saved = | person_id | {
2024-03-04 13:19:51 +00:00
post_saved ::table
. filter (
2023-09-04 09:05:00 +00:00
post_aggregates ::post_id
. eq ( post_saved ::post_id )
. and ( post_saved ::person_id . eq ( person_id ) ) ,
2024-03-04 13:19:51 +00:00
)
. select ( post_saved ::published . nullable ( ) )
. single_value ( )
2023-09-04 09:05:00 +00:00
} ;
let is_read = | person_id | {
exists (
post_read ::table . filter (
post_aggregates ::post_id
. eq ( post_read ::post_id )
. and ( post_read ::person_id . eq ( person_id ) ) ,
) ,
)
} ;
2024-02-29 15:42:34 +00:00
let is_hidden = | person_id | {
exists (
post_hide ::table . filter (
post_aggregates ::post_id
. eq ( post_hide ::post_id )
. and ( post_hide ::person_id . eq ( person_id ) ) ,
) ,
)
} ;
2023-09-04 09:05:00 +00:00
let is_creator_blocked = | person_id | {
exists (
person_block ::table . filter (
post_aggregates ::creator_id
. eq ( person_block ::target_id )
. and ( person_block ::person_id . eq ( person_id ) ) ,
) ,
)
} ;
let score = | person_id | {
post_like ::table
. filter (
post_aggregates ::post_id
. eq ( post_like ::post_id )
. and ( post_like ::person_id . eq ( person_id ) ) ,
)
. select ( post_like ::score . nullable ( ) )
. single_value ( )
} ;
let all_joins = move | query : post_aggregates ::BoxedQuery < ' a , Pg > ,
2024-03-04 13:19:51 +00:00
my_person_id : Option < PersonId > | {
2024-03-22 22:31:08 +00:00
let is_local_user_banned_from_community_selection : Box <
dyn BoxableExpression < _ , Pg , SqlType = sql_types ::Bool > ,
> = if let Some ( person_id ) = my_person_id {
Box ::new ( is_local_user_banned_from_community ( person_id ) )
} else {
Box ::new ( false . into_sql ::< sql_types ::Bool > ( ) )
} ;
2024-03-04 13:19:51 +00:00
let is_saved_selection : Box <
dyn BoxableExpression < _ , Pg , SqlType = sql_types ::Nullable < sql_types ::Timestamptz > > ,
> = if let Some ( person_id ) = my_person_id {
Box ::new ( is_saved ( person_id ) )
} else {
Box ::new ( None ::< DateTime < Utc > > . into_sql ::< sql_types ::Nullable < sql_types ::Timestamptz > > ( ) )
} ;
2023-09-04 09:05:00 +00:00
let is_read_selection : Box < dyn BoxableExpression < _ , Pg , SqlType = sql_types ::Bool > > =
if let Some ( person_id ) = my_person_id {
Box ::new ( is_read ( person_id ) )
} else {
Box ::new ( false . into_sql ::< sql_types ::Bool > ( ) )
} ;
2024-02-29 15:42:34 +00:00
let is_hidden_selection : Box < dyn BoxableExpression < _ , Pg , SqlType = sql_types ::Bool > > =
if let Some ( person_id ) = my_person_id {
Box ::new ( is_hidden ( person_id ) )
} else {
Box ::new ( false . into_sql ::< sql_types ::Bool > ( ) )
} ;
2023-09-04 09:05:00 +00:00
let is_creator_blocked_selection : Box < dyn BoxableExpression < _ , Pg , SqlType = sql_types ::Bool > > =
if let Some ( person_id ) = my_person_id {
Box ::new ( is_creator_blocked ( person_id ) )
} else {
Box ::new ( false . into_sql ::< sql_types ::Bool > ( ) )
} ;
let subscribed_type_selection : Box <
dyn BoxableExpression < _ , Pg , SqlType = sql_types ::Nullable < sql_types ::Bool > > ,
> = if let Some ( person_id ) = my_person_id {
Box ::new (
community_follower ::table
. filter (
post_aggregates ::community_id
. eq ( community_follower ::community_id )
. and ( community_follower ::person_id . eq ( person_id ) ) ,
)
. select ( community_follower ::pending . nullable ( ) )
. single_value ( ) ,
)
} else {
Box ::new ( None ::< bool > . into_sql ::< sql_types ::Nullable < sql_types ::Bool > > ( ) )
} ;
let score_selection : Box <
dyn BoxableExpression < _ , Pg , SqlType = sql_types ::Nullable < sql_types ::SmallInt > > ,
> = if let Some ( person_id ) = my_person_id {
Box ::new ( score ( person_id ) )
} else {
Box ::new ( None ::< i16 > . into_sql ::< sql_types ::Nullable < sql_types ::SmallInt > > ( ) )
} ;
let read_comments : Box <
dyn BoxableExpression < _ , Pg , SqlType = sql_types ::Nullable < sql_types ::BigInt > > ,
> = if let Some ( person_id ) = my_person_id {
Box ::new (
person_post_aggregates ::table
. filter (
post_aggregates ::post_id
. eq ( person_post_aggregates ::post_id )
. and ( person_post_aggregates ::person_id . eq ( person_id ) ) ,
)
. select ( person_post_aggregates ::read_comments . nullable ( ) )
. single_value ( ) ,
)
} else {
Box ::new ( None ::< i64 > . into_sql ::< sql_types ::Nullable < sql_types ::BigInt > > ( ) )
} ;
2023-07-28 08:36:50 +00:00
query
2021-03-10 22:33:55 +00:00
. inner_join ( person ::table )
2020-12-11 15:27:33 +00:00
. inner_join ( community ::table )
2023-07-20 15:13:21 +00:00
. inner_join ( post ::table )
2023-09-04 09:05:00 +00:00
. select ( (
post ::all_columns ,
person ::all_columns ,
community ::all_columns ,
is_creator_banned_from_community ,
2024-03-22 22:31:08 +00:00
is_local_user_banned_from_community_selection ,
2023-10-24 12:37:03 +00:00
creator_is_moderator ,
2023-11-21 16:20:24 +00:00
creator_is_admin ,
2023-09-04 09:05:00 +00:00
post_aggregates ::all_columns ,
subscribed_type_selection ,
2024-03-04 13:19:51 +00:00
is_saved_selection . is_not_null ( ) ,
2023-09-04 09:05:00 +00:00
is_read_selection ,
2024-02-29 15:42:34 +00:00
is_hidden_selection ,
2023-09-04 09:05:00 +00:00
is_creator_blocked_selection ,
score_selection ,
coalesce (
post_aggregates ::comments . nullable ( ) - read_comments ,
post_aggregates ::comments ,
2020-12-11 15:27:33 +00:00
) ,
2023-09-04 09:05:00 +00:00
) )
2023-07-28 08:36:50 +00:00
} ;
2023-08-11 09:13:14 +00:00
let read =
move | mut conn : DbConn < ' a > ,
( post_id , my_person_id , is_mod_or_admin ) : ( PostId , Option < PersonId > , bool ) | async move {
// The left join below will return None in this case
let person_id_join = my_person_id . unwrap_or ( PersonId ( - 1 ) ) ;
let mut query = all_joins (
post_aggregates ::table
. filter ( post_aggregates ::post_id . eq ( post_id ) )
. into_boxed ( ) ,
my_person_id ,
2023-09-04 09:05:00 +00:00
) ;
2023-03-01 03:46:15 +00:00
2023-08-11 09:13:14 +00:00
// Hide deleted and removed for non-admins or mods
if ! is_mod_or_admin {
query = query
2023-11-23 14:47:49 +00:00
. filter (
community ::removed
. eq ( false )
. or ( post ::creator_id . eq ( person_id_join ) ) ,
)
. filter (
post ::removed
. eq ( false )
. or ( post ::creator_id . eq ( person_id_join ) ) ,
)
2023-08-11 09:13:14 +00:00
// users can see their own deleted posts
. filter (
community ::deleted
. eq ( false )
. or ( post ::creator_id . eq ( person_id_join ) ) ,
)
. filter (
post ::deleted
. eq ( false )
. or ( post ::creator_id . eq ( person_id_join ) ) ,
) ;
}
2023-03-01 03:46:15 +00:00
2024-01-25 16:04:25 +00:00
// Hide posts in local only communities from unauthenticated users
if my_person_id . is_none ( ) {
query = query . filter ( community ::visibility . eq ( CommunityVisibility ::Public ) ) ;
}
2024-01-24 15:22:33 +00:00
Commented ::new ( query )
. text ( " PostView::read " )
2024-04-16 12:48:15 +00:00
. first ( & mut conn )
2024-01-24 15:22:33 +00:00
. await
2023-08-11 09:13:14 +00:00
} ;
2021-01-31 15:29:21 +00:00
2024-02-16 12:24:35 +00:00
let list = move | mut conn : DbConn < ' a > , ( options , site ) : ( PostQuery < ' a > , & ' a Site ) | async move {
2023-12-11 19:36:12 +00:00
let my_person_id = options . local_user . map ( | l | l . person . id ) ;
let my_local_user_id = options . local_user . map ( | l | l . local_user . id ) ;
2020-12-10 20:53:49 +00:00
2020-12-16 18:59:43 +00:00
// The left join below will return None in this case
2023-12-11 19:36:12 +00:00
let person_id_join = my_person_id . unwrap_or ( PersonId ( - 1 ) ) ;
let local_user_id_join = my_local_user_id . unwrap_or ( LocalUserId ( - 1 ) ) ;
2020-12-16 18:59:43 +00:00
2024-03-04 13:19:51 +00:00
let mut query = all_joins ( post_aggregates ::table . into_boxed ( ) , my_person_id ) ;
2020-12-10 20:53:49 +00:00
2023-11-21 13:59:47 +00:00
// hide posts from deleted communities
query = query . filter ( community ::deleted . eq ( false ) ) ;
2023-07-20 14:36:16 +00:00
// only show deleted posts to creator
2023-12-11 19:36:12 +00:00
if let Some ( person_id ) = my_person_id {
2023-11-21 13:59:47 +00:00
query = query . filter ( post ::deleted . eq ( false ) . or ( post ::creator_id . eq ( person_id ) ) ) ;
} else {
query = query . filter ( post ::deleted . eq ( false ) ) ;
2023-03-01 03:46:15 +00:00
}
2023-08-24 09:40:08 +00:00
let is_admin = options
. local_user
. map ( | l | l . local_user . admin )
. unwrap_or ( false ) ;
2023-07-20 14:36:16 +00:00
// only show removed posts to admin when viewing user profile
2023-12-11 19:36:12 +00:00
if ! ( options . creator_id . is_some ( ) & & is_admin ) {
2023-07-20 14:36:16 +00:00
query = query
. filter ( community ::removed . eq ( false ) )
. filter ( post ::removed . eq ( false ) ) ;
}
2023-09-18 13:44:48 +00:00
if let Some ( community_id ) = options . community_id {
query = query . filter ( post_aggregates ::community_id . eq ( community_id ) ) ;
2023-04-25 23:28:06 +00:00
}
2023-07-28 08:36:50 +00:00
if let Some ( creator_id ) = options . creator_id {
2023-07-20 15:13:21 +00:00
query = query . filter ( post_aggregates ::creator_id . eq ( creator_id ) ) ;
2023-04-25 23:28:06 +00:00
}
2023-12-11 19:36:12 +00:00
if let Some ( listing_type ) = options . listing_type {
if let Some ( person_id ) = my_person_id {
let is_subscribed = exists (
community_follower ::table . filter (
post_aggregates ::community_id
. eq ( community_follower ::community_id )
. and ( community_follower ::person_id . eq ( person_id ) ) ,
) ,
) ;
match listing_type {
ListingType ::Subscribed = > query = query . filter ( is_subscribed ) ,
ListingType ::Local = > {
query = query
. filter ( community ::local . eq ( true ) )
. filter ( community ::hidden . eq ( false ) . or ( is_subscribed ) ) ;
}
ListingType ::All = > query = query . filter ( community ::hidden . eq ( false ) . or ( is_subscribed ) ) ,
ListingType ::ModeratorView = > {
query = query . filter ( exists (
community_moderator ::table . filter (
post ::community_id
. eq ( community_moderator ::community_id )
. and ( community_moderator ::person_id . eq ( person_id ) ) ,
) ,
) ) ;
}
2023-08-31 11:07:45 +00:00
}
2022-02-18 02:30:47 +00:00
}
2023-12-11 19:36:12 +00:00
// If your person_id is missing, only show local
else {
match listing_type {
ListingType ::Local = > {
query = query
. filter ( community ::local . eq ( true ) )
. filter ( community ::hidden . eq ( false ) ) ;
}
_ = > query = query . filter ( community ::hidden . eq ( false ) ) ,
2023-11-22 14:41:52 +00:00
}
}
2023-12-11 19:36:12 +00:00
} else {
query = query . filter ( community ::hidden . eq ( false ) ) ;
2020-12-10 20:53:49 +00:00
}
2022-07-29 10:57:39 +00:00
2023-09-18 13:44:48 +00:00
if let Some ( url_search ) = & options . url_search {
2020-12-10 20:53:49 +00:00
query = query . filter ( post ::url . eq ( url_search ) ) ;
}
2023-09-18 13:44:48 +00:00
if let Some ( search_term ) = & options . search_term {
let searcher = fuzzy_search ( search_term ) ;
2024-04-30 10:24:18 +00:00
query = query
. filter (
post ::name
. ilike ( searcher . clone ( ) )
. or ( post ::body . ilike ( searcher ) ) ,
)
. filter ( not ( post ::removed . or ( post ::deleted ) ) ) ;
2020-12-10 20:53:49 +00:00
}
2024-02-16 12:24:35 +00:00
// If there is a content warning, show nsfw content by default.
let has_content_warning = site . content_warning . is_some ( ) ;
2023-07-28 08:36:50 +00:00
if ! options
2023-07-20 14:36:16 +00:00
. local_user
. map ( | l | l . local_user . show_nsfw )
2024-02-16 12:24:35 +00:00
. unwrap_or ( has_content_warning )
2023-07-20 14:36:16 +00:00
{
2020-12-16 18:59:43 +00:00
query = query
. filter ( post ::nsfw . eq ( false ) )
. filter ( community ::nsfw . eq ( false ) ) ;
} ;
2023-07-28 08:36:50 +00:00
if ! options
2023-07-20 14:36:16 +00:00
. local_user
. map ( | l | l . local_user . show_bot_accounts )
. unwrap_or ( true )
{
2021-04-21 21:41:14 +00:00
query = query . filter ( person ::bot_account . eq ( false ) ) ;
} ;
2024-03-04 13:19:51 +00:00
// If its saved only, then filter, and order by the saved time, not the comment creation time.
2023-12-11 19:36:12 +00:00
if let ( true , Some ( person_id ) ) = ( options . saved_only , my_person_id ) {
2024-03-04 13:19:51 +00:00
query = query
. filter ( is_saved ( person_id ) . is_not_null ( ) )
. then_order_by ( is_saved ( person_id ) . desc ( ) ) ;
2021-10-14 17:03:12 +00:00
}
// Only hide the read posts, if the saved_only is false. Otherwise ppl with the hide_read
// setting wont be able to see saved posts.
2023-07-28 08:36:50 +00:00
else if ! options
2023-07-20 14:36:16 +00:00
. local_user
. map ( | l | l . local_user . show_read_posts )
. unwrap_or ( true )
{
2023-08-02 16:31:28 +00:00
// Do not hide read posts when it is a user profile view
2023-12-19 09:46:41 +00:00
// Or, only hide read posts on non-profile views
if let ( None , Some ( person_id ) ) = ( options . creator_id , my_person_id ) {
2023-09-04 09:05:00 +00:00
query = query . filter ( not ( is_read ( person_id ) ) ) ;
2023-08-02 16:31:28 +00:00
}
2021-10-14 17:03:12 +00:00
}
2020-12-16 18:59:43 +00:00
2024-02-29 15:42:34 +00:00
if ! options . show_hidden {
// If a creator id isn't given (IE its on home or community pages), hide the hidden posts
if let ( None , Some ( person_id ) ) = ( options . creator_id , my_person_id ) {
query = query . filter ( not ( is_hidden ( person_id ) ) ) ;
}
}
2024-05-01 03:26:55 +00:00
if let Some ( my_id ) = my_person_id {
let not_creator_filter = post_aggregates ::creator_id . ne ( my_id ) ;
2023-09-04 09:05:00 +00:00
if options . liked_only {
2024-05-01 03:26:55 +00:00
query = query . filter ( not_creator_filter ) . filter ( score ( my_id ) . eq ( 1 ) ) ;
2023-09-04 09:05:00 +00:00
} else if options . disliked_only {
2024-05-01 03:26:55 +00:00
query = query . filter ( not_creator_filter ) . filter ( score ( my_id ) . eq ( - 1 ) ) ;
2023-09-04 09:05:00 +00:00
}
} ;
2023-08-08 09:40:28 +00:00
2024-01-25 16:04:25 +00:00
// Hide posts in local only communities from unauthenticated users
if options . local_user . is_none ( ) {
query = query . filter ( community ::visibility . eq ( CommunityVisibility ::Public ) ) ;
}
2023-08-31 11:07:45 +00:00
// Dont filter blocks or missing languages for moderator view type
2023-09-04 09:05:00 +00:00
if let ( Some ( person_id ) , false ) = (
2023-12-11 19:36:12 +00:00
my_person_id ,
2023-09-04 09:05:00 +00:00
options . listing_type . unwrap_or_default ( ) = = ListingType ::ModeratorView ,
) {
2022-08-19 14:27:39 +00:00
// Filter out the rows with missing languages
2023-09-04 09:05:00 +00:00
query = query . filter ( exists (
local_user_language ::table . filter (
post ::language_id
. eq ( local_user_language ::language_id )
. and ( local_user_language ::local_user_id . eq ( local_user_id_join ) ) ,
) ,
) ) ;
2022-08-18 19:11:19 +00:00
2023-09-20 09:56:13 +00:00
// Don't show blocked instances, communities or persons
2023-09-04 09:05:00 +00:00
query = query . filter ( not ( exists (
community_block ::table . filter (
post_aggregates ::community_id
. eq ( community_block ::community_id )
. and ( community_block ::person_id . eq ( person_id_join ) ) ,
) ,
) ) ) ;
2023-09-20 09:56:13 +00:00
query = query . filter ( not ( exists (
instance_block ::table . filter (
post_aggregates ::instance_id
. eq ( instance_block ::instance_id )
. and ( instance_block ::person_id . eq ( person_id_join ) ) ,
) ,
) ) ) ;
2023-09-04 09:05:00 +00:00
query = query . filter ( not ( is_creator_blocked ( person_id ) ) ) ;
2021-08-19 20:54:15 +00:00
}
2024-01-24 15:50:11 +00:00
let ( limit , offset ) = limit_and_offset ( options . page , options . limit ) ? ;
query = query . limit ( limit ) . offset ( offset ) ;
2023-12-14 12:10:01 +00:00
2024-01-24 15:50:11 +00:00
let mut query = PaginatedQueryBuilder ::new ( query ) ;
2023-12-14 12:10:01 +00:00
2024-01-24 15:50:11 +00:00
let page_after = options . page_after . map ( | c | c . 0 ) ;
let page_before_or_equal = options . page_before_or_equal . map ( | c | c . 0 ) ;
if options . page_back {
query = query
. before ( page_after )
. after_or_equal ( page_before_or_equal )
. limit_and_offset_from_end ( ) ;
} else {
query = query
. after ( page_after )
. before_or_equal ( page_before_or_equal ) ;
2023-12-14 12:10:01 +00:00
}
2024-01-24 15:50:11 +00:00
// featured posts first
query = if options . community_id . is_none ( ) | | options . community_id_just_for_prefetch {
query . then_desc ( key ::featured_local )
} else {
query . then_desc ( key ::featured_community )
} ;
2023-12-14 12:10:01 +00:00
2024-01-24 15:50:11 +00:00
let time = | interval | post_aggregates ::published . gt ( now ( ) - interval ) ;
// then use the main sort
query = match options . sort . unwrap_or ( SortType ::Hot ) {
SortType ::Active = > query . then_desc ( key ::hot_rank_active ) ,
SortType ::Hot = > query . then_desc ( key ::hot_rank ) ,
SortType ::Scaled = > query . then_desc ( key ::scaled_rank ) ,
SortType ::Controversial = > query . then_desc ( key ::controversy_rank ) ,
SortType ::New = > query . then_desc ( key ::published ) ,
SortType ::Old = > query . then_desc ( ReverseTimestampKey ( key ::published ) ) ,
SortType ::NewComments = > query . then_desc ( key ::newest_comment_time ) ,
SortType ::MostComments = > query . then_desc ( key ::comments ) ,
SortType ::TopAll = > query . then_desc ( key ::score ) ,
SortType ::TopYear = > query . then_desc ( key ::score ) . filter ( time ( 1. years ( ) ) ) ,
SortType ::TopMonth = > query . then_desc ( key ::score ) . filter ( time ( 1. months ( ) ) ) ,
SortType ::TopWeek = > query . then_desc ( key ::score ) . filter ( time ( 1. weeks ( ) ) ) ,
SortType ::TopDay = > query . then_desc ( key ::score ) . filter ( time ( 1. days ( ) ) ) ,
SortType ::TopHour = > query . then_desc ( key ::score ) . filter ( time ( 1. hours ( ) ) ) ,
SortType ::TopSixHour = > query . then_desc ( key ::score ) . filter ( time ( 6. hours ( ) ) ) ,
SortType ::TopTwelveHour = > query . then_desc ( key ::score ) . filter ( time ( 12. hours ( ) ) ) ,
SortType ::TopThreeMonths = > query . then_desc ( key ::score ) . filter ( time ( 3. months ( ) ) ) ,
SortType ::TopSixMonths = > query . then_desc ( key ::score ) . filter ( time ( 6. months ( ) ) ) ,
SortType ::TopNineMonths = > query . then_desc ( key ::score ) . filter ( time ( 9. months ( ) ) ) ,
} ;
2023-12-14 12:10:01 +00:00
2024-01-24 15:50:11 +00:00
// use publish as fallback. especially useful for hot rank which reaches zero after some days.
// necessary because old posts can be fetched over federation and inserted with high post id
query = match options . sort . unwrap_or ( SortType ::Hot ) {
// A second time-based sort would not be very useful
SortType ::New | SortType ::Old | SortType ::NewComments = > query ,
_ = > query . then_desc ( key ::published ) ,
} ;
2023-12-14 12:10:01 +00:00
2024-01-24 15:50:11 +00:00
// finally use unique post id as tie breaker
query = query . then_desc ( key ::post_id ) ;
2020-12-10 20:53:49 +00:00
2024-01-24 15:50:11 +00:00
// Not done by debug_query
let query = query . as_query ( ) ;
2021-01-06 18:23:05 +00:00
debug! ( " Post View Query: {:?} " , debug_query ::< Pg , _ > ( & query ) ) ;
2024-01-24 15:22:33 +00:00
Commented ::new ( query )
. text ( " PostQuery::list " )
. text_if (
" getting upper bound for next query " ,
options . community_id_just_for_prefetch ,
)
. load ::< PostView > ( & mut conn )
. await
2023-07-28 08:36:50 +00:00
} ;
Queries ::new ( read , list )
}
2020-12-10 20:53:49 +00:00
2023-07-28 08:36:50 +00:00
impl PostView {
pub async fn read (
pool : & mut DbPool < '_ > ,
post_id : PostId ,
my_person_id : Option < PersonId > ,
2023-08-11 09:13:14 +00:00
is_mod_or_admin : bool ,
2024-04-16 12:48:15 +00:00
) -> Result < Option < Self > , Error > {
queries ( )
2023-07-28 08:36:50 +00:00
. read ( pool , ( post_id , my_person_id , is_mod_or_admin ) )
2024-04-16 12:48:15 +00:00
. await
2023-07-28 08:36:50 +00:00
}
}
2023-09-18 13:44:48 +00:00
impl PaginationCursor {
// get cursor for page that starts immediately after the given post
pub fn after_post ( view : & PostView ) -> PaginationCursor {
// hex encoding to prevent ossification
PaginationCursor ( format! ( " P {:x} " , view . counts . post_id . 0 ) )
}
pub async fn read ( & self , pool : & mut DbPool < '_ > ) -> Result < PaginationCursorData , Error > {
2024-04-16 12:48:15 +00:00
let err_msg = | | Error ::QueryBuilderError ( " Could not parse pagination token " . into ( ) ) ;
let token = PostAggregates ::read (
pool ,
PostId (
self
. 0
. get ( 1 .. )
. and_then ( | e | i32 ::from_str_radix ( e , 16 ) . ok ( ) )
. ok_or_else ( err_msg ) ? ,
) ,
)
. await ?
. ok_or_else ( err_msg ) ? ;
Ok ( PaginationCursorData ( token ) )
2023-09-18 13:44:48 +00:00
}
}
// currently we use a postaggregates struct as the pagination token.
// we only use some of the properties of the post aggregates, depending on which sort type we page by
#[ derive(Clone) ]
pub struct PaginationCursorData ( PostAggregates ) ;
2024-02-16 12:24:35 +00:00
#[ derive(Clone, Default) ]
2023-07-28 08:36:50 +00:00
pub struct PostQuery < ' a > {
pub listing_type : Option < ListingType > ,
pub sort : Option < SortType > ,
pub creator_id : Option < PersonId > ,
pub community_id : Option < CommunityId > ,
2023-09-18 13:44:48 +00:00
// if true, the query should be handled as if community_id was not given except adding the literal filter
pub community_id_just_for_prefetch : bool ,
2023-07-28 08:36:50 +00:00
pub local_user : Option < & ' a LocalUserView > ,
pub search_term : Option < String > ,
pub url_search : Option < String > ,
2023-08-11 09:13:14 +00:00
pub saved_only : bool ,
pub liked_only : bool ,
pub disliked_only : bool ,
2023-07-28 08:36:50 +00:00
pub page : Option < i64 > ,
pub limit : Option < i64 > ,
2023-09-18 13:44:48 +00:00
pub page_after : Option < PaginationCursorData > ,
pub page_before_or_equal : Option < PaginationCursorData > ,
2024-01-24 15:50:11 +00:00
pub page_back : bool ,
2024-02-29 15:42:34 +00:00
pub show_hidden : bool ,
2023-07-28 08:36:50 +00:00
}
impl < ' a > PostQuery < ' a > {
2023-09-18 13:44:48 +00:00
async fn prefetch_upper_bound_for_page_before (
& self ,
2024-02-16 12:24:35 +00:00
site : & Site ,
2023-09-18 13:44:48 +00:00
pool : & mut DbPool < '_ > ,
) -> Result < Option < PostQuery < ' a > > , Error > {
2024-04-17 12:35:54 +00:00
// first get one page for the most popular community to get an upper bound for the page end for the real query
2023-09-18 13:44:48 +00:00
// the reason this is needed is that when fetching posts for a single community PostgreSQL can optimize
// the query to use an index on e.g. (=, >=, >=, >=) and fetch only LIMIT rows
// but for the followed-communities query it has to query the index on (IN, >=, >=, >=)
// which it currently can't do at all (as of PG 16). see the discussion here:
// https://github.com/LemmyNet/lemmy/issues/2877#issuecomment-1673597190
//
// the results are correct no matter which community we fetch these for, since it basically covers the "worst case" of the whole page consisting of posts from one community
// but using the largest community decreases the pagination-frame so make the real query more efficient.
use lemmy_db_schema ::schema ::{
community_aggregates ::dsl ::{ community_aggregates , community_id , users_active_month } ,
community_follower ::dsl ::{
community_follower ,
community_id as follower_community_id ,
person_id ,
} ,
} ;
let ( limit , offset ) = limit_and_offset ( self . page , self . limit ) ? ;
2023-10-11 14:53:18 +00:00
if offset ! = 0 & & self . page_after . is_some ( ) {
2023-09-18 13:44:48 +00:00
return Err ( Error ::QueryBuilderError (
" legacy pagination cannot be combined with v2 pagination " . into ( ) ,
) ) ;
}
let self_person_id = self
. local_user
. expect ( " part of the above if " )
. local_user
. person_id ;
let largest_subscribed = {
let conn = & mut get_conn ( pool ) . await ? ;
community_follower
. filter ( person_id . eq ( self_person_id ) )
. inner_join ( community_aggregates . on ( community_id . eq ( follower_community_id ) ) )
. order_by ( users_active_month . desc ( ) )
. select ( community_id )
. limit ( 1 )
. get_result ::< CommunityId > ( conn )
. await
. optional ( ) ?
} ;
let Some ( largest_subscribed ) = largest_subscribed else {
// nothing subscribed to? no posts
return Ok ( None ) ;
} ;
let mut v = queries ( )
. list (
pool ,
2024-02-16 12:24:35 +00:00
(
PostQuery {
community_id : Some ( largest_subscribed ) ,
community_id_just_for_prefetch : true ,
.. self . clone ( )
} ,
site ,
) ,
2023-09-18 13:44:48 +00:00
)
. await ? ;
// take last element of array. if this query returned less than LIMIT elements,
// the heuristic is invalid since we can't guarantee the full query will return >= LIMIT results (return original query)
if ( v . len ( ) as i64 ) < limit {
Ok ( Some ( self . clone ( ) ) )
} else {
2024-01-24 15:50:11 +00:00
let item = if self . page_back {
// for backward pagination, get first element instead
v . into_iter ( ) . next ( )
} else {
v . pop ( )
} ;
let limit_cursor = Some ( PaginationCursorData ( item . expect ( " else case " ) . counts ) ) ;
2023-09-18 13:44:48 +00:00
Ok ( Some ( PostQuery {
2024-01-24 15:50:11 +00:00
page_before_or_equal : limit_cursor ,
2023-09-18 13:44:48 +00:00
.. self . clone ( )
} ) )
}
}
2024-02-16 12:24:35 +00:00
pub async fn list ( self , site : & Site , pool : & mut DbPool < '_ > ) -> Result < Vec < PostView > , Error > {
2023-09-18 13:44:48 +00:00
if self . listing_type = = Some ( ListingType ::Subscribed )
& & self . community_id . is_none ( )
& & self . local_user . is_some ( )
& & self . page_before_or_equal . is_none ( )
{
2024-02-16 12:24:35 +00:00
if let Some ( query ) = self
. prefetch_upper_bound_for_page_before ( site , pool )
. await ?
{
queries ( ) . list ( pool , ( query , site ) ) . await
2023-09-18 13:44:48 +00:00
} else {
Ok ( vec! [ ] )
}
} else {
2024-02-16 12:24:35 +00:00
queries ( ) . list ( pool , ( self , site ) ) . await
2023-09-18 13:44:48 +00:00
}
2020-12-10 20:53:49 +00:00
}
}
2020-12-11 15:27:33 +00:00
#[ cfg(test) ]
mod tests {
2023-07-20 14:36:16 +00:00
use crate ::{
2023-12-14 12:10:01 +00:00
post_view ::{ PaginationCursorData , PostQuery , PostView } ,
2023-07-20 14:36:16 +00:00
structs ::LocalUserView ,
} ;
2023-12-14 12:10:01 +00:00
use chrono ::Utc ;
2021-10-16 13:33:38 +00:00
use lemmy_db_schema ::{
2022-05-03 17:44:13 +00:00
aggregates ::structs ::PostAggregates ,
2023-03-01 00:49:31 +00:00
impls ::actor_language ::UNDETERMINED_ID ,
2024-02-14 09:49:55 +00:00
newtypes ::LanguageId ,
2021-10-16 13:33:38 +00:00
source ::{
2022-10-06 18:27:58 +00:00
actor_language ::LocalUserLanguage ,
2023-12-14 12:10:01 +00:00
comment ::{ Comment , CommentInsertForm } ,
2024-01-25 16:04:25 +00:00
community ::{
Community ,
CommunityInsertForm ,
CommunityModerator ,
CommunityModeratorForm ,
2024-03-22 22:31:08 +00:00
CommunityPersonBan ,
CommunityPersonBanForm ,
2024-01-25 16:04:25 +00:00
CommunityUpdateForm ,
} ,
2021-10-16 13:33:38 +00:00
community_block ::{ CommunityBlock , CommunityBlockForm } ,
2022-10-27 09:24:07 +00:00
instance ::Instance ,
2023-09-20 09:56:13 +00:00
instance_block ::{ InstanceBlock , InstanceBlockForm } ,
2022-08-18 19:11:19 +00:00
language ::Language ,
2022-10-27 09:24:07 +00:00
local_user ::{ LocalUser , LocalUserInsertForm , LocalUserUpdateForm } ,
2024-03-13 16:10:58 +00:00
local_user_vote_display_mode ::LocalUserVoteDisplayMode ,
2023-03-01 17:19:46 +00:00
person ::{ Person , PersonInsertForm } ,
2021-10-16 13:33:38 +00:00
person_block ::{ PersonBlock , PersonBlockForm } ,
2024-02-29 15:42:34 +00:00
post ::{ Post , PostHide , PostInsertForm , PostLike , PostLikeForm , PostRead , PostUpdateForm } ,
2024-02-16 12:24:35 +00:00
site ::Site ,
2021-10-16 13:33:38 +00:00
} ,
2024-03-22 22:31:08 +00:00
traits ::{ Bannable , Blockable , Crud , Joinable , Likeable } ,
2024-01-25 16:04:25 +00:00
utils ::{ build_db_pool , build_db_pool_for_tests , DbPool , RANK_DEFAULT } ,
CommunityVisibility ,
2022-05-06 20:55:07 +00:00
SortType ,
2022-06-22 12:05:41 +00:00
SubscribedType ,
2020-12-11 15:27:33 +00:00
} ;
2024-04-16 12:48:15 +00:00
use lemmy_utils ::error ::{ LemmyErrorType , LemmyResult } ;
2024-01-09 17:19:25 +00:00
use pretty_assertions ::assert_eq ;
2021-02-25 19:43:39 +00:00
use serial_test ::serial ;
2023-12-19 09:46:41 +00:00
use std ::{ collections ::HashSet , time ::Duration } ;
2024-02-16 12:24:35 +00:00
use url ::Url ;
2020-12-11 15:27:33 +00:00
2024-01-09 17:19:25 +00:00
const POST_BY_BLOCKED_PERSON : & str = " post by blocked person " ;
const POST_BY_BOT : & str = " post by bot " ;
const POST : & str = " post " ;
fn names ( post_views : & [ PostView ] ) -> Vec < & str > {
post_views . iter ( ) . map ( | i | i . post . name . as_str ( ) ) . collect ( )
}
2022-08-18 19:11:19 +00:00
struct Data {
2022-10-27 09:24:07 +00:00
inserted_instance : Instance ,
2023-07-20 14:36:16 +00:00
local_user_view : LocalUserView ,
2023-12-13 10:09:10 +00:00
blocked_local_user_view : LocalUserView ,
2022-08-18 19:11:19 +00:00
inserted_bot : Person ,
inserted_community : Community ,
inserted_post : Post ,
2023-12-11 19:36:12 +00:00
inserted_bot_post : Post ,
2024-02-16 12:24:35 +00:00
site : Site ,
2022-08-18 19:11:19 +00:00
}
2020-12-11 15:27:33 +00:00
2024-01-09 17:19:25 +00:00
impl Data {
fn default_post_query ( & self ) -> PostQuery < '_ > {
PostQuery {
sort : Some ( SortType ::New ) ,
local_user : Some ( & self . local_user_view ) ,
.. Default ::default ( )
}
}
}
2020-12-11 15:27:33 +00:00
2024-01-09 17:19:25 +00:00
async fn init_data ( pool : & mut DbPool < '_ > ) -> LemmyResult < Data > {
let inserted_instance = Instance ::read_or_create ( pool , " my_domain.tld " . to_string ( ) ) . await ? ;
2021-04-21 21:41:14 +00:00
2024-02-14 09:49:55 +00:00
let new_person = PersonInsertForm ::test_form ( inserted_instance . id , " tegan " ) ;
2024-01-09 17:19:25 +00:00
let inserted_person = Person ::create ( pool , & new_person ) . await ? ;
let local_user_form = LocalUserInsertForm {
admin : Some ( true ) ,
2024-02-14 09:49:55 +00:00
.. LocalUserInsertForm ::test_form ( inserted_person . id )
2024-01-09 17:19:25 +00:00
} ;
2024-03-25 20:02:12 +00:00
let inserted_local_user = LocalUser ::create ( pool , & local_user_form , vec! [ ] ) . await ? ;
2024-01-09 17:19:25 +00:00
let new_bot = PersonInsertForm {
bot_account : Some ( true ) ,
2024-02-14 09:49:55 +00:00
.. PersonInsertForm ::test_form ( inserted_instance . id , " mybot " )
2024-01-09 17:19:25 +00:00
} ;
let inserted_bot = Person ::create ( pool , & new_bot ) . await ? ;
2021-04-21 21:41:14 +00:00
2022-10-27 09:24:07 +00:00
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 ( ) ;
2020-12-11 15:27:33 +00:00
2024-01-09 17:19:25 +00:00
let inserted_community = Community ::create ( pool , & new_community ) . await ? ;
2020-12-11 15:27:33 +00:00
2021-08-19 20:54:15 +00:00
// Test a person block, make sure the post query doesn't include their post
2024-02-14 09:49:55 +00:00
let blocked_person = PersonInsertForm ::test_form ( inserted_instance . id , " john " ) ;
2021-08-19 20:54:15 +00:00
2024-01-09 17:19:25 +00:00
let inserted_blocked_person = Person ::create ( pool , & blocked_person ) . await ? ;
2021-08-19 20:54:15 +00:00
2024-02-14 09:49:55 +00:00
let inserted_blocked_local_user = LocalUser ::create (
pool ,
& LocalUserInsertForm ::test_form ( inserted_blocked_person . id ) ,
2024-03-25 20:02:12 +00:00
vec! [ ] ,
2024-02-14 09:49:55 +00:00
)
. await ? ;
2023-12-13 10:09:10 +00:00
2022-10-27 09:24:07 +00:00
let post_from_blocked_person = PostInsertForm ::builder ( )
2024-01-09 17:19:25 +00:00
. name ( POST_BY_BLOCKED_PERSON . to_string ( ) )
2022-10-27 09:24:07 +00:00
. creator_id ( inserted_blocked_person . id )
. community_id ( inserted_community . id )
. language_id ( Some ( LanguageId ( 1 ) ) )
. build ( ) ;
2021-08-19 20:54:15 +00:00
2024-01-09 17:19:25 +00:00
Post ::create ( pool , & post_from_blocked_person ) . await ? ;
2021-08-19 20:54:15 +00:00
// block that person
let person_block = PersonBlockForm {
person_id : inserted_person . id ,
target_id : inserted_blocked_person . id ,
} ;
2024-01-09 17:19:25 +00:00
PersonBlock ::block ( pool , & person_block ) . await ? ;
2021-08-19 20:54:15 +00:00
// A sample post
2022-10-27 09:24:07 +00:00
let new_post = PostInsertForm ::builder ( )
2024-01-09 17:19:25 +00:00
. name ( POST . to_string ( ) )
2022-10-27 09:24:07 +00:00
. creator_id ( inserted_person . id )
. community_id ( inserted_community . id )
. language_id ( Some ( LanguageId ( 47 ) ) )
. build ( ) ;
2020-12-11 15:27:33 +00:00
2024-01-09 17:19:25 +00:00
let inserted_post = Post ::create ( pool , & new_post ) . await ? ;
2020-12-11 15:27:33 +00:00
2022-10-27 09:24:07 +00:00
let new_bot_post = PostInsertForm ::builder ( )
2024-01-09 17:19:25 +00:00
. name ( POST_BY_BOT . to_string ( ) )
2022-10-27 09:24:07 +00:00
. creator_id ( inserted_bot . id )
. community_id ( inserted_community . id )
. build ( ) ;
2021-04-21 21:41:14 +00:00
2024-01-09 17:19:25 +00:00
let inserted_bot_post = Post ::create ( pool , & new_bot_post ) . await ? ;
2023-07-20 14:36:16 +00:00
let local_user_view = LocalUserView {
local_user : inserted_local_user ,
2024-03-13 16:10:58 +00:00
local_user_vote_display_mode : LocalUserVoteDisplayMode ::default ( ) ,
2023-07-20 14:36:16 +00:00
person : inserted_person ,
counts : Default ::default ( ) ,
} ;
2023-12-13 10:09:10 +00:00
let blocked_local_user_view = LocalUserView {
local_user : inserted_blocked_local_user ,
2024-03-13 16:10:58 +00:00
local_user_vote_display_mode : LocalUserVoteDisplayMode ::default ( ) ,
2023-12-13 10:09:10 +00:00
person : inserted_blocked_person ,
counts : Default ::default ( ) ,
} ;
2020-12-11 15:27:33 +00:00
2024-02-16 12:24:35 +00:00
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 ::parse ( " http://example.com " ) ? . into ( ) ,
last_refreshed_at : Default ::default ( ) ,
inbox_url : Url ::parse ( " http://example.com " ) ? . into ( ) ,
private_key : None ,
public_key : String ::new ( ) ,
instance_id : Default ::default ( ) ,
content_warning : None ,
} ;
2024-01-09 17:19:25 +00:00
Ok ( Data {
2022-10-27 09:24:07 +00:00
inserted_instance ,
2023-07-20 14:36:16 +00:00
local_user_view ,
2023-12-13 10:09:10 +00:00
blocked_local_user_view ,
2022-08-18 19:11:19 +00:00
inserted_bot ,
inserted_community ,
inserted_post ,
2023-12-11 19:36:12 +00:00
inserted_bot_post ,
2024-02-16 12:24:35 +00:00
site ,
2024-01-09 17:19:25 +00:00
} )
2022-08-18 19:11:19 +00:00
}
2020-12-11 15:27:33 +00:00
2022-11-09 10:05:00 +00:00
#[ tokio::test ]
2022-08-18 19:11:19 +00:00
#[ serial ]
2024-01-09 17:19:25 +00:00
async fn post_listing_with_person ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
2023-07-11 13:09:59 +00:00
let pool = & mut pool . into ( ) ;
2024-01-09 17:19:25 +00:00
let mut data = init_data ( pool ) . await ? ;
2021-08-19 20:54:15 +00:00
2023-08-08 09:41:41 +00:00
let local_user_form = LocalUserUpdateForm {
show_bot_accounts : Some ( false ) ,
.. Default ::default ( )
} ;
2024-05-15 02:37:30 +00:00
LocalUser ::update ( pool , data . local_user_view . local_user . id , & local_user_form ) . await ? ;
data . local_user_view . local_user . show_bot_accounts = false ;
2022-08-19 14:27:39 +00:00
2023-07-17 10:20:25 +00:00
let read_post_listing = PostQuery {
2024-01-09 17:19:25 +00:00
community_id : Some ( data . inserted_community . id ) ,
.. data . default_post_query ( )
2023-07-17 10:20:25 +00:00
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ? ;
2020-12-11 15:27:33 +00:00
2023-03-01 03:46:15 +00:00
let post_listing_single_with_person = PostView ::read (
pool ,
data . inserted_post . id ,
2023-07-20 14:36:16 +00:00
Some ( data . local_user_view . person . id ) ,
2023-08-11 09:13:14 +00:00
false ,
2023-03-01 03:46:15 +00:00
)
2024-04-16 12:48:15 +00:00
. await ?
. ok_or ( LemmyErrorType ::CouldntFindPost ) ? ;
2020-12-11 15:27:33 +00:00
2024-01-09 17:19:25 +00:00
let expected_post_listing_with_user = expected_post_view ( & data , pool ) . await ? ;
2020-12-11 15:27:33 +00:00
2022-08-18 19:11:19 +00:00
// Should be only one person, IE the bot post, and blocked should be missing
2024-01-09 17:19:25 +00:00
assert_eq! (
vec! [ post_listing_single_with_person . clone ( ) ] ,
read_post_listing
) ;
2021-03-11 04:43:11 +00:00
assert_eq! (
expected_post_listing_with_user ,
2022-08-18 19:11:19 +00:00
post_listing_single_with_person
2021-03-11 04:43:11 +00:00
) ;
2021-04-21 21:41:14 +00:00
2023-08-08 09:41:41 +00:00
let local_user_form = LocalUserUpdateForm {
show_bot_accounts : Some ( true ) ,
.. Default ::default ( )
} ;
2024-05-15 02:37:30 +00:00
LocalUser ::update ( pool , data . local_user_view . local_user . id , & local_user_form ) . await ? ;
data . local_user_view . local_user . show_bot_accounts = true ;
2022-08-19 14:27:39 +00:00
2023-07-17 10:20:25 +00:00
let post_listings_with_bots = PostQuery {
2024-01-09 17:19:25 +00:00
community_id : Some ( data . inserted_community . id ) ,
.. data . default_post_query ( )
2023-07-17 10:20:25 +00:00
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ? ;
2022-08-18 19:11:19 +00:00
// should include bot post which has "undetermined" language
2024-01-09 17:19:25 +00:00
assert_eq! ( vec! [ POST_BY_BOT , POST ] , names ( & post_listings_with_bots ) ) ;
2022-08-18 19:11:19 +00:00
2024-01-09 17:19:25 +00:00
cleanup ( data , pool ) . await
2022-08-18 19:11:19 +00:00
}
2022-11-09 10:05:00 +00:00
#[ tokio::test ]
2022-08-18 19:11:19 +00:00
#[ serial ]
2024-01-09 17:19:25 +00:00
async fn post_listing_no_person ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
2023-07-11 13:09:59 +00:00
let pool = & mut pool . into ( ) ;
2024-01-09 17:19:25 +00:00
let data = init_data ( pool ) . await ? ;
2022-08-18 19:11:19 +00:00
2023-07-17 10:20:25 +00:00
let read_post_listing_multiple_no_person = PostQuery {
2024-01-09 17:19:25 +00:00
community_id : Some ( data . inserted_community . id ) ,
local_user : None ,
.. data . default_post_query ( )
2023-07-17 10:20:25 +00:00
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ? ;
2022-08-18 19:11:19 +00:00
2023-03-01 03:46:15 +00:00
let read_post_listing_single_no_person =
2024-04-16 12:48:15 +00:00
PostView ::read ( pool , data . inserted_post . id , None , false )
. await ?
. ok_or ( LemmyErrorType ::CouldntFindPost ) ? ;
2020-12-11 15:27:33 +00:00
2024-01-09 17:19:25 +00:00
let expected_post_listing_no_person = expected_post_view ( & data , pool ) . await ? ;
2022-08-18 19:11:19 +00:00
// Should be 2 posts, with the bot post, and the blocked
2024-01-09 17:19:25 +00:00
assert_eq! (
vec! [ POST_BY_BOT , POST , POST_BY_BLOCKED_PERSON ] ,
names ( & read_post_listing_multiple_no_person )
) ;
2022-08-18 19:11:19 +00:00
assert_eq! (
2024-01-09 17:19:25 +00:00
Some ( & expected_post_listing_no_person ) ,
read_post_listing_multiple_no_person . get ( 1 )
2022-08-18 19:11:19 +00:00
) ;
2021-03-11 04:43:11 +00:00
assert_eq! (
expected_post_listing_no_person ,
2022-08-18 19:11:19 +00:00
read_post_listing_single_no_person
2021-03-11 04:43:11 +00:00
) ;
2020-12-11 15:27:33 +00:00
2024-01-09 17:19:25 +00:00
cleanup ( data , pool ) . await
2022-08-18 19:11:19 +00:00
}
2022-11-09 10:05:00 +00:00
#[ tokio::test ]
2022-08-18 19:11:19 +00:00
#[ serial ]
2024-01-09 17:19:25 +00:00
async fn post_listing_block_community ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
2023-07-11 13:09:59 +00:00
let pool = & mut pool . into ( ) ;
2024-01-09 17:19:25 +00:00
let data = init_data ( pool ) . await ? ;
2022-08-18 19:11:19 +00:00
let community_block = CommunityBlockForm {
2023-07-20 14:36:16 +00:00
person_id : data . local_user_view . person . id ,
2022-08-18 19:11:19 +00:00
community_id : data . inserted_community . id ,
} ;
2024-01-09 17:19:25 +00:00
CommunityBlock ::block ( pool , & community_block ) . await ? ;
2021-08-19 20:54:15 +00:00
2023-07-17 10:20:25 +00:00
let read_post_listings_with_person_after_block = PostQuery {
2024-01-09 17:19:25 +00:00
community_id : Some ( data . inserted_community . id ) ,
.. data . default_post_query ( )
2023-07-17 10:20:25 +00:00
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ? ;
2021-08-19 20:54:15 +00:00
// Should be 0 posts after the community block
2024-01-09 17:19:25 +00:00
assert_eq! ( read_post_listings_with_person_after_block , vec! [ ] ) ;
2021-04-21 21:41:14 +00:00
2024-01-09 17:19:25 +00:00
CommunityBlock ::unblock ( pool , & community_block ) . await ? ;
cleanup ( data , pool ) . await
2022-08-18 19:11:19 +00:00
}
2022-11-09 10:05:00 +00:00
#[ tokio::test ]
2022-08-18 19:11:19 +00:00
#[ serial ]
2024-01-09 17:19:25 +00:00
async fn post_listing_like ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
2023-07-11 13:09:59 +00:00
let pool = & mut pool . into ( ) ;
2024-01-09 17:19:25 +00:00
let mut data = init_data ( pool ) . await ? ;
2022-08-18 19:11:19 +00:00
let post_like_form = PostLikeForm {
post_id : data . inserted_post . id ,
2023-07-20 14:36:16 +00:00
person_id : data . local_user_view . person . id ,
2022-08-18 19:11:19 +00:00
score : 1 ,
} ;
2024-01-09 17:19:25 +00:00
let inserted_post_like = PostLike ::like ( pool , & post_like_form ) . await ? ;
2022-08-18 19:11:19 +00:00
let expected_post_like = PostLike {
post_id : data . inserted_post . id ,
2023-07-20 14:36:16 +00:00
person_id : data . local_user_view . person . id ,
2022-08-18 19:11:19 +00:00
published : inserted_post_like . published ,
score : 1 ,
} ;
2020-12-11 15:27:33 +00:00
assert_eq! ( expected_post_like , inserted_post_like ) ;
2022-08-18 19:11:19 +00:00
2023-06-21 08:48:39 +00:00
let post_listing_single_with_person = PostView ::read (
pool ,
data . inserted_post . id ,
2023-07-20 14:36:16 +00:00
Some ( data . local_user_view . person . id ) ,
2023-08-11 09:13:14 +00:00
false ,
2023-06-21 08:48:39 +00:00
)
2024-04-16 12:48:15 +00:00
. await ?
. ok_or ( LemmyErrorType ::CouldntFindPost ) ? ;
2023-06-21 08:48:39 +00:00
2024-01-09 17:19:25 +00:00
let mut expected_post_with_upvote = expected_post_view ( & data , pool ) . await ? ;
2023-06-21 08:48:39 +00:00
expected_post_with_upvote . my_vote = Some ( 1 ) ;
expected_post_with_upvote . counts . score = 1 ;
expected_post_with_upvote . counts . upvotes = 1 ;
assert_eq! ( expected_post_with_upvote , post_listing_single_with_person ) ;
2023-08-08 09:41:41 +00:00
let local_user_form = LocalUserUpdateForm {
show_bot_accounts : Some ( false ) ,
.. Default ::default ( )
} ;
2024-05-15 02:37:30 +00:00
LocalUser ::update ( pool , data . local_user_view . local_user . id , & local_user_form ) . await ? ;
data . local_user_view . local_user . show_bot_accounts = false ;
2023-06-21 08:48:39 +00:00
2023-07-17 10:20:25 +00:00
let read_post_listing = PostQuery {
2024-01-09 17:19:25 +00:00
community_id : Some ( data . inserted_community . id ) ,
.. data . default_post_query ( )
2023-07-17 10:20:25 +00:00
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ? ;
assert_eq! ( vec! [ expected_post_with_upvote ] , read_post_listing ) ;
2023-06-21 08:48:39 +00:00
2024-05-01 03:26:55 +00:00
let like_removed =
PostLike ::remove ( pool , data . local_user_view . person . id , data . inserted_post . id ) . await ? ;
assert_eq! ( 1 , like_removed ) ;
cleanup ( data , pool ) . await
}
#[ tokio::test ]
#[ serial ]
async fn post_listing_liked_only ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
let pool = & mut pool . into ( ) ;
let data = init_data ( pool ) . await ? ;
// Like both the bot post, and your own
// The liked_only should not show your own post
let post_like_form = PostLikeForm {
post_id : data . inserted_post . id ,
person_id : data . local_user_view . person . id ,
score : 1 ,
} ;
PostLike ::like ( pool , & post_like_form ) . await ? ;
let bot_post_like_form = PostLikeForm {
post_id : data . inserted_bot_post . id ,
person_id : data . local_user_view . person . id ,
score : 1 ,
} ;
PostLike ::like ( pool , & bot_post_like_form ) . await ? ;
// Read the liked only
2023-08-08 09:40:28 +00:00
let read_liked_post_listing = PostQuery {
2024-01-09 17:19:25 +00:00
community_id : Some ( data . inserted_community . id ) ,
liked_only : true ,
.. data . default_post_query ( )
2023-08-08 09:40:28 +00:00
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ? ;
2024-05-01 03:26:55 +00:00
// This should only include the bot post, not the one you created
assert_eq! ( vec! [ POST_BY_BOT ] , names ( & read_liked_post_listing ) ) ;
2023-08-08 09:40:28 +00:00
let read_disliked_post_listing = PostQuery {
2024-01-09 17:19:25 +00:00
community_id : Some ( data . inserted_community . id ) ,
disliked_only : true ,
.. data . default_post_query ( )
2023-08-08 09:40:28 +00:00
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ? ;
2024-05-01 03:26:55 +00:00
// Should be no posts
2024-01-09 17:19:25 +00:00
assert_eq! ( read_disliked_post_listing , vec! [ ] ) ;
2023-08-08 09:40:28 +00:00
2024-01-09 17:19:25 +00:00
cleanup ( data , pool ) . await
2022-08-18 19:11:19 +00:00
}
2023-10-24 12:37:03 +00:00
#[ tokio::test ]
#[ serial ]
2024-01-09 17:19:25 +00:00
async fn creator_info ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
2023-10-24 12:37:03 +00:00
let pool = & mut pool . into ( ) ;
2024-01-09 17:19:25 +00:00
let data = init_data ( pool ) . await ? ;
2023-10-24 12:37:03 +00:00
// Make one of the inserted persons a moderator
let person_id = data . local_user_view . person . id ;
let community_id = data . inserted_community . id ;
let form = CommunityModeratorForm {
community_id ,
person_id ,
} ;
2024-01-09 17:19:25 +00:00
CommunityModerator ::join ( pool , & form ) . await ? ;
2023-10-24 12:37:03 +00:00
let post_listing = PostQuery {
2024-01-09 17:19:25 +00:00
community_id : Some ( data . inserted_community . id ) ,
.. data . default_post_query ( )
2023-11-21 16:20:24 +00:00
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ?
. into_iter ( )
. map ( | p | ( p . creator . name , p . creator_is_moderator , p . creator_is_admin ) )
. collect ::< Vec < _ > > ( ) ;
let expected_post_listing = vec! [
( " mybot " . to_owned ( ) , false , false ) ,
( " tegan " . to_owned ( ) , true , true ) ,
] ;
2023-11-21 16:20:24 +00:00
2024-01-09 17:19:25 +00:00
assert_eq! ( expected_post_listing , post_listing ) ;
2023-11-21 16:20:24 +00:00
2024-01-09 17:19:25 +00:00
cleanup ( data , pool ) . await
2023-11-21 16:20:24 +00:00
}
#[ tokio::test ]
#[ serial ]
2024-01-09 17:19:25 +00:00
async fn post_listing_person_language ( ) -> LemmyResult < ( ) > {
const EL_POSTO : & str = " el posto " ;
2023-10-24 12:37:03 +00:00
2024-01-09 17:19:25 +00:00
let pool = & build_db_pool ( ) . await ? ;
2023-07-11 13:09:59 +00:00
let pool = & mut pool . into ( ) ;
2024-01-09 17:19:25 +00:00
let data = init_data ( pool ) . await ? ;
2022-08-18 19:11:19 +00:00
2023-02-05 05:38:08 +00:00
let spanish_id = Language ::read_id_from_code ( pool , Some ( " es " ) )
2024-01-09 17:19:25 +00:00
. await ?
. expect ( " spanish should exist " ) ;
let french_id = Language ::read_id_from_code ( pool , Some ( " fr " ) )
. await ?
. expect ( " french should exist " ) ;
2022-10-27 09:24:07 +00:00
let post_spanish = PostInsertForm ::builder ( )
2024-01-09 17:19:25 +00:00
. name ( EL_POSTO . to_string ( ) )
2023-07-20 14:36:16 +00:00
. creator_id ( data . local_user_view . person . id )
2022-10-27 09:24:07 +00:00
. community_id ( data . inserted_community . id )
. language_id ( Some ( spanish_id ) )
. build ( ) ;
2022-08-18 19:11:19 +00:00
2024-01-09 17:19:25 +00:00
Post ::create ( pool , & post_spanish ) . await ? ;
2022-08-18 19:11:19 +00:00
2024-02-16 12:24:35 +00:00
let post_listings_all = data . default_post_query ( ) . list ( & data . site , pool ) . await ? ;
2022-08-18 19:11:19 +00:00
// no language filters specified, all posts should be returned
2024-01-09 17:19:25 +00:00
assert_eq! ( vec! [ EL_POSTO , POST_BY_BOT , POST ] , names ( & post_listings_all ) ) ;
2022-08-18 19:11:19 +00:00
2024-01-09 17:19:25 +00:00
LocalUserLanguage ::update ( pool , vec! [ french_id ] , data . local_user_view . local_user . id ) . await ? ;
2024-02-16 12:24:35 +00:00
let post_listing_french = data . default_post_query ( ) . list ( & data . site , pool ) . await ? ;
2022-08-18 19:11:19 +00:00
2023-05-18 14:34:21 +00:00
// only one post in french and one undetermined should be returned
2024-01-09 17:19:25 +00:00
assert_eq! ( vec! [ POST_BY_BOT , POST ] , names ( & post_listing_french ) ) ;
assert_eq! (
Some ( french_id ) ,
post_listing_french . get ( 1 ) . map ( | p | p . post . language_id )
) ;
2022-08-18 19:11:19 +00:00
2022-10-06 18:27:58 +00:00
LocalUserLanguage ::update (
2022-11-09 10:05:00 +00:00
pool ,
2023-03-01 00:49:31 +00:00
vec! [ french_id , UNDETERMINED_ID ] ,
2023-07-20 14:36:16 +00:00
data . local_user_view . local_user . id ,
2022-08-18 19:11:19 +00:00
)
2024-01-09 17:19:25 +00:00
. await ? ;
let post_listings_french_und = data
. default_post_query ( )
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ?
. into_iter ( )
. map ( | p | ( p . post . name , p . post . language_id ) )
. collect ::< Vec < _ > > ( ) ;
let expected_post_listings_french_und = vec! [
( POST_BY_BOT . to_owned ( ) , UNDETERMINED_ID ) ,
( POST . to_owned ( ) , french_id ) ,
] ;
2022-08-18 19:11:19 +00:00
// french post and undetermined language post should be returned
2024-01-09 17:19:25 +00:00
assert_eq! ( expected_post_listings_french_und , post_listings_french_und ) ;
2022-08-18 19:11:19 +00:00
2024-01-09 17:19:25 +00:00
cleanup ( data , pool ) . await
2023-06-23 10:53:46 +00:00
}
#[ tokio::test ]
#[ serial ]
2024-01-09 17:19:25 +00:00
async fn post_listings_removed ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
2023-07-11 13:09:59 +00:00
let pool = & mut pool . into ( ) ;
2024-01-09 17:19:25 +00:00
let mut data = init_data ( pool ) . await ? ;
2023-06-23 10:53:46 +00:00
2023-07-20 14:36:16 +00:00
// Remove the post
2023-06-23 10:53:46 +00:00
Post ::update (
pool ,
2023-12-11 19:36:12 +00:00
data . inserted_bot_post . id ,
2023-08-08 09:41:41 +00:00
& PostUpdateForm {
removed : Some ( true ) ,
.. Default ::default ( )
} ,
2023-06-23 10:53:46 +00:00
)
2024-01-09 17:19:25 +00:00
. await ? ;
2023-06-23 10:53:46 +00:00
2023-07-20 14:36:16 +00:00
// Make sure you don't see the removed post in the results
2024-02-16 12:24:35 +00:00
let post_listings_no_admin = data . default_post_query ( ) . list ( & data . site , pool ) . await ? ;
2024-01-09 17:19:25 +00:00
assert_eq! ( vec! [ POST ] , names ( & post_listings_no_admin ) ) ;
2023-06-23 10:53:46 +00:00
2023-12-11 19:36:12 +00:00
// Removed bot post is shown to admins on its profile page
2023-08-24 09:40:08 +00:00
data . local_user_view . local_user . admin = true ;
2023-07-17 10:20:25 +00:00
let post_listings_is_admin = PostQuery {
2023-12-11 19:36:12 +00:00
creator_id : Some ( data . inserted_bot . id ) ,
2024-01-09 17:19:25 +00:00
.. data . default_post_query ( )
2023-07-17 10:20:25 +00:00
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ? ;
assert_eq! ( vec! [ POST_BY_BOT ] , names ( & post_listings_is_admin ) ) ;
2023-06-23 10:53:46 +00:00
2024-01-09 17:19:25 +00:00
cleanup ( data , pool ) . await
2020-12-11 15:27:33 +00:00
}
2022-08-22 20:55:10 +00:00
2023-07-20 14:36:16 +00:00
#[ tokio::test ]
#[ serial ]
2024-01-09 17:19:25 +00:00
async fn post_listings_deleted ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
2023-07-20 14:36:16 +00:00
let pool = & mut pool . into ( ) ;
2024-01-09 17:19:25 +00:00
let data = init_data ( pool ) . await ? ;
2023-07-20 14:36:16 +00:00
// Delete the post
Post ::update (
pool ,
data . inserted_post . id ,
2023-08-08 09:41:41 +00:00
& PostUpdateForm {
deleted : Some ( true ) ,
.. Default ::default ( )
} ,
2023-07-20 14:36:16 +00:00
)
2024-01-09 17:19:25 +00:00
. await ? ;
2023-07-20 14:36:16 +00:00
2023-12-13 10:09:10 +00:00
// Deleted post is only shown to creator
for ( local_user , expect_contains_deleted ) in [
( None , false ) ,
( Some ( & data . blocked_local_user_view ) , false ) ,
( Some ( & data . local_user_view ) , true ) ,
] {
let contains_deleted = PostQuery {
local_user ,
2024-01-09 17:19:25 +00:00
.. data . default_post_query ( )
2023-12-13 10:09:10 +00:00
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ?
2023-07-20 14:36:16 +00:00
. iter ( )
2023-12-13 10:09:10 +00:00
. any ( | p | p . post . id = = data . inserted_post . id ) ;
2023-07-20 14:36:16 +00:00
2023-12-13 10:09:10 +00:00
assert_eq! ( expect_contains_deleted , contains_deleted ) ;
2023-07-20 14:36:16 +00:00
}
2024-01-09 17:19:25 +00:00
cleanup ( data , pool ) . await
2023-07-20 14:36:16 +00:00
}
2023-09-20 09:56:13 +00:00
#[ tokio::test ]
#[ serial ]
2024-01-09 17:19:25 +00:00
async fn post_listing_instance_block ( ) -> LemmyResult < ( ) > {
const POST_FROM_BLOCKED_INSTANCE : & str = " post on blocked instance " ;
let pool = & build_db_pool ( ) . await ? ;
2023-09-20 09:56:13 +00:00
let pool = & mut pool . into ( ) ;
2024-01-09 17:19:25 +00:00
let data = init_data ( pool ) . await ? ;
2023-09-20 09:56:13 +00:00
2024-01-09 17:19:25 +00:00
let blocked_instance = Instance ::read_or_create ( pool , " another_domain.tld " . to_string ( ) ) . await ? ;
2023-09-20 09:56:13 +00:00
let community_form = CommunityInsertForm ::builder ( )
. name ( " test_community_4 " . to_string ( ) )
. title ( " none " . to_owned ( ) )
. public_key ( " pubkey " . to_string ( ) )
. instance_id ( blocked_instance . id )
. build ( ) ;
2024-01-09 17:19:25 +00:00
let inserted_community = Community ::create ( pool , & community_form ) . await ? ;
2023-09-20 09:56:13 +00:00
let post_form = PostInsertForm ::builder ( )
2024-01-09 17:19:25 +00:00
. name ( POST_FROM_BLOCKED_INSTANCE . to_string ( ) )
2023-09-20 09:56:13 +00:00
. creator_id ( data . inserted_bot . id )
. community_id ( inserted_community . id )
. language_id ( Some ( LanguageId ( 1 ) ) )
. build ( ) ;
2024-01-09 17:19:25 +00:00
let post_from_blocked_instance = Post ::create ( pool , & post_form ) . await ? ;
2023-09-20 09:56:13 +00:00
// no instance block, should return all posts
2024-02-16 12:24:35 +00:00
let post_listings_all = data . default_post_query ( ) . list ( & data . site , pool ) . await ? ;
2024-01-09 17:19:25 +00:00
assert_eq! (
vec! [ POST_FROM_BLOCKED_INSTANCE , POST_BY_BOT , POST ] ,
names ( & post_listings_all )
) ;
2023-09-20 09:56:13 +00:00
// block the instance
let block_form = InstanceBlockForm {
person_id : data . local_user_view . person . id ,
instance_id : blocked_instance . id ,
} ;
2024-01-09 17:19:25 +00:00
InstanceBlock ::block ( pool , & block_form ) . await ? ;
2023-09-20 09:56:13 +00:00
// now posts from communities on that instance should be hidden
2024-02-16 12:24:35 +00:00
let post_listings_blocked = data . default_post_query ( ) . list ( & data . site , pool ) . await ? ;
2024-01-09 17:19:25 +00:00
assert_eq! ( vec! [ POST_BY_BOT , POST ] , names ( & post_listings_blocked ) ) ;
assert! ( post_listings_blocked
. iter ( )
. all ( | p | p . post . id ! = post_from_blocked_instance . id ) ) ;
2023-09-20 09:56:13 +00:00
// after unblocking it should return all posts again
2024-01-09 17:19:25 +00:00
InstanceBlock ::unblock ( pool , & block_form ) . await ? ;
2024-02-16 12:24:35 +00:00
let post_listings_blocked = data . default_post_query ( ) . list ( & data . site , pool ) . await ? ;
2024-01-09 17:19:25 +00:00
assert_eq! (
vec! [ POST_FROM_BLOCKED_INSTANCE , POST_BY_BOT , POST ] ,
names ( & post_listings_blocked )
) ;
2023-09-20 09:56:13 +00:00
2024-01-09 17:19:25 +00:00
Instance ::delete ( pool , blocked_instance . id ) . await ? ;
cleanup ( data , pool ) . await
2023-09-20 09:56:13 +00:00
}
2023-12-14 12:10:01 +00:00
#[ tokio::test ]
#[ serial ]
2024-01-09 17:19:25 +00:00
async fn pagination_includes_each_post_once ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
2023-12-14 12:10:01 +00:00
let pool = & mut pool . into ( ) ;
2024-01-09 17:19:25 +00:00
let data = init_data ( pool ) . await ? ;
2023-12-14 12:10:01 +00:00
let community_form = CommunityInsertForm ::builder ( )
. name ( " yes " . to_string ( ) )
. title ( " yes " . to_owned ( ) )
. public_key ( " pubkey " . to_string ( ) )
. instance_id ( data . inserted_instance . id )
. build ( ) ;
2024-01-09 17:19:25 +00:00
let inserted_community = Community ::create ( pool , & community_form ) . await ? ;
2023-12-14 12:10:01 +00:00
let mut inserted_post_ids = vec! [ ] ;
let mut inserted_comment_ids = vec! [ ] ;
// Create 150 posts with varying non-correlating values for publish date, number of comments, and featured
for comments in 0 .. 10 {
for _ in 0 .. 15 {
let post_form = PostInsertForm ::builder ( )
. name ( " keep Christ in Christmas " . to_owned ( ) )
. creator_id ( data . local_user_view . person . id )
. community_id ( inserted_community . id )
. featured_local ( Some ( ( comments % 2 ) = = 0 ) )
. featured_community ( Some ( ( comments % 2 ) = = 0 ) )
. published ( Some ( Utc ::now ( ) - Duration ::from_secs ( comments % 3 ) ) )
. build ( ) ;
2024-01-09 17:19:25 +00:00
let inserted_post = Post ::create ( pool , & post_form ) . await ? ;
2023-12-14 12:10:01 +00:00
inserted_post_ids . push ( inserted_post . id ) ;
for _ in 0 .. comments {
let comment_form = CommentInsertForm ::builder ( )
. creator_id ( data . local_user_view . person . id )
. post_id ( inserted_post . id )
. content ( " yes " . to_owned ( ) )
. build ( ) ;
2024-01-09 17:19:25 +00:00
let inserted_comment = Comment ::create ( pool , & comment_form , None ) . await ? ;
2023-12-14 12:10:01 +00:00
inserted_comment_ids . push ( inserted_comment . id ) ;
}
}
}
2024-01-24 15:50:11 +00:00
let options = PostQuery {
community_id : Some ( inserted_community . id ) ,
sort : Some ( SortType ::MostComments ) ,
limit : Some ( 10 ) ,
.. Default ::default ( )
} ;
2023-12-14 12:10:01 +00:00
let mut listed_post_ids = vec! [ ] ;
let mut page_after = None ;
loop {
let post_listings = PostQuery {
page_after ,
2024-01-24 15:50:11 +00:00
.. options . clone ( )
2023-12-14 12:10:01 +00:00
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-09 17:19:25 +00:00
. await ? ;
2023-12-14 12:10:01 +00:00
listed_post_ids . extend ( post_listings . iter ( ) . map ( | p | p . post . id ) ) ;
if let Some ( p ) = post_listings . into_iter ( ) . last ( ) {
page_after = Some ( PaginationCursorData ( p . counts ) ) ;
} else {
break ;
}
}
2024-01-24 15:50:11 +00:00
// Check that backward pagination matches forward pagination
let mut listed_post_ids_forward = listed_post_ids . clone ( ) ;
let mut page_before = None ;
loop {
let post_listings = PostQuery {
page_after : page_before ,
page_back : true ,
.. options . clone ( )
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-24 15:50:11 +00:00
. await ? ;
let listed_post_ids = post_listings . iter ( ) . map ( | p | p . post . id ) . collect ::< Vec < _ > > ( ) ;
let index = listed_post_ids_forward . len ( ) - listed_post_ids . len ( ) ;
assert_eq! (
listed_post_ids_forward . get ( index .. ) ,
listed_post_ids . get ( .. )
) ;
listed_post_ids_forward . truncate ( index ) ;
if let Some ( p ) = post_listings . into_iter ( ) . next ( ) {
page_before = Some ( PaginationCursorData ( p . counts ) ) ;
} else {
break ;
}
}
2023-12-14 12:10:01 +00:00
inserted_post_ids . sort_unstable_by_key ( | id | id . 0 ) ;
listed_post_ids . sort_unstable_by_key ( | id | id . 0 ) ;
assert_eq! ( inserted_post_ids , listed_post_ids ) ;
2024-01-09 17:19:25 +00:00
Community ::delete ( pool , inserted_community . id ) . await ? ;
cleanup ( data , pool ) . await
2023-12-14 12:10:01 +00:00
}
2023-12-19 09:46:41 +00:00
#[ tokio::test ]
#[ serial ]
2024-01-09 17:19:25 +00:00
async fn post_listings_hide_read ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
2023-12-19 09:46:41 +00:00
let pool = & mut pool . into ( ) ;
2024-01-09 17:19:25 +00:00
let mut data = init_data ( pool ) . await ? ;
2023-12-19 09:46:41 +00:00
// Make sure local user hides read posts
let local_user_form = LocalUserUpdateForm {
show_read_posts : Some ( false ) ,
.. Default ::default ( )
} ;
2024-05-15 02:37:30 +00:00
LocalUser ::update ( pool , data . local_user_view . local_user . id , & local_user_form ) . await ? ;
data . local_user_view . local_user . show_read_posts = false ;
2023-12-19 09:46:41 +00:00
// Mark a post as read
PostRead ::mark_as_read (
pool ,
HashSet ::from ( [ data . inserted_bot_post . id ] ) ,
data . local_user_view . person . id ,
)
2024-01-09 17:19:25 +00:00
. await ? ;
2023-12-19 09:46:41 +00:00
// Make sure you don't see the read post in the results
2024-02-16 12:24:35 +00:00
let post_listings_hide_read = data . default_post_query ( ) . list ( & data . site , pool ) . await ? ;
2024-01-09 17:19:25 +00:00
assert_eq! ( vec! [ POST ] , names ( & post_listings_hide_read ) ) ;
2023-12-19 09:46:41 +00:00
2024-01-09 17:19:25 +00:00
cleanup ( data , pool ) . await
2023-12-19 09:46:41 +00:00
}
2024-02-29 15:42:34 +00:00
#[ tokio::test ]
#[ serial ]
async fn post_listings_hide_hidden ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
let pool = & mut pool . into ( ) ;
let data = init_data ( pool ) . await ? ;
// Mark a post as hidden
PostHide ::hide (
pool ,
HashSet ::from ( [ data . inserted_bot_post . id ] ) ,
data . local_user_view . person . id ,
)
. await ? ;
// Make sure you don't see the hidden post in the results
let post_listings_hide_hidden = data . default_post_query ( ) . list ( & data . site , pool ) . await ? ;
assert_eq! ( vec! [ POST ] , names ( & post_listings_hide_hidden ) ) ;
// Make sure it does come back with the show_hidden option
let post_listings_show_hidden = PostQuery {
sort : Some ( SortType ::New ) ,
local_user : Some ( & data . local_user_view ) ,
show_hidden : true ,
.. Default ::default ( )
}
. list ( & data . site , pool )
. await ? ;
assert_eq! ( vec! [ POST_BY_BOT , POST ] , names ( & post_listings_show_hidden ) ) ;
// Make sure that hidden field is true.
assert! (
& post_listings_show_hidden
. first ( )
2024-05-01 03:26:55 +00:00
. ok_or ( LemmyErrorType ::CouldntFindPost ) ?
2024-02-29 15:42:34 +00:00
. hidden
) ;
cleanup ( data , pool ) . await
}
2024-01-09 17:19:25 +00:00
async fn cleanup ( data : Data , pool : & mut DbPool < '_ > ) -> LemmyResult < ( ) > {
let num_deleted = Post ::delete ( pool , data . inserted_post . id ) . await ? ;
Community ::delete ( pool , data . inserted_community . id ) . await ? ;
Person ::delete ( pool , data . local_user_view . person . id ) . await ? ;
Person ::delete ( pool , data . inserted_bot . id ) . await ? ;
Person ::delete ( pool , data . blocked_local_user_view . person . id ) . await ? ;
Instance ::delete ( pool , data . inserted_instance . id ) . await ? ;
2022-08-22 20:55:10 +00:00
assert_eq! ( 1 , num_deleted ) ;
2024-01-09 17:19:25 +00:00
Ok ( ( ) )
2022-08-22 20:55:10 +00:00
}
2024-01-09 17:19:25 +00:00
async fn expected_post_view ( data : & Data , pool : & mut DbPool < '_ > ) -> LemmyResult < PostView > {
2022-08-22 20:55:10 +00:00
let ( inserted_person , inserted_community , inserted_post ) = (
2023-07-20 14:36:16 +00:00
& data . local_user_view . person ,
2022-08-22 20:55:10 +00:00
& data . inserted_community ,
& data . inserted_post ,
) ;
2024-04-16 12:48:15 +00:00
let agg = PostAggregates ::read ( pool , inserted_post . id )
. await ?
. ok_or ( LemmyErrorType ::CouldntFindPost ) ? ;
2022-08-22 20:55:10 +00:00
2024-01-09 17:19:25 +00:00
Ok ( PostView {
2022-08-22 20:55:10 +00:00
post : Post {
id : inserted_post . id ,
name : inserted_post . name . clone ( ) ,
creator_id : inserted_person . id ,
url : None ,
body : None ,
2024-03-05 10:34:57 +00:00
alt_text : None ,
2022-08-22 20:55:10 +00:00
published : inserted_post . published ,
updated : None ,
community_id : inserted_community . id ,
removed : false ,
deleted : false ,
locked : false ,
nsfw : false ,
embed_title : None ,
embed_description : None ,
embed_video_url : None ,
thumbnail_url : None ,
2022-11-19 04:33:54 +00:00
ap_id : inserted_post . ap_id . clone ( ) ,
2022-08-22 20:55:10 +00:00
local : true ,
language_id : LanguageId ( 47 ) ,
2022-12-12 11:17:10 +00:00
featured_community : false ,
featured_local : false ,
2024-01-25 14:22:11 +00:00
url_content_type : None ,
2022-08-22 20:55:10 +00:00
} ,
my_vote : None ,
2022-09-27 16:45:46 +00:00
unread_comments : 0 ,
2023-03-01 17:19:46 +00:00
creator : Person {
2022-08-22 20:55:10 +00:00
id : inserted_person . id ,
name : inserted_person . name . clone ( ) ,
display_name : None ,
published : inserted_person . published ,
avatar : None ,
2022-11-19 04:33:54 +00:00
actor_id : inserted_person . actor_id . clone ( ) ,
2022-08-22 20:55:10 +00:00
local : true ,
bot_account : false ,
banned : false ,
deleted : false ,
bio : None ,
banner : None ,
updated : None ,
2022-11-19 04:33:54 +00:00
inbox_url : inserted_person . inbox_url . clone ( ) ,
2022-08-22 20:55:10 +00:00
shared_inbox_url : None ,
matrix_user_id : None ,
ban_expires : None ,
2022-10-27 09:24:07 +00:00
instance_id : data . inserted_instance . id ,
2023-03-01 17:19:46 +00:00
private_key : inserted_person . private_key . clone ( ) ,
public_key : inserted_person . public_key . clone ( ) ,
last_refreshed_at : inserted_person . last_refreshed_at ,
2022-08-22 20:55:10 +00:00
} ,
creator_banned_from_community : false ,
2024-03-22 22:31:08 +00:00
banned_from_community : false ,
2023-10-24 12:37:03 +00:00
creator_is_moderator : false ,
2023-11-21 16:20:24 +00:00
creator_is_admin : true ,
2023-03-01 17:19:46 +00:00
community : Community {
2022-08-22 20:55:10 +00:00
id : inserted_community . id ,
name : inserted_community . name . clone ( ) ,
icon : None ,
removed : false ,
deleted : false ,
nsfw : false ,
2022-11-19 04:33:54 +00:00
actor_id : inserted_community . actor_id . clone ( ) ,
2022-08-22 20:55:10 +00:00
local : true ,
title : " nada " . to_owned ( ) ,
description : None ,
updated : None ,
banner : None ,
hidden : false ,
posting_restricted_to_mods : false ,
published : inserted_community . published ,
2022-10-27 09:24:07 +00:00
instance_id : data . inserted_instance . id ,
2023-03-01 17:19:46 +00:00
private_key : inserted_community . private_key . clone ( ) ,
public_key : inserted_community . public_key . clone ( ) ,
last_refreshed_at : inserted_community . last_refreshed_at ,
followers_url : inserted_community . followers_url . clone ( ) ,
inbox_url : inserted_community . inbox_url . clone ( ) ,
shared_inbox_url : inserted_community . shared_inbox_url . clone ( ) ,
moderators_url : inserted_community . moderators_url . clone ( ) ,
featured_url : inserted_community . featured_url . clone ( ) ,
2024-01-25 16:04:25 +00:00
visibility : CommunityVisibility ::Public ,
2022-08-22 20:55:10 +00:00
} ,
counts : PostAggregates {
post_id : inserted_post . id ,
comments : 0 ,
score : 0 ,
upvotes : 0 ,
downvotes : 0 ,
published : agg . published ,
newest_comment_time_necro : inserted_post . published ,
newest_comment_time : inserted_post . published ,
2022-12-12 11:17:10 +00:00
featured_community : false ,
featured_local : false ,
2023-11-24 01:39:46 +00:00
hot_rank : RANK_DEFAULT ,
hot_rank_active : RANK_DEFAULT ,
2023-07-26 17:07:05 +00:00
controversy_rank : 0.0 ,
2023-11-24 01:39:46 +00:00
scaled_rank : RANK_DEFAULT ,
2023-07-20 15:13:21 +00:00
community_id : inserted_post . community_id ,
creator_id : inserted_post . creator_id ,
2023-09-20 09:56:13 +00:00
instance_id : data . inserted_instance . id ,
2022-08-22 20:55:10 +00:00
} ,
subscribed : SubscribedType ::NotSubscribed ,
read : false ,
2024-02-29 15:42:34 +00:00
hidden : false ,
2022-08-22 20:55:10 +00:00
saved : false ,
creator_blocked : false ,
2024-01-09 17:19:25 +00:00
} )
2022-08-22 20:55:10 +00:00
}
2024-01-25 16:04:25 +00:00
#[ tokio::test ]
#[ serial ]
async fn local_only_instance ( ) -> LemmyResult < ( ) > {
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 ? ;
let unauthenticated_query = PostQuery {
.. Default ::default ( )
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-25 16:04:25 +00:00
. await ? ;
assert_eq! ( 0 , unauthenticated_query . len ( ) ) ;
let authenticated_query = PostQuery {
local_user : Some ( & data . local_user_view ) ,
.. Default ::default ( )
}
2024-02-16 12:24:35 +00:00
. list ( & data . site , pool )
2024-01-25 16:04:25 +00:00
. await ? ;
assert_eq! ( 2 , authenticated_query . len ( ) ) ;
2024-04-16 12:48:15 +00:00
let unauthenticated_post = PostView ::read ( pool , data . inserted_post . id , None , false ) . await ? ;
assert! ( unauthenticated_post . is_none ( ) ) ;
2024-01-25 16:04:25 +00:00
let authenticated_post = PostView ::read (
pool ,
data . inserted_post . id ,
Some ( data . local_user_view . person . id ) ,
false ,
)
. await ;
assert! ( authenticated_post . is_ok ( ) ) ;
cleanup ( data , pool ) . await ? ;
Ok ( ( ) )
}
2024-03-22 22:31:08 +00:00
#[ tokio::test ]
#[ serial ]
async fn post_listing_local_user_banned_from_community ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
let pool = & mut pool . into ( ) ;
let data = init_data ( pool ) . await ? ;
// Test that post view shows if local user is blocked from community
let banned_from_comm_person = PersonInsertForm ::test_form ( data . inserted_instance . id , " jill " ) ;
let inserted_banned_from_comm_person = Person ::create ( pool , & banned_from_comm_person ) . await ? ;
let inserted_banned_from_comm_local_user = LocalUser ::create (
pool ,
& LocalUserInsertForm ::test_form ( inserted_banned_from_comm_person . id ) ,
2024-03-25 23:14:35 +00:00
vec! [ ] ,
2024-03-22 22:31:08 +00:00
)
. await ? ;
CommunityPersonBan ::ban (
pool ,
& CommunityPersonBanForm {
community_id : data . inserted_community . id ,
person_id : inserted_banned_from_comm_person . id ,
expires : None ,
} ,
)
. await ? ;
let post_view = PostView ::read (
pool ,
data . inserted_post . id ,
Some ( inserted_banned_from_comm_local_user . person_id ) ,
false ,
)
2024-04-16 12:48:15 +00:00
. await ?
. ok_or ( LemmyErrorType ::CouldntFindPost ) ? ;
2024-03-22 22:31:08 +00:00
assert! ( post_view . banned_from_community ) ;
Person ::delete ( pool , inserted_banned_from_comm_person . id ) . await ? ;
cleanup ( data , pool ) . await
}
#[ tokio::test ]
#[ serial ]
async fn post_listing_local_user_not_banned_from_community ( ) -> LemmyResult < ( ) > {
let pool = & build_db_pool ( ) . await ? ;
let pool = & mut pool . into ( ) ;
let data = init_data ( pool ) . await ? ;
let post_view = PostView ::read (
pool ,
data . inserted_post . id ,
Some ( data . local_user_view . person . id ) ,
false ,
)
2024-04-16 12:48:15 +00:00
. await ?
. ok_or ( LemmyErrorType ::CouldntFindPost ) ? ;
2024-03-22 22:31:08 +00:00
assert! ( ! post_view . banned_from_community ) ;
cleanup ( data , pool ) . await
}
2020-12-11 15:27:33 +00:00
}