From 14efd5730b84cc56e40b843b059a9328b02e45b5 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 6 Jan 2024 16:59:46 -0700 Subject: [PATCH 001/241] Post --- crates/db_schema/src/schema.rs | 31 +++++-------------------------- 1 file changed, 5 insertions(+), 26 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 88d468a6fa..7eac6a83ed 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -661,7 +661,11 @@ diesel::table! { person_id -> Int4, post_id -> Int4, read_comments -> Int8, - published -> Timestamptz, + read_comments_published -> Nullable, + read: Nullable, + saved: Nullable, + like_score: Int2, + like_published: Nullable, } } @@ -716,23 +720,6 @@ diesel::table! { } } -diesel::table! { - post_like (person_id, post_id) { - post_id -> Int4, - person_id -> Int4, - score -> Int2, - published -> Timestamptz, - } -} - -diesel::table! { - post_read (person_id, post_id) { - post_id -> Int4, - person_id -> Int4, - published -> Timestamptz, - } -} - diesel::table! { post_report (id) { id -> Int4, @@ -750,14 +737,6 @@ diesel::table! { } } -diesel::table! { - post_saved (person_id, post_id) { - post_id -> Int4, - person_id -> Int4, - published -> Timestamptz, - } -} - diesel::table! { private_message (id) { id -> Int4, From c32f62ece2114f540d7453e94d480e955e6e7fe3 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 6 Jan 2024 17:05:02 -0700 Subject: [PATCH 002/241] comment --- crates/db_schema/src/schema.rs | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 7eac6a83ed..4fecdf4e20 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -107,15 +107,14 @@ diesel::table! { } } -diesel::table! { - comment_like (person_id, comment_id) { - person_id -> Int4, - comment_id -> Int4, +person_id -> Int4, post_id -> Int4, - score -> Int2, - published -> Timestamptz, - } -} + read_comments -> Int8, + read_comments_published -> Nullable, + read: Nullable, + saved: Nullable, + like_score: Int2, + like_published: Nullable, diesel::table! { comment_reply (id) { @@ -141,14 +140,6 @@ diesel::table! { } } -diesel::table! { - comment_saved (person_id, comment_id) { - comment_id -> Int4, - person_id -> Int4, - published -> Timestamptz, - } -} - diesel::table! { community (id) { id -> Int4, @@ -656,6 +647,16 @@ diesel::table! { } } +diesel::table! { + person_comment_aggregates (person_id, comment_id) { + person_id -> Int4, + comment_id -> Int4, + saved: Nullable, + like_score: Int2, + like_published: Nullable, + } +} + diesel::table! { person_post_aggregates (person_id, post_id) { person_id -> Int4, From ba82958ed33fbeefec54c0b5f9ae9eea809c0bd3 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 6 Jan 2024 17:10:24 -0700 Subject: [PATCH 003/241] Update schema.rs --- crates/db_schema/src/schema.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 4fecdf4e20..75c75bbec3 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -107,15 +107,6 @@ diesel::table! { } } -person_id -> Int4, - post_id -> Int4, - read_comments -> Int8, - read_comments_published -> Nullable, - read: Nullable, - saved: Nullable, - like_score: Int2, - like_published: Nullable, - diesel::table! { comment_reply (id) { id -> Int4, From a82f99435bee91ee355f56ce8def4aace0723447 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 6 Jan 2024 17:40:49 -0700 Subject: [PATCH 004/241] Community --- crates/db_schema/src/schema.rs | 47 ++++++++++------------------------ 1 file changed, 13 insertions(+), 34 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 75c75bbec3..f5191b9fb1 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -183,23 +183,6 @@ diesel::table! { } } -diesel::table! { - community_block (person_id, community_id) { - person_id -> Int4, - community_id -> Int4, - published -> Timestamptz, - } -} - -diesel::table! { - community_follower (person_id, community_id) { - community_id -> Int4, - person_id -> Int4, - published -> Timestamptz, - pending -> Bool, - } -} - diesel::table! { community_language (community_id, language_id) { community_id -> Int4, @@ -207,23 +190,6 @@ diesel::table! { } } -diesel::table! { - community_moderator (person_id, community_id) { - community_id -> Int4, - person_id -> Int4, - published -> Timestamptz, - } -} - -diesel::table! { - community_person_ban (person_id, community_id) { - community_id -> Int4, - person_id -> Int4, - published -> Timestamptz, - expires -> Nullable, - } -} - diesel::table! { custom_emoji (id) { id -> Int4, @@ -648,6 +614,19 @@ diesel::table! { } } +diesel::table! { + person_community_aggregates (person_id, community_id) { + person_id -> Int4, + community_id -> Int4, + block -> Nullable, + follow -> Nullable, + follow_pending -> Bool, + moderator -> Nullable, + ban: Nullable, + ban_expires -> Nullable, + } +} + diesel::table! { person_post_aggregates (person_id, post_id) { person_id -> Int4, From 1bf52bbcc17568de1ee090cc2a95007456a55784 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 7 Jan 2024 14:59:16 -0700 Subject: [PATCH 005/241] person --- crates/db_schema/src/schema.rs | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index f5191b9fb1..cc2806565a 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -577,23 +577,6 @@ diesel::table! { } } -diesel::table! { - person_block (person_id, target_id) { - person_id -> Int4, - target_id -> Int4, - published -> Timestamptz, - } -} - -diesel::table! { - person_follower (follower_id, person_id) { - person_id -> Int4, - follower_id -> Int4, - published -> Timestamptz, - pending -> Bool, - } -} - diesel::table! { person_mention (id) { id -> Int4, @@ -627,6 +610,16 @@ diesel::table! { } } +diesel::table! { + person_person_aggregates (person_id, target_id) { + person_id -> Int4, + target_id -> Int4, + block -> Nullable, + follow -> Nullable, + follow_pending -> Bool, + } +} + diesel::table! { person_post_aggregates (person_id, post_id) { person_id -> Int4, From 06fedb38427d775767b02dbc0d330856fa90a08a Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 12 Jan 2024 20:15:08 -0700 Subject: [PATCH 006/241] Update post_view.rs --- crates/db_views/src/post_view.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index f5acc38041..e687d2c97b 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -222,6 +222,12 @@ fn queries<'a>() -> Queries< .inner_join(person::table) .inner_join(community::table) .inner_join(post::table) + .left_join(person_post_aggregates::table + .on( + post_aggregates::post_id + .eq(person_post_aggregates::post_id) + .and(person_post_aggregates::person_id.nullable().eq(my_person_id)), + )) .select(( post::all_columns, person::all_columns, @@ -231,12 +237,12 @@ fn queries<'a>() -> Queries< creator_is_admin, post_aggregates::all_columns, subscribed_type_selection, - is_saved_selection, - is_read_selection, + person_post_aggregates::saved.is_not_null(), + person_post_aggregates::read.is_not_null(), is_creator_blocked_selection, score_selection, coalesce( - post_aggregates::comments.nullable() - read_comments, + post_aggregates::comments.nullable() - person_post_aggregates::read_comments.nullable(), post_aggregates::comments, ), )) From a458c6e0396b991cf4409c94a8fa71b2ad6ea585 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 13 Jan 2024 09:50:58 -0700 Subject: [PATCH 007/241] Update schema.rs --- crates/db_schema/src/schema.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index cc2806565a..bcb9d2001f 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -592,8 +592,8 @@ diesel::table! { person_id -> Int4, comment_id -> Int4, saved: Nullable, - like_score: Int2, - like_published: Nullable, + liked: Nullable, + like_score: Nullable, } } @@ -601,11 +601,11 @@ diesel::table! { person_community_aggregates (person_id, community_id) { person_id -> Int4, community_id -> Int4, - block -> Nullable, - follow -> Nullable, - follow_pending -> Bool, + blocked -> Nullable, + followed -> Nullable, + follow_pending -> Nullable, moderator -> Nullable, - ban: Nullable, + banned: Nullable, ban_expires -> Nullable, } } @@ -614,9 +614,9 @@ diesel::table! { person_person_aggregates (person_id, target_id) { person_id -> Int4, target_id -> Int4, - block -> Nullable, - follow -> Nullable, - follow_pending -> Bool, + blocked -> Nullable, + followed -> Nullable, + follow_pending -> Nullable, } } @@ -628,8 +628,8 @@ diesel::table! { read_comments_published -> Nullable, read: Nullable, saved: Nullable, - like_score: Int2, - like_published: Nullable, + liked: Nullable, + like_score: Nullable, } } From e6e76ab1551b4dae7c1b8de736cc47c57ad2813b Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 13 Jan 2024 10:10:32 -0700 Subject: [PATCH 008/241] Update post_view.rs --- crates/db_views/src/post_view.rs | 33 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index e687d2c97b..8413e048b7 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -86,21 +86,6 @@ fn queries<'a>() -> Queries< impl ReadFn<'a, PostView, (PostId, Option, bool)>, impl ListFn<'a, PostView, PostQuery<'a>>, > { - 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)), - ), - ); - 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)), - ), - ); - let creator_is_admin = exists( local_user::table.filter( post_aggregates::creator_id @@ -219,15 +204,27 @@ fn queries<'a>() -> Queries< }; query - .inner_join(person::table) - .inner_join(community::table) - .inner_join(post::table) + .left_join(person_person_aggregates::table + .on( + post_aggregates::creator + .eq(person_person_aggregates::target_id) + .and(person_person_aggregates::person_id.nullable().eq(my_person_id)), + )) + .left_join(person_community_aggregates::table + .on( + post_aggregates::community_id + .eq(person_community_aggregates::community_id) + .and(person_community_aggregates::person_id.nullable().eq(my_person_id)), + )) .left_join(person_post_aggregates::table .on( post_aggregates::post_id .eq(person_post_aggregates::post_id) .and(person_post_aggregates::person_id.nullable().eq(my_person_id)), )) + .inner_join(person::table) + .inner_join(community::table) + .inner_join(post::table) .select(( post::all_columns, person::all_columns, From 4745cc34b27f6b9b9a9b47c17ccf16de889c0d8c Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 13 Jan 2024 11:06:41 -0700 Subject: [PATCH 009/241] Update schema.rs --- crates/db_schema/src/schema.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index bcb9d2001f..f723b1d1d0 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -588,7 +588,7 @@ diesel::table! { } diesel::table! { - person_comment_aggregates (person_id, comment_id) { + person_comment_actions (person_id, comment_id) { person_id -> Int4, comment_id -> Int4, saved: Nullable, @@ -598,20 +598,20 @@ diesel::table! { } diesel::table! { - person_community_aggregates (person_id, community_id) { + person_community_actions (person_id, community_id) { person_id -> Int4, community_id -> Int4, blocked -> Nullable, followed -> Nullable, follow_pending -> Nullable, - moderator -> Nullable, - banned: Nullable, + became_moderator -> Nullable, + received_ban: Nullable, ban_expires -> Nullable, } } diesel::table! { - person_person_aggregates (person_id, target_id) { + person_person_actions (person_id, target_id) { person_id -> Int4, target_id -> Int4, blocked -> Nullable, @@ -621,7 +621,7 @@ diesel::table! { } diesel::table! { - person_post_aggregates (person_id, post_id) { + person_post_actions (person_id, post_id) { person_id -> Int4, post_id -> Int4, read_comments -> Int8, From 56502414b359458cb0ca81bd91f1f4ffe9ce9a09 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 13 Jan 2024 11:20:05 -0700 Subject: [PATCH 010/241] Update schema.rs --- crates/db_schema/src/schema.rs | 92 +++++++++++++++++----------------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index f723b1d1d0..d34f49cb3b 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -94,6 +94,16 @@ diesel::table! { } } +diesel::table! { + comment_actions (person_id, comment_id) { + person_id -> Int4, + comment_id -> Int4, + saved: Nullable, + liked: Nullable, + like_score: Nullable, + } +} + diesel::table! { comment_aggregates (comment_id) { comment_id -> Int4, @@ -168,6 +178,19 @@ diesel::table! { } } +diesel::table! { + community_actions (person_id, community_id) { + person_id -> Int4, + community_id -> Int4, + blocked -> Nullable, + followed -> Nullable, + follow_pending -> Nullable, + became_moderator -> Nullable, + received_ban: Nullable, + ban_expires -> Nullable, + } +} + diesel::table! { community_aggregates (community_id) { community_id -> Int4, @@ -560,6 +583,16 @@ diesel::table! { } } +diesel::table! { + person_actions (person_id, target_id) { + person_id -> Int4, + target_id -> Int4, + blocked -> Nullable, + followed -> Nullable, + follow_pending -> Nullable, + } +} + diesel::table! { person_aggregates (person_id) { person_id -> Int4, @@ -587,52 +620,6 @@ diesel::table! { } } -diesel::table! { - person_comment_actions (person_id, comment_id) { - person_id -> Int4, - comment_id -> Int4, - saved: Nullable, - liked: Nullable, - like_score: Nullable, - } -} - -diesel::table! { - person_community_actions (person_id, community_id) { - person_id -> Int4, - community_id -> Int4, - blocked -> Nullable, - followed -> Nullable, - follow_pending -> Nullable, - became_moderator -> Nullable, - received_ban: Nullable, - ban_expires -> Nullable, - } -} - -diesel::table! { - person_person_actions (person_id, target_id) { - person_id -> Int4, - target_id -> Int4, - blocked -> Nullable, - followed -> Nullable, - follow_pending -> Nullable, - } -} - -diesel::table! { - person_post_actions (person_id, post_id) { - person_id -> Int4, - post_id -> Int4, - read_comments -> Int8, - read_comments_published -> Nullable, - read: Nullable, - saved: Nullable, - liked: Nullable, - like_score: Nullable, - } -} - diesel::table! { post (id) { id -> Int4, @@ -662,6 +649,19 @@ diesel::table! { } } +diesel::table! { + post_actions (person_id, post_id) { + person_id -> Int4, + post_id -> Int4, + read_comments -> Int8, + read_comments_published -> Nullable, + read: Nullable, + saved: Nullable, + liked: Nullable, + like_score: Nullable, + } +} + diesel::table! { post_aggregates (post_id) { post_id -> Int4, From 89124c02dc733c553158732140b9cc341f27b823 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 13 Jan 2024 11:52:31 -0700 Subject: [PATCH 011/241] Update post_view.rs --- crates/db_views/src/post_view.rs | 215 ++++++------------------------- 1 file changed, 36 insertions(+), 179 deletions(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 8413e048b7..38cdebb5c5 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -18,6 +18,7 @@ use diesel::{ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aggregates::structs::PostAggregates, + aliases::creator_community_actions, newtypes::{CommunityId, LocalUserId, PersonId, PostId}, schema::{ community, @@ -94,134 +95,14 @@ fn queries<'a>() -> Queries< ), ); - let is_saved = |person_id| { - exists( - post_saved::table.filter( - post_aggregates::post_id - .eq(post_saved::post_id) - .and(post_saved::person_id.eq(person_id)), - ), - ) - }; - - 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)), - ), - ) - }; - - 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>, my_person_id: Option, saved_only: bool| { - let is_saved_selection: Box> = - if saved_only { - Box::new(true.into_sql::()) - } else if let Some(person_id) = my_person_id { - Box::new(is_saved(person_id)) - } else { - Box::new(false.into_sql::()) - }; - - let is_read_selection: Box> = - if let Some(person_id) = my_person_id { - Box::new(is_read(person_id)) - } else { - Box::new(false.into_sql::()) - }; - - let is_creator_blocked_selection: Box> = - if let Some(person_id) = my_person_id { - Box::new(is_creator_blocked(person_id)) - } else { - Box::new(false.into_sql::()) - }; - - let subscribed_type_selection: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = 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::.into_sql::>()) - }; - - let score_selection: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = if let Some(person_id) = my_person_id { - Box::new(score(person_id)) - } else { - Box::new(None::.into_sql::>()) - }; - - let read_comments: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = 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::.into_sql::>()) - }; - query - .left_join(person_person_aggregates::table - .on( - post_aggregates::creator - .eq(person_person_aggregates::target_id) - .and(person_person_aggregates::person_id.nullable().eq(my_person_id)), - )) - .left_join(person_community_aggregates::table - .on( - post_aggregates::community_id - .eq(person_community_aggregates::community_id) - .and(person_community_aggregates::person_id.nullable().eq(my_person_id)), - )) - .left_join(person_post_aggregates::table - .on( - post_aggregates::post_id - .eq(person_post_aggregates::post_id) - .and(person_post_aggregates::person_id.nullable().eq(my_person_id)), - )) + .left_join(actions(person_actions::table, my_person_id, post_aggregates::creator_id)) + .left_join(actions(community_actions::table, my_person_id, post_aggregates::community_id)) + .left_join(actions(creator_community_actions, post_aggregates::creator_id.nullable(), post_aggregates::community_id)) + .left_join(actions(post_actions::table, my_person_id, post_aggregates::post_id)) .inner_join(person::table) .inner_join(community::table) .inner_join(post::table) @@ -229,19 +110,16 @@ fn queries<'a>() -> Queries< post::all_columns, person::all_columns, community::all_columns, - is_creator_banned_from_community, - creator_is_moderator, + creator_community_actions::received_ban.is_not_null(), + creator_community_actions::became_moderator.is_not_null(), creator_is_admin, post_aggregates::all_columns, - subscribed_type_selection, - person_post_aggregates::saved.is_not_null(), - person_post_aggregates::read.is_not_null(), - is_creator_blocked_selection, - score_selection, - coalesce( - post_aggregates::comments.nullable() - person_post_aggregates::read_comments.nullable(), - post_aggregates::comments, - ), + community_actions::follow_pending.nullable(), + post_actions::saved.is_not_null(), + post_actions::read.is_not_null(), + person_actions::blocked.is_not_null(), + post_actions::like_score.nullable(), + post_aggregates::comments - coalesce(post_actions::read_comments.nullable(), 0), )) }; @@ -332,13 +210,7 @@ fn queries<'a>() -> Queries< 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)), - ), - ); + let is_subscribed = community_actions::followed.is_not_null(); match listing_type { ListingType::Subscribed => query = query.filter(is_subscribed), ListingType::Local => { @@ -348,13 +220,7 @@ fn queries<'a>() -> Queries< } 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)), - ), - )); + query = query.filter(community_actions::became_moderator.is_not_null()); } } } @@ -404,8 +270,8 @@ fn queries<'a>() -> Queries< query = query.filter(person::bot_account.eq(false)); }; - if let (true, Some(person_id)) = (options.saved_only, my_person_id) { - query = query.filter(is_saved(person_id)); + if options.saved_only { + query = query.filter(post_actions::saved.is_not_null()); } // 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. @@ -416,41 +282,32 @@ fn queries<'a>() -> Queries< { // Do not hide read posts when it is a user profile view // Or, only hide read posts on non-profile views - if let (None, Some(person_id)) = (options.creator_id, my_person_id) { - query = query.filter(not(is_read(person_id))); + if let None = options.creator_id { + query = query.filter(post_actions::read.is_null()); } } - if let Some(person_id) = my_person_id { - if options.liked_only { - query = query.filter(score(person_id).eq(1)); - } else if options.disliked_only { - query = query.filter(score(person_id).eq(-1)); - } - }; + if options.liked_only { + query = query.filter(post_actions::like_score.eq(1)); + } else if options.disliked_only { + query = query.filter(post_actions::like_score.eq(-1)); + } // Dont filter blocks or missing languages for moderator view type - if let (Some(person_id), false) = ( - my_person_id, - options.listing_type.unwrap_or_default() == ListingType::ModeratorView, - ) { - // Filter out the rows with missing languages - 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)), - ), - )); + if let options.listing_type.unwrap_or_default() != ListingType::ModeratorView { + // Filter out the rows with missing languages if user is logged in + if my_person_id.is_some() { + 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)), + ), + )); + } // Don't show blocked instances, communities or persons - 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)), - ), - ))); + query = query.filter(community_actions::blocked.is_null()); query = query.filter(not(exists( instance_block::table.filter( post_aggregates::instance_id From 04f44df280644463161f1c155d8244a36046494e Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 13 Jan 2024 13:01:47 -0700 Subject: [PATCH 012/241] Update schema.rs --- crates/db_schema/src/schema.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index d34f49cb3b..99d5416a01 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -295,10 +295,10 @@ diesel::table! { } diesel::table! { - instance_block (person_id, instance_id) { + instance_actions (person_id, instance_id) { person_id -> Int4, instance_id -> Int4, - published -> Timestamptz, + blocked -> Nullable, } } From 4718efa6e9fc871a549bda8913b5e7bcc53d40d5 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 13 Jan 2024 13:14:47 -0700 Subject: [PATCH 013/241] Update post_view.rs --- crates/db_views/src/post_view.rs | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 38cdebb5c5..58ca3263ce 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -99,10 +99,11 @@ fn queries<'a>() -> Queries< my_person_id: Option, saved_only: bool| { query - .left_join(actions(person_actions::table, my_person_id, post_aggregates::creator_id)) .left_join(actions(community_actions::table, my_person_id, post_aggregates::community_id)) - .left_join(actions(creator_community_actions, post_aggregates::creator_id.nullable(), post_aggregates::community_id)) + .left_join(actions(person_actions::table, my_person_id, post_aggregates::creator_id)) .left_join(actions(post_actions::table, my_person_id, post_aggregates::post_id)) + .left_join(actions(instance_actions::table, my_person_id, post_aggregates::instance_id)) + .left_join(actions(creator_community_actions, post_aggregates::creator_id.nullable(), post_aggregates::community_id)) .inner_join(person::table) .inner_join(community::table) .inner_join(post::table) @@ -308,14 +309,8 @@ fn queries<'a>() -> Queries< // Don't show blocked instances, communities or persons query = query.filter(community_actions::blocked.is_null()); - 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)), - ), - ))); - query = query.filter(not(is_creator_blocked(person_id))); + query = query.filter(instance_actions::blocked.is_null()); + query = query.filter(person_actions::blocked.is_null()); } let featured_field = if options.community_id.is_none() || options.community_id_just_for_prefetch From 5ca3a31c3aa9e012c25986170aca4006338266ae Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 13 Jan 2024 13:58:56 -0700 Subject: [PATCH 014/241] Update schema.rs --- crates/db_schema/src/schema.rs | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 99d5416a01..ed1de97cdc 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -91,6 +91,12 @@ diesel::table! { path -> Ltree, distinguished -> Bool, language_id -> Int4, + score -> Int8, + upvotes -> Int8, + downvotes -> Int8, + child_count -> Int4, + hot_rank -> Float8, + controversy_rank -> Float8, } } @@ -104,19 +110,6 @@ diesel::table! { } } -diesel::table! { - comment_aggregates (comment_id) { - comment_id -> Int4, - score -> Int8, - upvotes -> Int8, - downvotes -> Int8, - published -> Timestamptz, - child_count -> Int4, - hot_rank -> Float8, - controversy_rank -> Float8, - } -} - diesel::table! { comment_reply (id) { id -> Int4, @@ -580,6 +573,10 @@ diesel::table! { bot_account -> Bool, ban_expires -> Nullable, instance_id -> Int4, + post_count -> Int8, + post_score -> Int8, + comment_count -> Int8, + comment_score -> Int8, } } @@ -593,16 +590,6 @@ diesel::table! { } } -diesel::table! { - person_aggregates (person_id) { - person_id -> Int4, - post_count -> Int8, - post_score -> Int8, - comment_count -> Int8, - comment_score -> Int8, - } -} - diesel::table! { person_ban (person_id) { person_id -> Int4, From eff5ffdb8115e346fa56d27035d457b719a7afae Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 13 Jan 2024 19:36:41 -0700 Subject: [PATCH 015/241] Update schema.rs --- crates/db_schema/src/schema.rs | 30 ++++++++---------------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index ed1de97cdc..08a5e40f3e 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -633,6 +633,14 @@ diesel::table! { language_id -> Int4, featured_community -> Bool, featured_local -> Bool, + comments -> Int8, + upvotes -> Int8, + downvotes -> Int8, + newest_comment_time_necro -> Timestamptz, + newest_comment_time -> Timestamptz, + instance_id -> Int4, + ranks_time -> Timestamptz, + community_users_active_month -> Int8, } } @@ -649,28 +657,6 @@ diesel::table! { } } -diesel::table! { - post_aggregates (post_id) { - post_id -> Int4, - comments -> Int8, - score -> Int8, - upvotes -> Int8, - downvotes -> Int8, - published -> Timestamptz, - newest_comment_time_necro -> Timestamptz, - newest_comment_time -> Timestamptz, - featured_community -> Bool, - featured_local -> Bool, - hot_rank -> Float8, - hot_rank_active -> Float8, - community_id -> Int4, - creator_id -> Int4, - controversy_rank -> Float8, - instance_id -> Int4, - scaled_rank -> Float8, - } -} - diesel::table! { post_report (id) { id -> Int4, From a8eb28076d92fcf35e3c2126a796a34568f504af Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 13 Jan 2024 19:39:06 -0700 Subject: [PATCH 016/241] Update schema.rs --- crates/db_schema/src/schema.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 08a5e40f3e..65993eef6f 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -186,16 +186,14 @@ diesel::table! { diesel::table! { community_aggregates (community_id) { - community_id -> Int4, subscribers -> Int8, posts -> Int8, comments -> Int8, - published -> Timestamptz, users_active_day -> Int8, users_active_week -> Int8, users_active_month -> Int8, users_active_half_year -> Int8, - hot_rank -> Float8, + ranks_time -> Timestamptz, } } From b1e7e83750e84e12cb992cfad07ac8f7892565e2 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 13 Jan 2024 19:57:16 -0700 Subject: [PATCH 017/241] Update post_view.rs --- crates/db_views/src/post_view.rs | 40 +++++++++++++------------------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 58ca3263ce..5bd8074e95 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -95,33 +95,31 @@ fn queries<'a>() -> Queries< ), ); - let all_joins = move |query: post_aggregates::BoxedQuery<'a, Pg>, - my_person_id: Option, - saved_only: bool| { - query - .left_join(actions(community_actions::table, my_person_id, post_aggregates::community_id)) - .left_join(actions(person_actions::table, my_person_id, post_aggregates::creator_id)) - .left_join(actions(post_actions::table, my_person_id, post_aggregates::post_id)) - .left_join(actions(instance_actions::table, my_person_id, post_aggregates::instance_id)) - .left_join(actions(creator_community_actions, post_aggregates::creator_id.nullable(), post_aggregates::community_id)) + let all_joins = move |my_person_id: Option| { + post::table + .left_join(actions(community_actions::table, my_person_id, post::community_id)) + .left_join(actions(person_actions::table, my_person_id, post::creator_id)) + .left_join(actions(post_actions::table, my_person_id, post::post_id)) + .left_join(actions(instance_actions::table, my_person_id, post::instance_id)) + .left_join(actions(creator_community_actions, post::creator_id.nullable(), post::community_id)) .inner_join(person::table) .inner_join(community::table) - .inner_join(post::table) .select(( - post::all_columns, - person::all_columns, - community::all_columns, + Post::as_select(), + Person::as_select(), + Community::as_select(), creator_community_actions::received_ban.is_not_null(), creator_community_actions::became_moderator.is_not_null(), creator_is_admin, - post_aggregates::all_columns, + PostAggregates::as_select(), community_actions::follow_pending.nullable(), post_actions::saved.is_not_null(), post_actions::read.is_not_null(), person_actions::blocked.is_not_null(), post_actions::like_score.nullable(), - post_aggregates::comments - coalesce(post_actions::read_comments.nullable(), 0), + post::comments - coalesce(post_actions::read_comments.nullable(), 0), )) + .into_boxed() }; let read = @@ -131,12 +129,8 @@ fn queries<'a>() -> Queries< 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, - false, - ); + ).filter(post::id.eq(post_id)); // Hide deleted and removed for non-admins or mods if !is_mod_or_admin { @@ -176,9 +170,7 @@ fn queries<'a>() -> Queries< let local_user_id_join = my_local_user_id.unwrap_or(LocalUserId(-1)); let mut query = all_joins( - post_aggregates::table.into_boxed(), my_person_id, - options.saved_only, ); // hide posts from deleted communities @@ -202,11 +194,11 @@ fn queries<'a>() -> Queries< .filter(post::removed.eq(false)); } if let Some(community_id) = options.community_id { - query = query.filter(post_aggregates::community_id.eq(community_id)); + query = query.filter(post::community_id.eq(community_id)); } if let Some(creator_id) = options.creator_id { - query = query.filter(post_aggregates::creator_id.eq(creator_id)); + query = query.filter(post::creator_id.eq(creator_id)); } if let Some(listing_type) = options.listing_type { From bf9320708fdd67b7ef1e81c2038636582bdcf3d5 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 20 Jan 2024 15:08:35 -0700 Subject: [PATCH 018/241] Update schema.rs --- crates/db_schema/src/schema.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 65993eef6f..5621a716c0 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -91,12 +91,10 @@ diesel::table! { path -> Ltree, distinguished -> Bool, language_id -> Int4, - score -> Int8, upvotes -> Int8, downvotes -> Int8, child_count -> Int4, - hot_rank -> Float8, - controversy_rank -> Float8, + ranks_time -> Timestamptz, } } From 96a24ebfa1457f029a705b7739f76cded58ff038 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 21 Jan 2024 09:01:08 -0700 Subject: [PATCH 019/241] Update post_view.rs --- crates/db_views/src/post_view.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 5bd8074e95..db3bfae651 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -108,8 +108,8 @@ fn queries<'a>() -> Queries< Post::as_select(), Person::as_select(), Community::as_select(), - creator_community_actions::received_ban.is_not_null(), - creator_community_actions::became_moderator.is_not_null(), + creator_community_actions.field(community_actions::received_ban).is_not_null(), + creator_community_actions.field(community_actions::became_moderator).is_not_null(), creator_is_admin, PostAggregates::as_select(), community_actions::follow_pending.nullable(), From a597eb65c1bbef29401464b38f5ae3b4e5995aae Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 21 Jan 2024 09:29:51 -0700 Subject: [PATCH 020/241] Update comment_report_view.rs --- crates/db_views/src/comment_report_view.rs | 46 ++++++++++------------ 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index 83ab5c0635..290688fc9e 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -32,22 +32,18 @@ fn queries<'a>() -> Queries< impl ListFn<'a, CommentReportView, (CommentReportQuery, &'a LocalUserView)>, > { let all_joins = |query: comment_report::BoxedQuery<'a, Pg>, my_person_id: PersonId| { - query + let my_vote = actions_subquery(comment_actions::table, my_person_id, comment_report::comment_id) + .select(comment_actions::like_score) + .single_value + + comment_report::table .inner_join(comment::table) .inner_join(post::table.on(comment::post_id.eq(post::id))) .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(person::table.on(comment_report::creator_id.eq(person::id))) .inner_join(aliases::person1.on(comment::creator_id.eq(aliases::person1.field(person::id)))) - .inner_join( - comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)), - ) - .left_join( - comment_like::table.on( - comment::id - .eq(comment_like::comment_id) - .and(comment_like::person_id.eq(my_person_id)), - ), - ) + .left_join(actions(comment_actions::table, my_person_id, comment::id)) + .left_join(actions(community_actions::table, comment::creator_id.nullable(), post::community_id)) .left_join( aliases::person2 .on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), @@ -68,25 +64,23 @@ fn queries<'a>() -> Queries< ); let read = move |mut conn: DbConn<'a>, (report_id, my_person_id): (CommentReportId, PersonId)| async move { - all_joins( - comment_report::table.find(report_id).into_boxed(), - my_person_id, - ) - .left_join( - community_person_ban::table.on( - community::id - .eq(community_person_ban::community_id) - .and(community_person_ban::person_id.eq(comment::creator_id)), - ), - ) - .select(selection) - .first::(&mut conn) - .await + all_joins(my_person_id) + .left_join( + community_person_ban::table.on( + community::id + .eq(community_person_ban::community_id) + .and(community_person_ban::person_id.eq(comment::creator_id)), + ), + ) + .filter(comment_report::id.eq(report_id)) + .select(selection) + .first::(&mut conn) + .await }; let list = move |mut conn: DbConn<'a>, (options, user): (CommentReportQuery, &'a LocalUserView)| async move { - let mut query = all_joins(comment_report::table.into_boxed(), user.person.id) + let mut query = all_joins(user.person.id) .left_join( community_person_ban::table.on( community::id From b7f11f1b9d3dd67308a26699fa143863950e7376 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 21 Jan 2024 09:44:59 -0700 Subject: [PATCH 021/241] Update comment_report_view.rs --- crates/db_views/src/comment_report_view.rs | 62 +++++++++------------- 1 file changed, 25 insertions(+), 37 deletions(-) diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index 290688fc9e..1ffc9b470e 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -32,9 +32,21 @@ fn queries<'a>() -> Queries< impl ListFn<'a, CommentReportView, (CommentReportQuery, &'a LocalUserView)>, > { let all_joins = |query: comment_report::BoxedQuery<'a, Pg>, my_person_id: PersonId| { + let creator_banned_from_community = exists( + actions_subquery(community_actions::table, comment::creator_id.nullable(), post::community_id) + .filter( + community_actions::received_ban_from_community.is_not_null() + .and( + community_actions::ban_expires + .is_null() + .or(community_actions::ban_expires.gt(now)), + ) + ) + ); + let my_vote = actions_subquery(comment_actions::table, my_person_id, comment_report::comment_id) .select(comment_actions::like_score) - .single_value + .single_value(); comment_report::table .inner_join(comment::table) @@ -42,36 +54,25 @@ fn queries<'a>() -> Queries< .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(person::table.on(comment_report::creator_id.eq(person::id))) .inner_join(aliases::person1.on(comment::creator_id.eq(aliases::person1.field(person::id)))) - .left_join(actions(comment_actions::table, my_person_id, comment::id)) - .left_join(actions(community_actions::table, comment::creator_id.nullable(), post::community_id)) .left_join( aliases::person2 .on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), ) + .select(( + comment_report::all_columns, + comment::all_columns, + post::all_columns, + community::all_columns, + person::all_columns, + aliases::person1.fields(person::all_columns), + creator_banned_from_community, + my_vote, + aliases::person2.fields(person::all_columns).nullable(), + )) }; - let selection = ( - comment_report::all_columns, - comment::all_columns, - post::all_columns, - community::all_columns, - person::all_columns, - aliases::person1.fields(person::all_columns), - comment_aggregates::all_columns, - community_person_ban::community_id.nullable().is_not_null(), - comment_like::score.nullable(), - aliases::person2.fields(person::all_columns).nullable(), - ); - let read = move |mut conn: DbConn<'a>, (report_id, my_person_id): (CommentReportId, PersonId)| async move { all_joins(my_person_id) - .left_join( - community_person_ban::table.on( - community::id - .eq(community_person_ban::community_id) - .and(community_person_ban::person_id.eq(comment::creator_id)), - ), - ) .filter(comment_report::id.eq(report_id)) .select(selection) .first::(&mut conn) @@ -80,20 +81,7 @@ fn queries<'a>() -> Queries< let list = move |mut conn: DbConn<'a>, (options, user): (CommentReportQuery, &'a LocalUserView)| async move { - let mut query = all_joins(user.person.id) - .left_join( - community_person_ban::table.on( - community::id - .eq(community_person_ban::community_id) - .and(community_person_ban::person_id.eq(comment::creator_id)) - .and( - community_person_ban::expires - .is_null() - .or(community_person_ban::expires.gt(now)), - ), - ), - ) - .select(selection); + let mut query = all_joins(user.person.id).into_boxed(); if let Some(community_id) = options.community_id { query = query.filter(post::community_id.eq(community_id)); From 0f9ffa81a21870f20d8719b5a8d1b4d82c2866de Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 11 Feb 2024 10:23:57 -0700 Subject: [PATCH 022/241] Update post_view.rs --- crates/db_views/src/post_view.rs | 38 ++++++++++++++------------------ 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index db3bfae651..0fd42d9643 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -87,37 +87,31 @@ fn queries<'a>() -> Queries< impl ReadFn<'a, PostView, (PostId, Option, bool)>, impl ListFn<'a, PostView, PostQuery<'a>>, > { - let creator_is_admin = exists( - local_user::table.filter( - post_aggregates::creator_id - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ); - let all_joins = move |my_person_id: Option| { - post::table - .left_join(actions(community_actions::table, my_person_id, post::community_id)) - .left_join(actions(person_actions::table, my_person_id, post::creator_id)) - .left_join(actions(post_actions::table, my_person_id, post::post_id)) - .left_join(actions(instance_actions::table, my_person_id, post::instance_id)) - .left_join(actions(creator_community_actions, post::creator_id.nullable(), post::community_id)) + post_aggregates::table .inner_join(person::table) + .left_join(local_user::table) .inner_join(community::table) + .inner_join(post::table) + .left_join(actions(community_actions::table, my_person_id, post_aggregates::community_id)) + .left_join(actions(person_actions::table, my_person_id, post_aggregates::creator_id)) + .left_join(actions(post_actions::table, my_person_id, post_aggregates::post_id)) + .left_join(actions(instance_actions::table, my_person_id, post_aggregates::instance_id)) + .left_join(actions(creator_community_actions, post_aggregates::creator_id.nullable(), post_aggregates::community_id)) .select(( - Post::as_select(), - Person::as_select(), - Community::as_select(), + post::all_columns, + person::all_columns, + community::all_columns, creator_community_actions.field(community_actions::received_ban).is_not_null(), creator_community_actions.field(community_actions::became_moderator).is_not_null(), creator_is_admin, - PostAggregates::as_select(), + post_aggregates::all_columns, community_actions::follow_pending.nullable(), post_actions::saved.is_not_null(), post_actions::read.is_not_null(), person_actions::blocked.is_not_null(), post_actions::like_score.nullable(), - post::comments - coalesce(post_actions::read_comments.nullable(), 0), + post_aggregates::comments - coalesce(post_actions::read_comments.nullable(), 0), )) .into_boxed() }; @@ -130,7 +124,7 @@ fn queries<'a>() -> Queries< let mut query = all_joins( my_person_id, - ).filter(post::id.eq(post_id)); + ).filter(post_aggregates::post_id.eq(post_id)); // Hide deleted and removed for non-admins or mods if !is_mod_or_admin { @@ -194,11 +188,11 @@ fn queries<'a>() -> Queries< .filter(post::removed.eq(false)); } if let Some(community_id) = options.community_id { - query = query.filter(post::community_id.eq(community_id)); + query = query.filter(post_aggregates::community_id.eq(community_id)); } if let Some(creator_id) = options.creator_id { - query = query.filter(post::creator_id.eq(creator_id)); + query = query.filter(post_aggregates::creator_id.eq(creator_id)); } if let Some(listing_type) = options.listing_type { From d4ffdf5de1733b16a443fa9f793559dcb7379ce4 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 11 Feb 2024 11:56:28 -0700 Subject: [PATCH 023/241] Update utils.rs --- crates/db_schema/src/utils.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index d7af175448..76e1a962f4 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -396,6 +396,19 @@ pub fn now() -> AsExprOf { diesel::dsl::now.into_sql::() } +pub fn actions(actions_table: T, person_id: Option, target_id: C) -> dsl::On< + T, + dsl::And< + dsl::Eq, BindIfSome>, + dsl::Eq, + >, +> +where + T: Table, +{ + actions_table.on(K0::default().nullable().eq(BindIfSome(person_id)).and(K1::default().eq(target_id))) +} + pub type ResultFuture<'a, T> = BoxFuture<'a, Result>; pub trait ReadFn<'a, T, Args>: Fn(DbConn<'a>, Args) -> ResultFuture<'a, T> {} From aa7077f89727494ace196ac8e3176e5a78053bc0 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 11 Feb 2024 18:24:42 -0700 Subject: [PATCH 024/241] Update schema.rs --- crates/db_schema/src/schema.rs | 61 +++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 16 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 5621a716c0..9a85140833 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -91,10 +91,6 @@ diesel::table! { path -> Ltree, distinguished -> Bool, language_id -> Int4, - upvotes -> Int8, - downvotes -> Int8, - child_count -> Int4, - ranks_time -> Timestamptz, } } @@ -108,6 +104,19 @@ diesel::table! { } } +diesel::table! { + comment_aggregates (comment_id) { + comment_id -> Int4, + score -> Int8, + upvotes -> Int8, + downvotes -> Int8, + published -> Timestamptz, + child_count -> Int4, + hot_rank -> Float8, + controversy_rank -> Float8, + } +} + diesel::table! { comment_reply (id) { id -> Int4, @@ -569,10 +578,6 @@ diesel::table! { bot_account -> Bool, ban_expires -> Nullable, instance_id -> Int4, - post_count -> Int8, - post_score -> Int8, - comment_count -> Int8, - comment_score -> Int8, } } @@ -586,6 +591,16 @@ diesel::table! { } } +diesel::table! { + person_aggregates (person_id) { + person_id -> Int4, + post_count -> Int8, + post_score -> Int8, + comment_count -> Int8, + comment_score -> Int8, + } +} + diesel::table! { person_ban (person_id) { person_id -> Int4, @@ -629,14 +644,6 @@ diesel::table! { language_id -> Int4, featured_community -> Bool, featured_local -> Bool, - comments -> Int8, - upvotes -> Int8, - downvotes -> Int8, - newest_comment_time_necro -> Timestamptz, - newest_comment_time -> Timestamptz, - instance_id -> Int4, - ranks_time -> Timestamptz, - community_users_active_month -> Int8, } } @@ -653,6 +660,28 @@ diesel::table! { } } +diesel::table! { + post_aggregates (post_id) { + post_id -> Int4, + comments -> Int8, + score -> Int8, + upvotes -> Int8, + downvotes -> Int8, + published -> Timestamptz, + newest_comment_time_necro -> Timestamptz, + newest_comment_time -> Timestamptz, + featured_community -> Bool, + featured_local -> Bool, + hot_rank -> Float8, + hot_rank_active -> Float8, + community_id -> Int4, + creator_id -> Int4, + controversy_rank -> Float8, + instance_id -> Int4, + scaled_rank -> Float8, + } +} + diesel::table! { post_report (id) { id -> Int4, From 9761ea8f7e2a723b7b6ea37e25b5cc5794581358 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 16 Feb 2024 17:43:59 +0000 Subject: [PATCH 025/241] stuff --- crates/db_schema/src/schema.rs | 5 +- crates/db_schema/src/utils.rs | 19 ++++---- crates/db_views/src/comment_report_view.rs | 56 ++++++++++++---------- crates/db_views/src/post_view.rs | 55 +++++++++++++++------ 4 files changed, 86 insertions(+), 49 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 4d2b0571b5..84d295f9df 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -201,14 +201,17 @@ diesel::table! { diesel::table! { community_aggregates (community_id) { + community_id -> Int4, subscribers -> Int8, posts -> Int8, comments -> Int8, + published -> Timestamptz, users_active_day -> Int8, users_active_week -> Int8, users_active_month -> Int8, users_active_half_year -> Int8, - ranks_time -> Timestamptz, + hot_rank -> Float8, + subscribers_local -> Int8, } } diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 558d6014b9..40b0b7712d 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -502,17 +502,20 @@ pub fn now() -> AsExprOf { diesel::dsl::now.into_sql::() } -pub fn actions(actions_table: T, person_id: Option, target_id: C) -> dsl::On< - T, - dsl::And< - dsl::Eq, BindIfSome>, - dsl::Eq, - >, -> +pub fn actions( + actions_table: T, + person_id: Option, + target_id: C, +) -> dsl::On, BindIfSome>, dsl::Eq>> where T: Table, { - actions_table.on(K0::default().nullable().eq(BindIfSome(person_id)).and(K1::default().eq(target_id))) + actions_table.on( + K0::default() + .nullable() + .eq(BindIfSome(person_id)) + .and(K1::default().eq(target_id)), + ) } pub type ResultFuture<'a, T> = BoxFuture<'a, Result>; diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index f2c47fefd2..e1618b7275 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -32,32 +32,29 @@ fn queries<'a>() -> Queries< impl ListFn<'a, CommentReportView, (CommentReportQuery, &'a LocalUserView)>, > { let all_joins = |query: comment_report::BoxedQuery<'a, Pg>, my_person_id: PersonId| { - let creator_banned_from_community = exists( - actions_subquery(community_actions::table, comment::creator_id.nullable(), post::community_id) - .filter( - community_actions::received_ban_from_community.is_not_null() - .and( - community_actions::ban_expires - .is_null() - .or(community_actions::ban_expires.gt(now)), - ) - ) - ); - - let my_vote = actions_subquery(comment_actions::table, my_person_id, comment_report::comment_id) - .select(comment_actions::like_score) - .single_value(); - - comment_report::table + query .inner_join(comment::table) .inner_join(post::table.on(comment::post_id.eq(post::id))) .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(person::table.on(comment_report::creator_id.eq(person::id))) .inner_join(aliases::person1.on(comment::creator_id.eq(aliases::person1.field(person::id)))) + .inner_join( + comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)), + ) + .left_join(actions( + comment_actions::table, + Some(my_person_id), + comment_report::comment_id, + )) .left_join( aliases::person2 .on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), ) + .left_join(actions( + creator_community_actions, + comment::creator_id.nullable(), + post_aggregates::community_id, + )) .select(( comment_report::all_columns, comment::all_columns, @@ -65,23 +62,32 @@ fn queries<'a>() -> Queries< community::all_columns, person::all_columns, aliases::person1.fields(person::all_columns), - creator_banned_from_community, - my_vote, + comment_aggregates::all_columns, + creator_community_actions + .field(community_actions::received_ban) + .is_null() + .or( + creator_community_actions + .field(community_actions::ban_expires) + .gt(now), + ), + post_actions::like_score.nullable(), aliases::person2.fields(person::all_columns).nullable(), )) }; let read = move |mut conn: DbConn<'a>, (report_id, my_person_id): (CommentReportId, PersonId)| async move { - all_joins(my_person_id) - .filter(comment_report::id.eq(report_id)) - .select(selection) - .first::(&mut conn) - .await + all_joins( + comment_report::table.find(report_id).into_boxed(), + my_person_id, + ) + .first::(&mut conn) + .await }; let list = move |mut conn: DbConn<'a>, (options, user): (CommentReportQuery, &'a LocalUserView)| async move { - let mut query = all_joins(user.person.id).into_boxed(); + let mut query = all_joins(comment_report::table.into_boxed(), user.person.id); if let Some(community_id) = options.community_id { query = query.filter(post::community_id.eq(community_id)); diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 215f2a946e..141429c7f0 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -65,23 +65,48 @@ fn queries<'a>() -> Queries< impl ReadFn<'a, PostView, (PostId, Option, bool)>, impl ListFn<'a, PostView, (PostQuery<'a>, &'a Site)>, > { - let all_joins = move |my_person_id: Option| { - post_aggregates::table + let all_joins = move |query: post_aggregates::BoxedQuery<'a, Pg>, + my_person_id: Option| { + query .inner_join(person::table) .left_join(local_user::table) .inner_join(community::table) .inner_join(post::table) - .left_join(actions(community_actions::table, my_person_id, post_aggregates::community_id)) - .left_join(actions(person_actions::table, my_person_id, post_aggregates::creator_id)) - .left_join(actions(post_actions::table, my_person_id, post_aggregates::post_id)) - .left_join(actions(instance_actions::table, my_person_id, post_aggregates::instance_id)) - .left_join(actions(creator_community_actions, post_aggregates::creator_id.nullable(), post_aggregates::community_id)) + .left_join(actions( + community_actions::table, + my_person_id, + post_aggregates::community_id, + )) + .left_join(actions( + person_actions::table, + my_person_id, + post_aggregates::creator_id, + )) + .left_join(actions( + post_actions::table, + my_person_id, + post_aggregates::post_id, + )) + .left_join(actions( + instance_actions::table, + my_person_id, + post_aggregates::instance_id, + )) + .left_join(actions( + creator_community_actions, + post_aggregates::creator_id.nullable(), + post_aggregates::community_id, + )) .select(( post::all_columns, person::all_columns, community::all_columns, - creator_community_actions.field(community_actions::received_ban).is_not_null(), - creator_community_actions.field(community_actions::became_moderator).is_not_null(), + creator_community_actions + .field(community_actions::received_ban) + .is_not_null(), + creator_community_actions + .field(community_actions::became_moderator) + .is_not_null(), creator_is_admin, post_aggregates::all_columns, community_actions::follow_pending.nullable(), @@ -91,7 +116,6 @@ fn queries<'a>() -> Queries< post_actions::like_score.nullable(), post_aggregates::comments - coalesce(post_actions::read_comments.nullable(), 0), )) - .into_boxed() }; let read = @@ -101,8 +125,11 @@ fn queries<'a>() -> Queries< 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, - ).filter(post_aggregates::post_id.eq(post_id)); + ); // Hide deleted and removed for non-admins or mods if !is_mod_or_admin { @@ -149,9 +176,7 @@ fn queries<'a>() -> Queries< let person_id_join = my_person_id.unwrap_or(PersonId(-1)); let local_user_id_join = my_local_user_id.unwrap_or(LocalUserId(-1)); - let mut query = all_joins( - my_person_id, - ); + let mut query = all_joins(post_aggregates::table.into_boxed(), my_person_id); // hide posts from deleted communities query = query.filter(community::deleted.eq(false)); @@ -274,7 +299,7 @@ fn queries<'a>() -> Queries< } // Dont filter blocks or missing languages for moderator view type - if let options.listing_type.unwrap_or_default() != ListingType::ModeratorView { + if options.listing_type.unwrap_or_default() != ListingType::ModeratorView { // Filter out the rows with missing languages if user is logged in if my_person_id.is_some() { query = query.filter(exists( From 893d62a8122e53b014738af48c6f4a52e269820e Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 16 Feb 2024 17:57:59 +0000 Subject: [PATCH 026/241] stuff --- crates/db_schema/src/schema.rs | 67 ++++++++++++---------------------- crates/db_schema/src/utils.rs | 14 +------ 2 files changed, 25 insertions(+), 56 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 84d295f9df..8b34c38d7b 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -102,9 +102,10 @@ diesel::table! { comment_actions (person_id, comment_id) { person_id -> Int4, comment_id -> Int4, - saved: Nullable, - liked: Nullable, - like_score: Nullable, + post_id -> Int4, + saved -> Nullable, + liked -> Nullable, + like_score -> Nullable, } } @@ -194,7 +195,7 @@ diesel::table! { followed -> Nullable, follow_pending -> Nullable, became_moderator -> Nullable, - received_ban: Nullable, + received_ban -> Nullable, ban_expires -> Nullable, } } @@ -669,10 +670,10 @@ diesel::table! { post_id -> Int4, read_comments -> Int8, read_comments_published -> Nullable, - read: Nullable, - saved: Nullable, - liked: Nullable, - like_score: Nullable, + read -> Nullable, + saved -> Nullable, + liked -> Nullable, + like_score -> Nullable, } } @@ -860,35 +861,27 @@ diesel::joinable!(admin_purge_post -> person (admin_person_id)); diesel::joinable!(comment -> language (language_id)); diesel::joinable!(comment -> person (creator_id)); diesel::joinable!(comment -> post (post_id)); +diesel::joinable!(comment_actions -> comment (comment_id)); +diesel::joinable!(comment_actions -> person (person_id)); +diesel::joinable!(comment_actions -> post (post_id)); diesel::joinable!(comment_aggregates -> comment (comment_id)); -diesel::joinable!(comment_like -> comment (comment_id)); -diesel::joinable!(comment_like -> person (person_id)); -diesel::joinable!(comment_like -> post (post_id)); diesel::joinable!(comment_reply -> comment (comment_id)); diesel::joinable!(comment_reply -> person (recipient_id)); diesel::joinable!(comment_report -> comment (comment_id)); -diesel::joinable!(comment_saved -> comment (comment_id)); -diesel::joinable!(comment_saved -> person (person_id)); diesel::joinable!(community -> instance (instance_id)); +diesel::joinable!(community_actions -> community (community_id)); +diesel::joinable!(community_actions -> person (person_id)); diesel::joinable!(community_aggregates -> community (community_id)); -diesel::joinable!(community_block -> community (community_id)); -diesel::joinable!(community_block -> person (person_id)); -diesel::joinable!(community_follower -> community (community_id)); -diesel::joinable!(community_follower -> person (person_id)); diesel::joinable!(community_language -> community (community_id)); diesel::joinable!(community_language -> language (language_id)); -diesel::joinable!(community_moderator -> community (community_id)); -diesel::joinable!(community_moderator -> person (person_id)); -diesel::joinable!(community_person_ban -> community (community_id)); -diesel::joinable!(community_person_ban -> person (person_id)); diesel::joinable!(custom_emoji -> local_site (local_site_id)); diesel::joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id)); diesel::joinable!(email_verification -> local_user (local_user_id)); diesel::joinable!(federation_allowlist -> instance (instance_id)); diesel::joinable!(federation_blocklist -> instance (instance_id)); diesel::joinable!(federation_queue_state -> instance (instance_id)); -diesel::joinable!(instance_block -> instance (instance_id)); -diesel::joinable!(instance_block -> person (person_id)); +diesel::joinable!(instance_actions -> instance (instance_id)); +diesel::joinable!(instance_actions -> person (person_id)); diesel::joinable!(local_image -> local_user (local_user_id)); diesel::joinable!(local_site -> site (site_id)); diesel::joinable!(local_site_rate_limit -> local_site (local_site_id)); @@ -917,22 +910,16 @@ diesel::joinable!(person_aggregates -> person (person_id)); diesel::joinable!(person_ban -> person (person_id)); diesel::joinable!(person_mention -> comment (comment_id)); diesel::joinable!(person_mention -> person (recipient_id)); -diesel::joinable!(person_post_aggregates -> person (person_id)); -diesel::joinable!(person_post_aggregates -> post (post_id)); diesel::joinable!(post -> community (community_id)); diesel::joinable!(post -> language (language_id)); diesel::joinable!(post -> person (creator_id)); +diesel::joinable!(post_actions -> person (person_id)); +diesel::joinable!(post_actions -> post (post_id)); diesel::joinable!(post_aggregates -> community (community_id)); diesel::joinable!(post_aggregates -> instance (instance_id)); diesel::joinable!(post_aggregates -> person (creator_id)); diesel::joinable!(post_aggregates -> post (post_id)); -diesel::joinable!(post_like -> person (person_id)); -diesel::joinable!(post_like -> post (post_id)); -diesel::joinable!(post_read -> person (person_id)); -diesel::joinable!(post_read -> post (post_id)); diesel::joinable!(post_report -> post (post_id)); -diesel::joinable!(post_saved -> person (person_id)); -diesel::joinable!(post_saved -> post (post_id)); diesel::joinable!(private_message_report -> private_message (private_message_id)); diesel::joinable!(registration_application -> local_user (local_user_id)); diesel::joinable!(registration_application -> person (admin_id)); @@ -949,18 +936,14 @@ diesel::allow_tables_to_appear_in_same_query!( admin_purge_post, captcha_answer, comment, + comment_actions, comment_aggregates, - comment_like, comment_reply, comment_report, - comment_saved, community, + community_actions, community_aggregates, - community_block, - community_follower, community_language, - community_moderator, - community_person_ban, custom_emoji, custom_emoji_keyword, email_verification, @@ -968,7 +951,7 @@ diesel::allow_tables_to_appear_in_same_query!( federation_blocklist, federation_queue_state, instance, - instance_block, + instance_actions, language, local_image, local_site, @@ -989,18 +972,14 @@ diesel::allow_tables_to_appear_in_same_query!( mod_transfer_community, password_reset_request, person, + person_actions, person_aggregates, person_ban, - person_block, - person_follower, person_mention, - person_post_aggregates, post, + post_actions, post_aggregates, - post_like, - post_read, post_report, - post_saved, private_message, private_message_report, received_activity, diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 40b0b7712d..c94a907abc 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -1,7 +1,7 @@ use crate::{ diesel::Connection, diesel_migrations::MigrationHarness, - newtypes::DbUrl, + newtypes::{DbUrl, PersonId}, CommentSortType, SortType, }; @@ -10,17 +10,7 @@ use anyhow::Context; use chrono::{DateTime, Utc}; use deadpool::Runtime; use diesel::{ - backend::Backend, - deserialize::FromSql, - helper_types::AsExprOf, - pg::Pg, - query_builder::{Query, QueryFragment}, - query_dsl::methods::LimitDsl, - result::{ConnectionError, ConnectionResult, Error as DieselError, Error::QueryBuilderError}, - serialize::{Output, ToSql}, - sql_types::{self, Text, Timestamptz}, - IntoSql, - PgConnection, + backend::Backend, deserialize::FromSql, dsl, helper_types::AsExprOf, pg::Pg, query_builder::{Query, QueryFragment}, query_dsl::methods::LimitDsl, result::{ConnectionError, ConnectionResult, Error::{self as DieselError, QueryBuilderError}}, serialize::{Output, ToSql}, sql_types::{self, Text, Timestamptz}, IntoSql, PgConnection, Table }; use diesel_async::{ pg::AsyncPgConnection, From 66bdc491d1c0074fa490ecc80a15dfea51299883 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 16 Feb 2024 19:53:14 +0000 Subject: [PATCH 027/241] fix actions --- crates/db_schema/src/utils.rs | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index c94a907abc..3c46bac846 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -10,7 +10,25 @@ use anyhow::Context; use chrono::{DateTime, Utc}; use deadpool::Runtime; use diesel::{ - backend::Backend, deserialize::FromSql, dsl, helper_types::AsExprOf, pg::Pg, query_builder::{Query, QueryFragment}, query_dsl::methods::LimitDsl, result::{ConnectionError, ConnectionResult, Error::{self as DieselError, QueryBuilderError}}, serialize::{Output, ToSql}, sql_types::{self, Text, Timestamptz}, IntoSql, PgConnection, Table + backend::Backend, + deserialize::FromSql, + dsl, + expression::AsExpression, + helper_types::AsExprOf, + pg::Pg, + query_builder::{Query, QueryFragment}, + query_dsl::methods::LimitDsl, + result::{ + ConnectionError, + ConnectionResult, + Error::{self as DieselError, QueryBuilderError}, + }, + serialize::{Output, ToSql}, + sql_types::{self, SqlType, Text, Timestamptz}, + Expression, + IntoSql, + PgConnection, + Table, }; use diesel_async::{ pg::AsyncPgConnection, @@ -492,18 +510,29 @@ pub fn now() -> AsExprOf { diesel::dsl::now.into_sql::() } +// TODO: BindIfSome pub fn actions( actions_table: T, person_id: Option, target_id: C, -) -> dsl::On, BindIfSome>, dsl::Eq>> +) -> dsl::On, Option>, dsl::Eq>> where T: Table, + K0: Default + Expression + Sized, + dsl::Nullable: diesel::ExpressionMethods, + as Expression>::SqlType: SqlType, + Option: AsExpression< as Expression>::SqlType>, + K1: Default + diesel::ExpressionMethods, + ::SqlType: SqlType, + C: AsExpression<::SqlType>, + dsl::Eq, Option>: + Expression>, + dsl::Eq: Expression, { actions_table.on( K0::default() .nullable() - .eq(BindIfSome(person_id)) + .eq(person_id) .and(K1::default().eq(target_id)), ) } From 8cb325bf88e596228fe38082609e49bd11269905 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 20 Feb 2024 02:24:17 +0000 Subject: [PATCH 028/241] PostLike --- crates/db_schema/src/impls/post.rs | 112 +++++++++++++++++++++------ crates/db_schema/src/source/post.rs | 12 ++- crates/db_schema/src/utils.rs | 2 + crates/db_schema/src/utils/uplete.rs | 33 ++++++++ 4 files changed, 128 insertions(+), 31 deletions(-) create mode 100644 crates/db_schema/src/utils/uplete.rs diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index f49af62269..6c356c83e2 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -1,20 +1,23 @@ use crate::{ newtypes::{CommunityId, DbUrl, PersonId, PostId}, - schema::post::dsl::{ - ap_id, - body, - community_id, - creator_id, - deleted, - featured_community, - local, - name, - post, - published, - removed, - thumbnail_url, - updated, - url, + schema::{ + post::dsl::{ + ap_id, + body, + community_id, + creator_id, + deleted, + featured_community, + local, + name, + post, + published, + removed, + thumbnail_url, + updated, + url, + }, + post_actions, }, source::post::{ Post, @@ -32,6 +35,7 @@ use crate::{ functions::coalesce, get_conn, naive_now, + uplete::Uplete, DbPool, DELETED_REPLACEMENT_TEXT, FETCH_LIMIT_MAX, @@ -40,8 +44,19 @@ use crate::{ }, }; use ::url::Url; -use chrono::{Duration, Utc}; -use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl, TextExpressionMethods}; +use chrono::{DateTime, Duration, Utc}; +use diesel::{ + dsl::{self, insert_into}, + pg::Pg, + result::Error, + sql_types, + BoxableExpression, + ExpressionMethods, + IntoSql, + PgExpressionMethods, + QueryDsl, + TextExpressionMethods, +}; use diesel_async::RunQueryDsl; use std::collections::HashSet; @@ -253,18 +268,65 @@ impl Post { } } +fn uplete_actions( + person_id: PersonId, + post_id: PostId, + update_values: T, +) -> Uplete< + post_actions::table, + dsl::And, dsl::Eq>, + Box>, + T, +> { + Uplete { + target: post_actions::table, + filter: post_actions::person_id.eq(person_id).and(post_actions::post_id.eq(post_id)), + delete_condition: Box::new( + post_actions::all_columns + .into_sql::>() + .is_not_distinct_from(( + post_actions::person_id, + post_actions::post_id, + None::, + None::>, + None::>, + None::>, + None::>, + None::, + )), + ), + update_values, + } +} + +impl PostLike { + fn as_select_unwrap() -> ( + post_actions::post_id, + post_actions::person_id, + dsl::AssumeNotNull, + dsl::AssumeNotNull, + ) { + ( + post_actions::post_id, + post_actions::person_id, + post_actions::like_score.assume_not_null(), + post_actions::liked.assume_not_null(), + ) + } +} + #[async_trait] impl Likeable for PostLike { type Form = PostLikeForm; type IdType = PostId; async fn like(pool: &mut DbPool<'_>, post_like_form: &PostLikeForm) -> Result { - use crate::schema::post_like::dsl::{person_id, post_id, post_like}; let conn = &mut get_conn(pool).await?; - insert_into(post_like) + insert_into(post_actions::table) .values(post_like_form) - .on_conflict((post_id, person_id)) + .on_conflict((post_actions::post_id, post_actions::person_id)) .do_update() .set(post_like_form) + .returning(PostLike::as_select_unwrap()) .get_result::(conn) .await } @@ -273,11 +335,13 @@ impl Likeable for PostLike { person_id: PersonId, post_id: PostId, ) -> Result { - use crate::schema::post_like::dsl; let conn = &mut get_conn(pool).await?; - diesel::delete(dsl::post_like.find((person_id, post_id))) - .execute(conn) - .await + uplete_actions(person_id, post_id,( + post_actions::like_score.eq(None::), + post_actions::liked.eq(None::>), + )) + .execute(conn) + .await } } diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 4ac3e2a659..2a0b4f1591 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -1,6 +1,6 @@ use crate::newtypes::{CommunityId, DbUrl, LanguageId, PersonId, PostId}; #[cfg(feature = "full")] -use crate::schema::{post, post_like, post_read, post_saved}; +use crate::schema::{post, post_actions}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -115,12 +115,9 @@ pub struct PostUpdateForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr( - feature = "full", - derive(Identifiable, Queryable, Selectable, Associations) -)] +#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] -#[cfg_attr(feature = "full", diesel(table_name = post_like))] +#[cfg_attr(feature = "full", diesel(table_name = post_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct PostLike { @@ -132,10 +129,11 @@ pub struct PostLike { #[derive(Clone)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = post_like))] +#[cfg_attr(feature = "full", diesel(table_name = post_actions))] pub struct PostLikeForm { pub post_id: PostId, pub person_id: PersonId, + #[cfg_attr(feature = "full", diesel(column_name = like_score))] pub score: i16, } diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 878b0e5619..ad5cbc82be 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -1,3 +1,5 @@ +pub mod uplete; + use crate::{ diesel::Connection, diesel_migrations::MigrationHarness, diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs new file mode 100644 index 0000000000..638006b93d --- /dev/null +++ b/crates/db_schema/src/utils/uplete.rs @@ -0,0 +1,33 @@ +use diesel::{ + pg::Pg, + query_builder::{AstPass, QueryFragment}, + QueryResult, +}; + +// TODO: use trait bounds to validate fields + +#[derive(QueryId)] +pub struct Uplete { + pub target: T, + /// Must only match 1 row + pub filter: F, + pub delete_condition: D, + pub update_values: U, +} + +impl, F: QueryFragment, D: QueryFragment, U: QueryFragment> QueryFragment + for Uplete +{ + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { + out.push_sql("MERGE INTO "); + self.target.walk_ast(out.reborrow())?; + out.push_sql("USING (VALUES (1)) AS uplete_source ON ("); + self.filter.walk_ast(out.reborrow())?; + out.push_sql(") WHEN MATCHED AND ("); + self.delete_condition.walk_ast(out.reborrow())?; + out.push_sql(") THEN DELETE WHEN MATCHED THEN UPDATE SET "); + self.update_values.walk_ast(out.reborrow())?; + + Ok(()) + } +} From e12e2c2529a3105a71f57f83104a57fd6ab1d98c Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 20 Feb 2024 02:26:43 +0000 Subject: [PATCH 029/241] fmt --- crates/db_schema/src/impls/post.rs | 16 +++++++++++----- crates/db_schema/src/utils/uplete.rs | 4 ++-- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 6c356c83e2..e967009974 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -280,7 +280,9 @@ fn uplete_actions( > { Uplete { target: post_actions::table, - filter: post_actions::person_id.eq(person_id).and(post_actions::post_id.eq(post_id)), + filter: post_actions::person_id + .eq(person_id) + .and(post_actions::post_id.eq(post_id)), delete_condition: Box::new( post_actions::all_columns .into_sql::>() @@ -336,10 +338,14 @@ impl Likeable for PostLike { post_id: PostId, ) -> Result { let conn = &mut get_conn(pool).await?; - uplete_actions(person_id, post_id,( - post_actions::like_score.eq(None::), - post_actions::liked.eq(None::>), - )) + uplete_actions( + person_id, + post_id, + ( + post_actions::like_score.eq(None::), + post_actions::liked.eq(None::>), + ), + ) .execute(conn) .await } diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 638006b93d..f39a4706d4 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -15,8 +15,8 @@ pub struct Uplete { pub update_values: U, } -impl, F: QueryFragment, D: QueryFragment, U: QueryFragment> QueryFragment - for Uplete +impl, F: QueryFragment, D: QueryFragment, U: QueryFragment> + QueryFragment for Uplete { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { out.push_sql("MERGE INTO "); From d3f57ba14dd416781745a27ca6e50bde679c0cba Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 24 Feb 2024 03:33:35 +0000 Subject: [PATCH 030/241] more post stuff (partial) --- crates/db_schema/src/impls/post.rs | 55 ++++++++++++---------------- crates/db_schema/src/source/post.rs | 6 +-- crates/db_schema/src/utils/uplete.rs | 35 +++++++++++++----- 3 files changed, 53 insertions(+), 43 deletions(-) diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index e967009974..23edc98132 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -32,15 +32,7 @@ use crate::{ }, traits::{Crud, Likeable, Saveable}, utils::{ - functions::coalesce, - get_conn, - naive_now, - uplete::Uplete, - DbPool, - DELETED_REPLACEMENT_TEXT, - FETCH_LIMIT_MAX, - SITEMAP_DAYS, - SITEMAP_LIMIT, + functions::coalesce, get_conn, naive_now, now, uplete::Uplete, DbPool, DELETED_REPLACEMENT_TEXT, FETCH_LIMIT_MAX, SITEMAP_DAYS, SITEMAP_LIMIT }, }; use ::url::Url; @@ -56,6 +48,7 @@ use diesel::{ PgExpressionMethods, QueryDsl, TextExpressionMethods, + NullableExpressionMethods, }; use diesel_async::RunQueryDsl; use std::collections::HashSet; @@ -269,20 +262,17 @@ impl Post { } fn uplete_actions( - person_id: PersonId, - post_id: PostId, + keys: impl IntoIterator, update_values: T, ) -> Uplete< post_actions::table, - dsl::And, dsl::Eq>, + Vec<(dsl::AsExprOf, dsl::AsExprOf)>, Box>, T, > { Uplete { target: post_actions::table, - filter: post_actions::person_id - .eq(person_id) - .and(post_actions::post_id.eq(post_id)), + keys: keys.into_iter().map(|(a, b)| (a.into_sql::(), b.into_sql::())).collect::>(), delete_condition: Box::new( post_actions::all_columns .into_sql::>() @@ -323,6 +313,7 @@ impl Likeable for PostLike { type IdType = PostId; async fn like(pool: &mut DbPool<'_>, post_like_form: &PostLikeForm) -> Result { let conn = &mut get_conn(pool).await?; + let post_like_form = (post_like_form.clone(), post_actions::liked.eq(now()), "a"); insert_into(post_actions::table) .values(post_like_form) .on_conflict((post_actions::post_id, post_actions::person_id)) @@ -339,8 +330,8 @@ impl Likeable for PostLike { ) -> Result { let conn = &mut get_conn(pool).await?; uplete_actions( - person_id, - post_id, + [(person_id, + post_id,)], ( post_actions::like_score.eq(None::), post_actions::liked.eq(None::>), @@ -355,20 +346,19 @@ impl Likeable for PostLike { impl Saveable for PostSaved { type Form = PostSavedForm; async fn save(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result { - use crate::schema::post_saved::dsl::{person_id, post_id, post_saved}; let conn = &mut get_conn(pool).await?; - insert_into(post_saved) + let post_saved_form = (post_saved_form.clone(), post_actions::saved.eq(now())); + insert_into(post_actions::table) .values(post_saved_form) - .on_conflict((post_id, person_id)) + .on_conflict((post_actions::post_id, post_actions::person_id)) .do_update() .set(post_saved_form) .get_result::(conn) .await } async fn unsave(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result { - use crate::schema::post_saved::dsl::post_saved; let conn = &mut get_conn(pool).await?; - diesel::delete(post_saved.find((post_saved_form.person_id, post_saved_form.post_id))) + uplete_actions([(post_saved_form.person_id, post_saved_form.post_id)], post_actions::saved.eq(None::>)) .execute(conn) .await } @@ -380,16 +370,20 @@ impl PostRead { post_ids: HashSet, person_id: PersonId, ) -> Result { - use crate::schema::post_read::dsl::post_read; let conn = &mut get_conn(pool).await?; let forms = post_ids .into_iter() - .map(|post_id| PostReadForm { post_id, person_id }) - .collect::>(); - insert_into(post_read) + .map(|post_id| ( + PostReadForm{post_id,person_id}, + post_actions::read.eq(now().nullable()) + )) + .collect::>(); + insert_into(post_actions::table) .values(forms) - .on_conflict_do_nothing() + .on_conflict((post_actions::person_id, post_actions::post_id)) + .do_update() + .set(post_actions::read.eq(now().nullable())) .execute(conn) .await } @@ -399,13 +393,12 @@ impl PostRead { post_id_: HashSet, person_id_: PersonId, ) -> Result { - use crate::schema::post_read::dsl::{person_id, post_id, post_read}; let conn = &mut get_conn(pool).await?; diesel::delete( - post_read - .filter(post_id.eq_any(post_id_)) - .filter(person_id.eq(person_id_)), + post_actions::table + .filter(post_actions::post_id.eq_any(post_id_)) + .filter(post_actions::person_id.eq(person_id_)), ) .execute(conn) .await diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 2a0b4f1591..5448b4d60b 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -162,10 +162,10 @@ pub struct PostSavedForm { #[derive(PartialEq, Eq, Debug)] #[cfg_attr( feature = "full", - derive(Identifiable, Queryable, Selectable, Associations) + derive(Identifiable, Queryable, Associations) )] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] -#[cfg_attr(feature = "full", diesel(table_name = post_read))] +#[cfg_attr(feature = "full", diesel(table_name = post_actions))] #[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct PostRead { @@ -175,7 +175,7 @@ pub struct PostRead { } #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = post_read))] +#[cfg_attr(feature = "full", diesel(table_name = post_actions))] pub(crate) struct PostReadForm { pub post_id: PostId, pub person_id: PersonId, diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index f39a4706d4..f9dbd121d7 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -1,29 +1,46 @@ use diesel::{ pg::Pg, query_builder::{AstPass, QueryFragment}, - QueryResult, + QueryResult, Table, }; // TODO: use trait bounds to validate fields +/// Find each row in `target` that has its primary key listed in `keys`, and +/// if `delete_condition` is true, then delete the row, otherwise update it with `update_values` #[derive(QueryId)] -pub struct Uplete { +pub struct Uplete { pub target: T, - /// Must only match 1 row - pub filter: F, + pub keys: K, pub delete_condition: D, pub update_values: U, } -impl, F: QueryFragment, D: QueryFragment, U: QueryFragment> - QueryFragment for Uplete +impl + Table, K, D: QueryFragment, U: QueryFragment> + QueryFragment for Uplete + where + for<'a> &'a K: IntoIterator, + for<'a> <&'a K as IntoIterator>::Item: QueryFragment, + T::PrimaryKey: QueryFragment { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { + // Needed because of the keys loop + out.unsafe_to_cache_prepared(); + out.push_sql("MERGE INTO "); self.target.walk_ast(out.reborrow())?; - out.push_sql("USING (VALUES (1)) AS uplete_source ON ("); - self.filter.walk_ast(out.reborrow())?; - out.push_sql(") WHEN MATCHED AND ("); + out.push_sql("USING (VALUES "); + for (i, key) in (&self.keys).into_iter().enumerate() { + if i != 0 { + out.push_sql(","); + } + out.push_sql("(ROW("); + key.walk_ast(out.reborrow())?; + out.push_sql("))"); + } + out.push_sql(") AS uplete_source (uplete_key) ON ROW("); + self.target.primary_key().walk_ast(out.reborrow())?; + out.push_sql(") = uplete_source.uplete_key WHEN MATCHED AND ("); self.delete_condition.walk_ast(out.reborrow())?; out.push_sql(") THEN DELETE WHEN MATCHED THEN UPDATE SET "); self.update_values.walk_ast(out.reborrow())?; From 9a18388e06d31ebd8bff0ae58b126a6f2342ae28 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 24 Feb 2024 17:55:30 +0000 Subject: [PATCH 031/241] remove uplete --- crates/db_schema/src/impls/post.rs | 76 +++++++++------------------- crates/db_schema/src/source/post.rs | 5 +- crates/db_schema/src/utils.rs | 2 - crates/db_schema/src/utils/uplete.rs | 50 ------------------ 4 files changed, 26 insertions(+), 107 deletions(-) delete mode 100644 crates/db_schema/src/utils/uplete.rs diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 23edc98132..be829af7cb 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -32,23 +32,26 @@ use crate::{ }, traits::{Crud, Likeable, Saveable}, utils::{ - functions::coalesce, get_conn, naive_now, now, uplete::Uplete, DbPool, DELETED_REPLACEMENT_TEXT, FETCH_LIMIT_MAX, SITEMAP_DAYS, SITEMAP_LIMIT + functions::coalesce, + get_conn, + naive_now, + now, + DbPool, + DELETED_REPLACEMENT_TEXT, + FETCH_LIMIT_MAX, + SITEMAP_DAYS, + SITEMAP_LIMIT, }, }; use ::url::Url; use chrono::{DateTime, Duration, Utc}; use diesel::{ dsl::{self, insert_into}, - pg::Pg, result::Error, - sql_types, - BoxableExpression, ExpressionMethods, - IntoSql, - PgExpressionMethods, + NullableExpressionMethods, QueryDsl, TextExpressionMethods, - NullableExpressionMethods, }; use diesel_async::RunQueryDsl; use std::collections::HashSet; @@ -261,36 +264,6 @@ impl Post { } } -fn uplete_actions( - keys: impl IntoIterator, - update_values: T, -) -> Uplete< - post_actions::table, - Vec<(dsl::AsExprOf, dsl::AsExprOf)>, - Box>, - T, -> { - Uplete { - target: post_actions::table, - keys: keys.into_iter().map(|(a, b)| (a.into_sql::(), b.into_sql::())).collect::>(), - delete_condition: Box::new( - post_actions::all_columns - .into_sql::>() - .is_not_distinct_from(( - post_actions::person_id, - post_actions::post_id, - None::, - None::>, - None::>, - None::>, - None::>, - None::, - )), - ), - update_values, - } -} - impl PostLike { fn as_select_unwrap() -> ( post_actions::post_id, @@ -329,16 +302,13 @@ impl Likeable for PostLike { post_id: PostId, ) -> Result { let conn = &mut get_conn(pool).await?; - uplete_actions( - [(person_id, - post_id,)], - ( + diesel::update(post_actions::table.find((person_id, post_id))) + .set(( post_actions::like_score.eq(None::), post_actions::liked.eq(None::>), - ), - ) - .execute(conn) - .await + )) + .execute(conn) + .await } } @@ -358,7 +328,8 @@ impl Saveable for PostSaved { } async fn unsave(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result { let conn = &mut get_conn(pool).await?; - uplete_actions([(post_saved_form.person_id, post_saved_form.post_id)], post_actions::saved.eq(None::>)) + diesel::update(post_actions::table.find((post_saved_form.person_id, post_saved_form.post_id))) + .set(post_actions::saved.eq(None::>)) .execute(conn) .await } @@ -374,10 +345,12 @@ impl PostRead { let forms = post_ids .into_iter() - .map(|post_id| ( - PostReadForm{post_id,person_id}, - post_actions::read.eq(now().nullable()) - )) + .map(|post_id| { + ( + PostReadForm { post_id, person_id }, + post_actions::read.eq(now().nullable()), + ) + }) .collect::>(); insert_into(post_actions::table) .values(forms) @@ -395,11 +368,12 @@ impl PostRead { ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::delete( + diesel::update( post_actions::table .filter(post_actions::post_id.eq_any(post_id_)) .filter(post_actions::person_id.eq(person_id_)), ) + .set(post_actions::read.eq(None::>)) .execute(conn) .await } diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 5448b4d60b..1ac43e4ea2 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -160,10 +160,7 @@ pub struct PostSavedForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr( - feature = "full", - derive(Identifiable, Queryable, Associations) -)] +#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))] #[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))] diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index ad5cbc82be..878b0e5619 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -1,5 +1,3 @@ -pub mod uplete; - use crate::{ diesel::Connection, diesel_migrations::MigrationHarness, diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs deleted file mode 100644 index f9dbd121d7..0000000000 --- a/crates/db_schema/src/utils/uplete.rs +++ /dev/null @@ -1,50 +0,0 @@ -use diesel::{ - pg::Pg, - query_builder::{AstPass, QueryFragment}, - QueryResult, Table, -}; - -// TODO: use trait bounds to validate fields - -/// Find each row in `target` that has its primary key listed in `keys`, and -/// if `delete_condition` is true, then delete the row, otherwise update it with `update_values` -#[derive(QueryId)] -pub struct Uplete { - pub target: T, - pub keys: K, - pub delete_condition: D, - pub update_values: U, -} - -impl + Table, K, D: QueryFragment, U: QueryFragment> - QueryFragment for Uplete - where - for<'a> &'a K: IntoIterator, - for<'a> <&'a K as IntoIterator>::Item: QueryFragment, - T::PrimaryKey: QueryFragment -{ - fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> QueryResult<()> { - // Needed because of the keys loop - out.unsafe_to_cache_prepared(); - - out.push_sql("MERGE INTO "); - self.target.walk_ast(out.reborrow())?; - out.push_sql("USING (VALUES "); - for (i, key) in (&self.keys).into_iter().enumerate() { - if i != 0 { - out.push_sql(","); - } - out.push_sql("(ROW("); - key.walk_ast(out.reborrow())?; - out.push_sql("))"); - } - out.push_sql(") AS uplete_source (uplete_key) ON ROW("); - self.target.primary_key().walk_ast(out.reborrow())?; - out.push_sql(") = uplete_source.uplete_key WHEN MATCHED AND ("); - self.delete_condition.walk_ast(out.reborrow())?; - out.push_sql(") THEN DELETE WHEN MATCHED THEN UPDATE SET "); - self.update_values.walk_ast(out.reborrow())?; - - Ok(()) - } -} From 3f563240b44e531e551e6b34345d48b50bfcc1e3 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 24 Feb 2024 18:11:34 +0000 Subject: [PATCH 032/241] returning --- crates/db_schema/src/impls/post.rs | 17 +++++++++++++++++ crates/db_schema/src/source/post.rs | 7 ++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index be829af7cb..a3e93d917a 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -264,6 +264,8 @@ impl Post { } } +// TODO: replace `as_select_unwrap` definitions with `Default::default()` after next Diesel release which should +// implement `Default` for `AssumeNotNull` impl PostLike { fn as_select_unwrap() -> ( post_actions::post_id, @@ -312,6 +314,20 @@ impl Likeable for PostLike { } } +impl PostSaved { + fn as_select_unwrap() -> ( + post_actions::post_id, + post_actions::person_id, + dsl::AssumeNotNull, + ) { + ( + post_actions::post_id, + post_actions::person_id, + post_actions::saved.assume_not_null(), + ) + } +} + #[async_trait] impl Saveable for PostSaved { type Form = PostSavedForm; @@ -323,6 +339,7 @@ impl Saveable for PostSaved { .on_conflict((post_actions::post_id, post_actions::person_id)) .do_update() .set(post_saved_form) + .returning(PostSaved::as_select_unwrap()) .get_result::(conn) .await } diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 1ac43e4ea2..8fae08e9f2 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -138,12 +138,9 @@ pub struct PostLikeForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr( - feature = "full", - derive(Identifiable, Queryable, Selectable, Associations) -)] +#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] -#[cfg_attr(feature = "full", diesel(table_name = post_saved))] +#[cfg_attr(feature = "full", diesel(table_name = post_actions))] #[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct PostSaved { From af44c6c2bf3096cbd169b6551bdbc260fa252add Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 24 Feb 2024 18:16:20 +0000 Subject: [PATCH 033/241] rename read_comments field --- crates/db_schema/src/schema.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 8b34c38d7b..01f38c499b 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -668,8 +668,8 @@ diesel::table! { post_actions (person_id, post_id) { person_id -> Int4, post_id -> Int4, - read_comments -> Int8, - read_comments_published -> Nullable, + read_comments -> Nullable, + read_comments_amount -> Int8, read -> Nullable, saved -> Nullable, liked -> Nullable, From 152a53f38cc37ded9d71ec2e523a55955f1d07c4 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 24 Feb 2024 20:23:18 +0000 Subject: [PATCH 034/241] PersonPostAggregates --- .../src/aggregates/person_post_aggregates.rs | 38 ++++++++++++++++--- crates/db_schema/src/aggregates/structs.rs | 13 +++---- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/crates/db_schema/src/aggregates/person_post_aggregates.rs b/crates/db_schema/src/aggregates/person_post_aggregates.rs index 7657dae9e0..dc7157132e 100644 --- a/crates/db_schema/src/aggregates/person_post_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_post_aggregates.rs @@ -1,23 +1,46 @@ use crate::{ aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm}, newtypes::{PersonId, PostId}, - schema::person_post_aggregates::dsl::{person_id, person_post_aggregates, post_id}, - utils::{get_conn, DbPool}, + schema::post_actions, + utils::{get_conn, now, DbPool}, +}; +use diesel::{ + dsl, + insert_into, + result::Error, + ExpressionMethods, + NullableExpressionMethods, + QueryDsl, }; -use diesel::{insert_into, result::Error, QueryDsl}; use diesel_async::RunQueryDsl; impl PersonPostAggregates { + fn as_select_unwrap() -> ( + post_actions::person_id, + post_actions::post_id, + dsl::AssumeNotNull, + dsl::AssumeNotNull, + ) { + ( + post_actions::person_id, + post_actions::post_id, + post_actions::read_comments_amount.assume_not_null(), + post_actions::read_comments.assume_not_null(), + ) + } + pub async fn upsert( pool: &mut DbPool<'_>, form: &PersonPostAggregatesForm, ) -> Result { let conn = &mut get_conn(pool).await?; - insert_into(person_post_aggregates) + let form = (form, post_actions::read_comments.eq(now().nullable())); + insert_into(post_actions::table) .values(form) - .on_conflict((person_id, post_id)) + .on_conflict((post_actions::person_id, post_actions::post_id)) .do_update() .set(form) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } @@ -27,8 +50,11 @@ impl PersonPostAggregates { post_id_: PostId, ) -> Result { let conn = &mut get_conn(pool).await?; - person_post_aggregates + post_actions::table .find((person_id_, post_id_)) + .filter(post_actions::read_comments.is_not_null()) + .filter(post_actions::read_comments_amount.is_not_null()) + .select(Self::as_select_unwrap()) .first::(conn) .await } diff --git a/crates/db_schema/src/aggregates/structs.rs b/crates/db_schema/src/aggregates/structs.rs index 45a43adf8f..45fbcfb500 100644 --- a/crates/db_schema/src/aggregates/structs.rs +++ b/crates/db_schema/src/aggregates/structs.rs @@ -4,7 +4,7 @@ use crate::schema::{ comment_aggregates, community_aggregates, person_aggregates, - person_post_aggregates, + post_actions, post_aggregates, site_aggregates, }; @@ -147,11 +147,8 @@ pub struct PostAggregates { } #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)] -#[cfg_attr( - feature = "full", - derive(Queryable, Selectable, Associations, Identifiable) -)] -#[cfg_attr(feature = "full", diesel(table_name = person_post_aggregates))] +#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))] +#[cfg_attr(feature = "full", diesel(table_name = post_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] @@ -168,12 +165,12 @@ pub struct PersonPostAggregates { #[derive(Clone, Default)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = person_post_aggregates))] +#[cfg_attr(feature = "full", diesel(table_name = post_actions))] pub struct PersonPostAggregatesForm { pub person_id: PersonId, pub post_id: PostId, + #[cfg_attr(feature = "full", diesel(column_name = read_comments_amount))] pub read_comments: i64, - pub published: Option>, } #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)] From 61c75ea70df6ba83e620355415ac69778b3e2191 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 24 Feb 2024 20:37:02 +0000 Subject: [PATCH 035/241] a --- crates/db_schema/src/impls/post.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index a3e93d917a..b9383854f0 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -288,7 +288,7 @@ impl Likeable for PostLike { type IdType = PostId; async fn like(pool: &mut DbPool<'_>, post_like_form: &PostLikeForm) -> Result { let conn = &mut get_conn(pool).await?; - let post_like_form = (post_like_form.clone(), post_actions::liked.eq(now()), "a"); + let post_like_form = (post_like_form.clone(), post_actions::liked.eq(now())); insert_into(post_actions::table) .values(post_like_form) .on_conflict((post_actions::post_id, post_actions::person_id)) From cedce7d46a6c56cbbe884b388aa5cd664dadaa78 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 24 Feb 2024 20:54:20 +0000 Subject: [PATCH 036/241] fix usage of read_comments_amount --- crates/db_schema/src/aggregates/person_post_aggregates.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/db_schema/src/aggregates/person_post_aggregates.rs b/crates/db_schema/src/aggregates/person_post_aggregates.rs index dc7157132e..53dc4ed4e5 100644 --- a/crates/db_schema/src/aggregates/person_post_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_post_aggregates.rs @@ -18,13 +18,13 @@ impl PersonPostAggregates { fn as_select_unwrap() -> ( post_actions::person_id, post_actions::post_id, - dsl::AssumeNotNull, + post_actions::read_comments_amount, dsl::AssumeNotNull, ) { ( post_actions::person_id, post_actions::post_id, - post_actions::read_comments_amount.assume_not_null(), + post_actions::read_comments_amount, post_actions::read_comments.assume_not_null(), ) } @@ -53,7 +53,6 @@ impl PersonPostAggregates { post_actions::table .find((person_id_, post_id_)) .filter(post_actions::read_comments.is_not_null()) - .filter(post_actions::read_comments_amount.is_not_null()) .select(Self::as_select_unwrap()) .first::(conn) .await From fb143b58edeca939d0d0914b50870637bb337b58 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 24 Feb 2024 21:43:09 +0000 Subject: [PATCH 037/241] comment --- crates/db_schema/src/impls/comment.rs | 75 +++++++++++++++++++++----- crates/db_schema/src/impls/post.rs | 4 +- crates/db_schema/src/source/comment.rs | 21 +++----- 3 files changed, 71 insertions(+), 29 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index c8a389475c..fe15e03c7b 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -1,6 +1,9 @@ use crate::{ newtypes::{CommentId, DbUrl, PersonId}, - schema::comment::dsl::{ap_id, comment, content, creator_id, deleted, path, removed, updated}, + schema::{ + comment::dsl::{ap_id, comment, content, creator_id, deleted, path, removed, updated}, + comment_actions, + }, source::comment::{ Comment, CommentInsertForm, @@ -11,10 +14,11 @@ use crate::{ CommentUpdateForm, }, traits::{Crud, Likeable, Saveable}, - utils::{get_conn, naive_now, DbPool, DELETED_REPLACEMENT_TEXT}, + utils::{get_conn, naive_now, now, DbPool, DELETED_REPLACEMENT_TEXT}, }; +use chrono::{DateTime, Utc}; use diesel::{ - dsl::{insert_into, sql_query}, + dsl::{self, insert_into, sql_query}, result::Error, ExpressionMethods, QueryDsl, @@ -178,18 +182,40 @@ impl Crud for Comment { } } +impl CommentLike { + fn as_select_unwrap() -> ( + comment_actions::person_id, + comment_actions::comment_id, + comment_actions::post_id, + dsl::AssumeNotNull, + dsl::AssumeNotNull, + ) { + ( + comment_actions::person_id, + comment_actions::comment_id, + comment_actions::post_id, + comment_actions::like_score.assume_not_null(), + comment_actions::liked.assume_not_null(), + ) + } +} + #[async_trait] impl Likeable for CommentLike { type Form = CommentLikeForm; type IdType = CommentId; async fn like(pool: &mut DbPool<'_>, comment_like_form: &CommentLikeForm) -> Result { - use crate::schema::comment_like::dsl::{comment_id, comment_like, person_id}; let conn = &mut get_conn(pool).await?; - insert_into(comment_like) + let comment_like_form = ( + comment_like_form, + comment_actions::liked.eq(now().nullable()), + ); + insert_into(comment_actions::table) .values(comment_like_form) - .on_conflict((comment_id, person_id)) + .on_conflict((comment_actions::comment_id, comment_actions::person_id)) .do_update() .set(comment_like_form) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } @@ -198,14 +224,31 @@ impl Likeable for CommentLike { person_id: PersonId, comment_id: CommentId, ) -> Result { - use crate::schema::comment_like::dsl::comment_like; let conn = &mut get_conn(pool).await?; - diesel::delete(comment_like.find((person_id, comment_id))) + diesel::update(comment_actions::table.find((person_id, comment_id))) + .set(( + comment_actions::like_score.eq(None::), + comment_actions::liked.eq(None::>), + )) .execute(conn) .await } } +impl CommentSaved { + fn as_select_unwrap() -> ( + comment_actions::comment_id, + comment_actions::person_id, + dsl::AssumeNotNull, + ) { + ( + comment_actions::comment_id, + comment_actions::person_id, + comment_actions::saved.assume_not_null(), + ) + } +} + #[async_trait] impl Saveable for CommentSaved { type Form = CommentSavedForm; @@ -213,13 +256,17 @@ impl Saveable for CommentSaved { pool: &mut DbPool<'_>, comment_saved_form: &CommentSavedForm, ) -> Result { - use crate::schema::comment_saved::dsl::{comment_id, comment_saved, person_id}; let conn = &mut get_conn(pool).await?; - insert_into(comment_saved) + let comment_saved_form = ( + comment_saved_form, + comment_actions::saved.eq(now().nullable()), + ); + insert_into(comment_actions::table) .values(comment_saved_form) - .on_conflict((comment_id, person_id)) + .on_conflict((comment_actions::comment_id, comment_actions::person_id)) .do_update() .set(comment_saved_form) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } @@ -227,11 +274,11 @@ impl Saveable for CommentSaved { pool: &mut DbPool<'_>, comment_saved_form: &CommentSavedForm, ) -> Result { - use crate::schema::comment_saved::dsl::comment_saved; let conn = &mut get_conn(pool).await?; - diesel::delete( - comment_saved.find((comment_saved_form.person_id, comment_saved_form.comment_id)), + diesel::update( + comment_actions::table.find((comment_saved_form.person_id, comment_saved_form.comment_id)), ) + .set(comment_actions::saved.eq(None::>)) .execute(conn) .await } diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index b9383854f0..ec7fb488c6 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -294,7 +294,7 @@ impl Likeable for PostLike { .on_conflict((post_actions::post_id, post_actions::person_id)) .do_update() .set(post_like_form) - .returning(PostLike::as_select_unwrap()) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } @@ -339,7 +339,7 @@ impl Saveable for PostSaved { .on_conflict((post_actions::post_id, post_actions::person_id)) .do_update() .set(post_saved_form) - .returning(PostSaved::as_select_unwrap()) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index 3ebea42c1f..c8872a8181 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -2,7 +2,7 @@ use crate::newtypes::LtreeDef; use crate::newtypes::{CommentId, DbUrl, LanguageId, PersonId, PostId}; #[cfg(feature = "full")] -use crate::schema::{comment, comment_like, comment_saved}; +use crate::schema::{comment, comment_actions}; use chrono::{DateTime, Utc}; #[cfg(feature = "full")] use diesel_ltree::Ltree; @@ -87,12 +87,9 @@ pub struct CommentUpdateForm { } #[derive(PartialEq, Eq, Debug, Clone)] -#[cfg_attr( - feature = "full", - derive(Identifiable, Queryable, Selectable, Associations) -)] +#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))] -#[cfg_attr(feature = "full", diesel(table_name = comment_like))] +#[cfg_attr(feature = "full", diesel(table_name = comment_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, comment_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct CommentLike { @@ -105,21 +102,19 @@ pub struct CommentLike { #[derive(Clone)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = comment_like))] +#[cfg_attr(feature = "full", diesel(table_name = comment_actions))] pub struct CommentLikeForm { pub person_id: PersonId, pub comment_id: CommentId, pub post_id: PostId, // TODO this is redundant + #[cfg_attr(feature = "full", diesel(column_name = like_score))] pub score: i16, } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr( - feature = "full", - derive(Identifiable, Queryable, Selectable, Associations) -)] +#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))] -#[cfg_attr(feature = "full", diesel(table_name = comment_saved))] +#[cfg_attr(feature = "full", diesel(table_name = comment_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, comment_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct CommentSaved { @@ -129,7 +124,7 @@ pub struct CommentSaved { } #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = comment_saved))] +#[cfg_attr(feature = "full", diesel(table_name = comment_actions))] pub struct CommentSavedForm { pub comment_id: CommentId, pub person_id: PersonId, From 042860c2d9398995b05eb72b0cdc9c6b7bf0f6a9 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 25 Feb 2024 02:09:01 +0000 Subject: [PATCH 038/241] community --- crates/db_schema/src/impls/community.rs | 143 +++++++++++++++++------ crates/db_schema/src/source/community.rs | 31 ++--- 2 files changed, 120 insertions(+), 54 deletions(-) diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 3298da8943..f0d8c80706 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -1,6 +1,6 @@ use crate::{ newtypes::{CommunityId, DbUrl, PersonId}, - schema::{community, community_follower, instance}, + schema::{community, community_actions, instance}, source::{ actor_language::CommunityLanguage, community::{ @@ -16,9 +16,10 @@ use crate::{ }, }, traits::{ApubActor, Bannable, Crud, Followable, Joinable}, - utils::{functions::lower, get_conn, DbPool}, + utils::{functions::lower, get_conn, now, DbPool}, SubscribedType, }; +use chrono::{DateTime, Utc}; use diesel::{ deserialize, dsl, @@ -77,6 +78,20 @@ impl Crud for Community { } } +impl CommunityModerator { + fn as_select_unwrap() -> ( + community_actions::community_id, + community_actions::person_id, + dsl::AssumeNotNull, + ) { + ( + community_actions::community_id, + community_actions::person_id, + community_actions::became_moderator.assume_not_null(), + ) + } +} + #[async_trait] impl Joinable for CommunityModerator { type Form = CommunityModeratorForm; @@ -84,10 +99,14 @@ impl Joinable for CommunityModerator { pool: &mut DbPool<'_>, community_moderator_form: &CommunityModeratorForm, ) -> Result { - use crate::schema::community_moderator::dsl::community_moderator; let conn = &mut get_conn(pool).await?; - insert_into(community_moderator) + let community_moderator_form = ( + community_moderator_form, + community_actions::became_moderator.eq(now().nullable()), + ); + insert_into(community_actions::table) .values(community_moderator_form) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } @@ -96,12 +115,12 @@ impl Joinable for CommunityModerator { pool: &mut DbPool<'_>, community_moderator_form: &CommunityModeratorForm, ) -> Result { - use crate::schema::community_moderator::dsl::community_moderator; let conn = &mut get_conn(pool).await?; - diesel::delete(community_moderator.find(( + diesel::update(community_actions::table.find(( community_moderator_form.person_id, community_moderator_form.community_id, ))) + .set(community_actions::became_moderator.eq(None::>)) .execute(conn) .await } @@ -144,21 +163,23 @@ impl CommunityModerator { pool: &mut DbPool<'_>, for_community_id: CommunityId, ) -> Result { - use crate::schema::community_moderator::dsl::{community_id, community_moderator}; let conn = &mut get_conn(pool).await?; - diesel::delete(community_moderator.filter(community_id.eq(for_community_id))) - .execute(conn) - .await + diesel::update( + community_actions::table.filter(community_actions::community_id.eq(for_community_id)), + ) + .set(community_actions::became_moderator.eq(None::>)) + .execute(conn) + .await } pub async fn leave_all_communities( pool: &mut DbPool<'_>, for_person_id: PersonId, ) -> Result { - use crate::schema::community_moderator::dsl::{community_moderator, person_id}; let conn = &mut get_conn(pool).await?; - diesel::delete(community_moderator.filter(person_id.eq(for_person_id))) + diesel::update(community_actions::table.filter(community_actions::person_id.eq(for_person_id))) + .set(community_actions::became_moderator.eq(None::>)) .execute(conn) .await } @@ -167,16 +188,32 @@ impl CommunityModerator { pool: &mut DbPool<'_>, for_person_id: PersonId, ) -> Result, Error> { - use crate::schema::community_moderator::dsl::{community_id, community_moderator, person_id}; let conn = &mut get_conn(pool).await?; - community_moderator - .filter(person_id.eq(for_person_id)) - .select(community_id) + community_actions::table + .filter(community_actions::person_id.eq(for_person_id)) + .filter(community_actions::became_moderator.is_not_null()) + .select(community_actions::community_id) .load::(conn) .await } } +impl CommunityPersonBan { + fn as_select_unwrap() -> ( + community_actions::community_id, + community_actions::person_id, + dsl::AssumeNotNull, + community_actions::ban_expires, + ) { + ( + community_actions::community_id, + community_actions::person_id, + community_actions::received_ban.assume_not_null(), + community_actions::ban_expires, + ) + } +} + #[async_trait] impl Bannable for CommunityPersonBan { type Form = CommunityPersonBanForm; @@ -184,13 +221,20 @@ impl Bannable for CommunityPersonBan { pool: &mut DbPool<'_>, community_person_ban_form: &CommunityPersonBanForm, ) -> Result { - use crate::schema::community_person_ban::dsl::{community_id, community_person_ban, person_id}; let conn = &mut get_conn(pool).await?; - insert_into(community_person_ban) + let community_person_ban_form = ( + community_person_ban_form, + community_actions::received_ban.eq(now().nullable()), + ); + insert_into(community_actions::table) .values(community_person_ban_form) - .on_conflict((community_id, person_id)) + .on_conflict(( + community_actions::community_id, + community_actions::person_id, + )) .do_update() .set(community_person_ban_form) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } @@ -199,18 +243,35 @@ impl Bannable for CommunityPersonBan { pool: &mut DbPool<'_>, community_person_ban_form: &CommunityPersonBanForm, ) -> Result { - use crate::schema::community_person_ban::dsl::community_person_ban; let conn = &mut get_conn(pool).await?; - diesel::delete(community_person_ban.find(( + diesel::update(community_actions::table.find(( community_person_ban_form.person_id, community_person_ban_form.community_id, ))) + .set(( + community_actions::received_ban.eq(None::>), + community_actions::ban_expires.eq(None::>), + )) .execute(conn) .await } } impl CommunityFollower { + fn as_select_unwrap() -> ( + community_actions::community_id, + community_actions::person_id, + dsl::AssumeNotNull, + dsl::AssumeNotNull, + ) { + ( + community_actions::community_id, + community_actions::person_id, + community_actions::followed.assume_not_null(), + community_actions::follow_pending.assume_not_null(), + ) + } + pub fn to_subscribed_type(follower: &Option) -> SubscribedType { match follower { Some(f) => { @@ -225,8 +286,8 @@ impl CommunityFollower { } } - pub fn select_subscribed_type() -> dsl::Nullable { - community_follower::pending.nullable() + pub fn select_subscribed_type() -> dsl::Nullable { + community_actions::follow_pending.nullable() } /// Check if a remote instance has any followers on local instance. For this it is enough to check @@ -235,10 +296,11 @@ impl CommunityFollower { pool: &mut DbPool<'_>, remote_community_id: CommunityId, ) -> Result { - use crate::schema::community_follower::dsl::{community_follower, community_id}; let conn = &mut get_conn(pool).await?; select(exists( - community_follower.filter(community_id.eq(remote_community_id)), + community_actions::table + .filter(community_actions::community_id.eq(remote_community_id)) + .filter(community_actions::followed.is_not_null()), )) .get_result(conn) .await @@ -260,13 +322,17 @@ impl Queryable, Pg> for SubscribedType { impl Followable for CommunityFollower { type Form = CommunityFollowerForm; async fn follow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result { - use crate::schema::community_follower::dsl::{community_follower, community_id, person_id}; let conn = &mut get_conn(pool).await?; - insert_into(community_follower) + let form = (form, community_actions::followed.eq(now().nullable())); + insert_into(community_actions::table) .values(form) - .on_conflict((community_id, person_id)) + .on_conflict(( + community_actions::community_id, + community_actions::person_id, + )) .do_update() .set(form) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } @@ -275,17 +341,24 @@ impl Followable for CommunityFollower { community_id: CommunityId, person_id: PersonId, ) -> Result { - use crate::schema::community_follower::dsl::{community_follower, pending}; let conn = &mut get_conn(pool).await?; - diesel::update(community_follower.find((person_id, community_id))) - .set(pending.eq(false)) - .get_result::(conn) - .await + diesel::update( + community_actions::table + .find((person_id, community_id)) + .filter(community_actions::follow_pending.is_not_null()), + ) + .set(community_actions::follow_pending.eq(Some(false))) + .returning(Self::as_select_unwrap()) + .get_result::(conn) + .await } async fn unfollow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result { - use crate::schema::community_follower::dsl::community_follower; let conn = &mut get_conn(pool).await?; - diesel::delete(community_follower.find((form.person_id, form.community_id))) + diesel::update(community_actions::table.find((form.person_id, form.community_id))) + .set(( + community_actions::followed.eq(None::>), + community_actions::follow_pending.eq(None::), + )) .execute(conn) .await } diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index 434fa70372..b8a97c14c9 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -1,5 +1,5 @@ #[cfg(feature = "full")] -use crate::schema::{community, community_follower, community_moderator, community_person_ban}; +use crate::schema::{community, community_actions}; use crate::{ newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, source::placeholder_apub_url, @@ -133,15 +133,12 @@ pub struct CommunityUpdateForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr( - feature = "full", - derive(Identifiable, Queryable, Selectable, Associations) -)] +#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr( feature = "full", diesel(belongs_to(crate::source::community::Community)) )] -#[cfg_attr(feature = "full", diesel(table_name = community_moderator))] +#[cfg_attr(feature = "full", diesel(table_name = community_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct CommunityModerator { @@ -152,22 +149,19 @@ pub struct CommunityModerator { #[derive(Clone)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = community_moderator))] +#[cfg_attr(feature = "full", diesel(table_name = community_actions))] pub struct CommunityModeratorForm { pub community_id: CommunityId, pub person_id: PersonId, } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr( - feature = "full", - derive(Identifiable, Queryable, Selectable, Associations) -)] +#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr( feature = "full", diesel(belongs_to(crate::source::community::Community)) )] -#[cfg_attr(feature = "full", diesel(table_name = community_person_ban))] +#[cfg_attr(feature = "full", diesel(table_name = community_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct CommunityPersonBan { @@ -179,23 +173,21 @@ pub struct CommunityPersonBan { #[derive(Clone)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = community_person_ban))] +#[cfg_attr(feature = "full", diesel(table_name = community_actions))] pub struct CommunityPersonBanForm { pub community_id: CommunityId, pub person_id: PersonId, + #[cfg_attr(feature = "full", diesel(column_name = ban_expires))] pub expires: Option>>, } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr( - feature = "full", - derive(Identifiable, Queryable, Selectable, Associations) -)] +#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr( feature = "full", diesel(belongs_to(crate::source::community::Community)) )] -#[cfg_attr(feature = "full", diesel(table_name = community_follower))] +#[cfg_attr(feature = "full", diesel(table_name = community_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct CommunityFollower { @@ -207,9 +199,10 @@ pub struct CommunityFollower { #[derive(Clone)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = community_follower))] +#[cfg_attr(feature = "full", diesel(table_name = community_actions))] pub struct CommunityFollowerForm { pub community_id: CommunityId, pub person_id: PersonId, + #[cfg_attr(feature = "full", diesel(column_name = follow_pending))] pub pending: bool, } From c93994b7c92860f46edd688ec886f1e2ee75d36c Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 25 Feb 2024 16:45:38 +0000 Subject: [PATCH 039/241] community_block --- crates/db_schema/src/impls/community_block.rs | 39 +++++++++++++++---- .../db_schema/src/source/community_block.rs | 11 ++---- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index 1393f49d30..fcb7d32cff 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -1,19 +1,34 @@ use crate::{ newtypes::{CommunityId, PersonId}, - schema::community_block::dsl::{community_block, community_id, person_id}, + schema::community_actions, source::community_block::{CommunityBlock, CommunityBlockForm}, traits::Blockable, - utils::{get_conn, DbPool}, + utils::{get_conn, now, DbPool}, }; +use chrono::{DateTime, Utc}; use diesel::{ - dsl::{exists, insert_into}, + dsl::{self, exists, insert_into}, result::Error, select, + ExpressionMethods, + NullableExpressionMethods, QueryDsl, }; use diesel_async::RunQueryDsl; impl CommunityBlock { + fn as_select_unwrap() -> ( + community_actions::person_id, + community_actions::community_id, + dsl::AssumeNotNull, + ) { + ( + community_actions::person_id, + community_actions::community_id, + community_actions::blocked.assume_not_null(), + ) + } + pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, @@ -21,7 +36,9 @@ impl CommunityBlock { ) -> Result { let conn = &mut get_conn(pool).await?; select(exists( - community_block.find((for_person_id, for_community_id)), + community_actions::table + .find((for_person_id, for_community_id)) + .filter(community_actions::blocked.is_not_null()), )) .get_result(conn) .await @@ -33,9 +50,16 @@ impl Blockable for CommunityBlock { type Form = CommunityBlockForm; async fn block(pool: &mut DbPool<'_>, community_block_form: &Self::Form) -> Result { let conn = &mut get_conn(pool).await?; - insert_into(community_block) + let community_block_form = ( + community_block_form, + community_actions::blocked.eq(now().nullable()), + ); + insert_into(community_actions::table) .values(community_block_form) - .on_conflict((person_id, community_id)) + .on_conflict(( + community_actions::person_id, + community_actions::community_id, + )) .do_update() .set(community_block_form) .get_result::(conn) @@ -46,10 +70,11 @@ impl Blockable for CommunityBlock { community_block_form: &Self::Form, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::delete(community_block.find(( + diesel::update(community_actions::table.find(( community_block_form.person_id, community_block_form.community_id, ))) + .set(community_actions::blocked.eq(None::>)) .execute(conn) .await } diff --git a/crates/db_schema/src/source/community_block.rs b/crates/db_schema/src/source/community_block.rs index 7d43af173b..d46ade3e82 100644 --- a/crates/db_schema/src/source/community_block.rs +++ b/crates/db_schema/src/source/community_block.rs @@ -1,19 +1,16 @@ use crate::newtypes::{CommunityId, PersonId}; #[cfg(feature = "full")] -use crate::schema::community_block; +use crate::schema::community_actions; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -#[cfg_attr( - feature = "full", - derive(Queryable, Selectable, Associations, Identifiable) -)] +#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))] #[cfg_attr( feature = "full", diesel(belongs_to(crate::source::community::Community)) )] -#[cfg_attr(feature = "full", diesel(table_name = community_block))] +#[cfg_attr(feature = "full", diesel(table_name = community_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, community_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct CommunityBlock { @@ -23,7 +20,7 @@ pub struct CommunityBlock { } #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = community_block))] +#[cfg_attr(feature = "full", diesel(table_name = community_actions))] pub struct CommunityBlockForm { pub person_id: PersonId, pub community_id: CommunityId, From 5293929644c11666702219df4f9f2e68c8e43291 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 25 Feb 2024 16:54:29 +0000 Subject: [PATCH 040/241] instance_block --- crates/db_schema/src/impls/instance_block.rs | 37 +++++++++++++++---- crates/db_schema/src/source/instance_block.rs | 11 ++---- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index e326884119..f6f92b4bd1 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -1,19 +1,34 @@ use crate::{ newtypes::{InstanceId, PersonId}, - schema::instance_block::dsl::{instance_block, instance_id, person_id}, + schema::instance_actions, source::instance_block::{InstanceBlock, InstanceBlockForm}, traits::Blockable, - utils::{get_conn, DbPool}, + utils::{get_conn, now, DbPool}, }; +use chrono::{DateTime, Utc}; use diesel::{ - dsl::{exists, insert_into}, + dsl::{self, exists, insert_into}, result::Error, select, + ExpressionMethods, + NullableExpressionMethods, QueryDsl, }; use diesel_async::RunQueryDsl; impl InstanceBlock { + fn as_select_unwrap() -> ( + instance_actions::person_id, + instance_actions::instance_id, + dsl::AssumeNotNull, + ) { + ( + instance_actions::person_id, + instance_actions::instance_id, + instance_actions::blocked.assume_not_null(), + ) + } + pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, @@ -21,7 +36,9 @@ impl InstanceBlock { ) -> Result { let conn = &mut get_conn(pool).await?; select(exists( - instance_block.find((for_person_id, for_instance_id)), + instance_actions::table + .find((for_person_id, for_instance_id)) + .filter(instance_actions::blocked.is_not_null()), )) .get_result(conn) .await @@ -33,11 +50,16 @@ impl Blockable for InstanceBlock { type Form = InstanceBlockForm; async fn block(pool: &mut DbPool<'_>, instance_block_form: &Self::Form) -> Result { let conn = &mut get_conn(pool).await?; - insert_into(instance_block) + let instance_block_form = ( + instance_block_form, + instance_actions::blocked.eq(now().nullable()), + ); + insert_into(instance_actions::table) .values(instance_block_form) - .on_conflict((person_id, instance_id)) + .on_conflict((instance_actions::person_id, instance_actions::instance_id)) .do_update() .set(instance_block_form) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } @@ -46,10 +68,11 @@ impl Blockable for InstanceBlock { instance_block_form: &Self::Form, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::delete(instance_block.find(( + diesel::update(instance_actions::table.find(( instance_block_form.person_id, instance_block_form.instance_id, ))) + .set(instance_actions::blocked.eq(None::>)) .execute(conn) .await } diff --git a/crates/db_schema/src/source/instance_block.rs b/crates/db_schema/src/source/instance_block.rs index 4eebbf1a8a..237b21bc9f 100644 --- a/crates/db_schema/src/source/instance_block.rs +++ b/crates/db_schema/src/source/instance_block.rs @@ -1,19 +1,16 @@ use crate::newtypes::{InstanceId, PersonId}; #[cfg(feature = "full")] -use crate::schema::instance_block; +use crate::schema::instance_actions; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -#[cfg_attr( - feature = "full", - derive(Queryable, Selectable, Associations, Identifiable) -)] +#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))] #[cfg_attr( feature = "full", diesel(belongs_to(crate::source::instance::Instance)) )] -#[cfg_attr(feature = "full", diesel(table_name = instance_block))] +#[cfg_attr(feature = "full", diesel(table_name = instance_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, instance_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct InstanceBlock { @@ -23,7 +20,7 @@ pub struct InstanceBlock { } #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = instance_block))] +#[cfg_attr(feature = "full", diesel(table_name = instance_actions))] pub struct InstanceBlockForm { pub person_id: PersonId, pub instance_id: InstanceId, From 798e32650002bf1b3781acfc10af791159386618 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 25 Feb 2024 17:04:26 +0000 Subject: [PATCH 041/241] LocalUser::export_backup --- crates/db_schema/src/impls/local_user.rs | 49 +++++++++++++----------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index 14da24baee..5afc0d0ed5 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -98,55 +98,60 @@ impl LocalUser { ) -> Result { use crate::schema::{ comment, - comment_saved, + comment_actions, community, - community_block, - community_follower, + community_actions, instance, - instance_block, - person_block, + instance_actions, + person_actions, post, - post_saved, + post_actions, }; let conn = &mut get_conn(pool).await?; - let followed_communities = community_follower::dsl::community_follower - .filter(community_follower::person_id.eq(person_id_)) - .inner_join(community::table.on(community_follower::community_id.eq(community::id))) + let followed_communities = community_actions::table + .filter(community_actions::person_id.eq(person_id_)) + .filter(community_actions::followed.is_not_null()) + .inner_join(community::table.on(community_actions::community_id.eq(community::id))) .select(community::actor_id) .get_results(conn) .await?; - let saved_posts = post_saved::dsl::post_saved - .filter(post_saved::person_id.eq(person_id_)) - .inner_join(post::table.on(post_saved::post_id.eq(post::id))) + let saved_posts = post_actions::table + .filter(post_actions::person_id.eq(person_id_)) + .filter(post_actions::saved.is_not_null()) + .inner_join(post::table.on(post_actions::post_id.eq(post::id))) .select(post::ap_id) .get_results(conn) .await?; - let saved_comments = comment_saved::dsl::comment_saved - .filter(comment_saved::person_id.eq(person_id_)) - .inner_join(comment::table.on(comment_saved::comment_id.eq(comment::id))) + let saved_comments = comment_actions::table + .filter(comment_actions::person_id.eq(person_id_)) + .filter(comment_actions::saved.is_not_null()) + .inner_join(comment::table.on(comment_actions::comment_id.eq(comment::id))) .select(comment::ap_id) .get_results(conn) .await?; - let blocked_communities = community_block::dsl::community_block - .filter(community_block::person_id.eq(person_id_)) + let blocked_communities = community_actions::table + .filter(community_actions::person_id.eq(person_id_)) + .filter(community_actions::blocked.is_not_null()) .inner_join(community::table) .select(community::actor_id) .get_results(conn) .await?; - let blocked_users = person_block::dsl::person_block - .filter(person_block::person_id.eq(person_id_)) - .inner_join(person::table.on(person_block::target_id.eq(person::id))) + let blocked_users = person_actions::table + .filter(person_actions::person_id.eq(person_id_)) + .filter(person_actions::blocked.is_not_null()) + .inner_join(person::table.on(person_actions::target_id.eq(person::id))) .select(person::actor_id) .get_results(conn) .await?; - let blocked_instances = instance_block::dsl::instance_block - .filter(instance_block::person_id.eq(person_id_)) + let blocked_instances = instance_actions::table + .filter(instance_actions::person_id.eq(person_id_)) + .filter(instance_actions::blocked.is_not_null()) .inner_join(instance::table) .select(instance::domain) .get_results(conn) From ea69354c7b8f7c444977726cc0baab545e304409 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Mon, 26 Feb 2024 00:11:52 +0000 Subject: [PATCH 042/241] person_block --- crates/db_schema/src/impls/person_block.rs | 48 ++++++++++++++++----- crates/db_schema/src/source/person_block.rs | 11 ++--- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index 0dbf003d84..15c4d721ab 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -1,28 +1,48 @@ use crate::{ newtypes::PersonId, - schema::person_block::dsl::{person_block, person_id, target_id}, + schema::person_actions, source::person_block::{PersonBlock, PersonBlockForm}, traits::Blockable, - utils::{get_conn, DbPool}, + utils::{get_conn, now, DbPool}, }; +use chrono::{DateTime, Utc}; use diesel::{ + dsl, dsl::{exists, insert_into}, result::Error, select, + ExpressionMethods, + NullableExpressionMethods, QueryDsl, }; use diesel_async::RunQueryDsl; impl PersonBlock { + fn as_select_unwrap() -> ( + person_actions::person_id, + person_actions::target_id, + dsl::AssumeNotNull, + ) { + ( + person_actions::person_id, + person_actions::target_id, + person_actions::blocked.assume_not_null(), + ) + } + pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, for_recipient_id: PersonId, ) -> Result { let conn = &mut get_conn(pool).await?; - select(exists(person_block.find((for_person_id, for_recipient_id)))) - .get_result(conn) - .await + select(exists( + person_actions::table + .find((for_person_id, for_recipient_id)) + .filter(person_actions::blocked.is_not_null()), + )) + .get_result(conn) + .await } } @@ -34,18 +54,26 @@ impl Blockable for PersonBlock { person_block_form: &PersonBlockForm, ) -> Result { let conn = &mut get_conn(pool).await?; - insert_into(person_block) + let person_block_form = ( + person_block_form, + person_actions::blocked.eq(now().nullable()), + ); + insert_into(person_actions::table) .values(person_block_form) - .on_conflict((person_id, target_id)) + .on_conflict((person_actions::person_id, person_actions::target_id)) .do_update() .set(person_block_form) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } async fn unblock(pool: &mut DbPool<'_>, person_block_form: &Self::Form) -> Result { let conn = &mut get_conn(pool).await?; - diesel::delete(person_block.find((person_block_form.person_id, person_block_form.target_id))) - .execute(conn) - .await + diesel::update( + person_actions::table.find((person_block_form.person_id, person_block_form.target_id)), + ) + .set(person_actions::blocked.eq(None::>)) + .execute(conn) + .await } } diff --git a/crates/db_schema/src/source/person_block.rs b/crates/db_schema/src/source/person_block.rs index 43048fb393..0d3f8118c3 100644 --- a/crates/db_schema/src/source/person_block.rs +++ b/crates/db_schema/src/source/person_block.rs @@ -1,16 +1,13 @@ use crate::newtypes::PersonId; #[cfg(feature = "full")] -use crate::schema::person_block; +use crate::schema::person_actions; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -#[cfg_attr( - feature = "full", - derive(Queryable, Selectable, Associations, Identifiable) -)] +#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))] -#[cfg_attr(feature = "full", diesel(table_name = person_block))] +#[cfg_attr(feature = "full", diesel(table_name = person_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, target_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct PersonBlock { @@ -20,7 +17,7 @@ pub struct PersonBlock { } #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = person_block))] +#[cfg_attr(feature = "full", diesel(table_name = person_actions))] pub struct PersonBlockForm { pub person_id: PersonId, pub target_id: PersonId, From d3996dd2bf4518e5505e9b4902919c0b3003c265 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Mon, 26 Feb 2024 00:26:56 +0000 Subject: [PATCH 043/241] person --- crates/db_schema/src/impls/person.rs | 50 +++++++++++++++++++++------ crates/db_schema/src/source/person.rs | 14 ++++---- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 9fb1ee1c51..4f19a78844 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -1,6 +1,6 @@ use crate::{ newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, - schema::{instance, local_user, person, person_follower}, + schema::{instance, local_user, person, person_actions}, source::person::{ Person, PersonFollower, @@ -9,9 +9,16 @@ use crate::{ PersonUpdateForm, }, traits::{ApubActor, Crud, Followable}, - utils::{functions::lower, get_conn, naive_now, DbPool}, + utils::{functions::lower, get_conn, naive_now, now, DbPool}, +}; +use chrono::{DateTime, Utc}; +use diesel::{ + dsl::{self, insert_into}, + result::Error, + ExpressionMethods, + JoinOnDsl, + QueryDsl, }; -use diesel::{dsl::insert_into, result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; use diesel_async::RunQueryDsl; #[async_trait] @@ -147,17 +154,34 @@ impl ApubActor for Person { } } +impl PersonFollower { + fn as_select_unwrap() -> ( + person_actions::target_id, + person_actions::person_id, + dsl::AssumeNotNull, + dsl::AssumeNotNull, + ) { + ( + person_actions::target_id, + person_actions::person_id, + person_actions::followed.assume_not_null(), + person_actions::follow_pending.assume_not_null(), + ) + } +} + #[async_trait] impl Followable for PersonFollower { type Form = PersonFollowerForm; async fn follow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result { - use crate::schema::person_follower::dsl::{follower_id, person_follower, person_id}; let conn = &mut get_conn(pool).await?; - insert_into(person_follower) + let form = (form, person_actions::followed.eq(now().nullable())); + insert_into(person_actions::table) .values(form) - .on_conflict((follower_id, person_id)) + .on_conflict((person_actions::person_id, person_actions::target_id)) .do_update() .set(form) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } @@ -165,9 +189,12 @@ impl Followable for PersonFollower { unimplemented!() } async fn unfollow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result { - use crate::schema::person_follower::dsl::person_follower; let conn = &mut get_conn(pool).await?; - diesel::delete(person_follower.find((form.follower_id, form.person_id))) + diesel::update(person_actions::table.find((form.follower_id, form.person_id))) + .set(( + person_actions::followed.eq(None::>), + person_actions::follow_pending.eq(None::), + )) .execute(conn) .await } @@ -179,9 +206,10 @@ impl PersonFollower { for_person_id: PersonId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - person_follower::table - .inner_join(person::table.on(person_follower::follower_id.eq(person::id))) - .filter(person_follower::person_id.eq(for_person_id)) + person_actions::table + .inner_join(person::table.on(person_actions::person_id.eq(person::id))) + .filter(person_actions::target_id.eq(for_person_id)) + .filter(person_actions::followed.is_not_null()) .select(person::all_columns) .load(conn) .await diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index 25e65ae79a..86f93bdd41 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -1,5 +1,5 @@ #[cfg(feature = "full")] -use crate::schema::{person, person_follower}; +use crate::schema::{person, person_actions}; use crate::{ newtypes::{DbUrl, InstanceId, PersonId}, source::placeholder_apub_url, @@ -113,12 +113,9 @@ pub struct PersonUpdateForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr( - feature = "full", - derive(Identifiable, Queryable, Selectable, Associations) -)] +#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))] -#[cfg_attr(feature = "full", diesel(table_name = person_follower))] +#[cfg_attr(feature = "full", diesel(table_name = person_actions))] #[cfg_attr(feature = "full", diesel(primary_key(follower_id, person_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct PersonFollower { @@ -130,9 +127,12 @@ pub struct PersonFollower { #[derive(Clone)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = person_follower))] +#[cfg_attr(feature = "full", diesel(table_name = person_actions))] pub struct PersonFollowerForm { + #[cfg_attr(feature = "full", diesel(column_name = target_id))] pub person_id: PersonId, + #[cfg_attr(feature = "full", diesel(column_name = person_id))] pub follower_id: PersonId, + #[cfg_attr(feature = "full", diesel(column_name = follow_pending))] pub pending: bool, } From 074a11272a562720a2a779337645dcf3d20d9404 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Thu, 7 Mar 2024 16:07:39 +0000 Subject: [PATCH 044/241] stuff (partial) --- crates/db_schema/src/impls/comment.rs | 2 +- crates/db_schema/src/impls/community_block.rs | 1 + crates/db_schema/src/impls/person.rs | 2 +- crates/db_schema/src/impls/post.rs | 8 ++++---- crates/db_schema/src/lib.rs | 3 +-- crates/db_schema/src/source/post.rs | 2 +- crates/db_schema/src/utils.rs | 5 +++-- 7 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index fe15e03c7b..04c86b112e 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -21,7 +21,7 @@ use diesel::{ dsl::{self, insert_into, sql_query}, result::Error, ExpressionMethods, - QueryDsl, + QueryDsl,NullableExpressionMethods }; use diesel_async::RunQueryDsl; use diesel_ltree::Ltree; diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index fcb7d32cff..0d015dc56c 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -62,6 +62,7 @@ impl Blockable for CommunityBlock { )) .do_update() .set(community_block_form) + .returning(Self::as_select_unwrap()) .get_result::(conn) .await } diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 4f19a78844..dde176ef8e 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -17,7 +17,7 @@ use diesel::{ result::Error, ExpressionMethods, JoinOnDsl, - QueryDsl, + QueryDsl,NullableExpressionMethods }; use diesel_async::RunQueryDsl; diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index ec7fb488c6..0fabaf8725 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -288,12 +288,12 @@ impl Likeable for PostLike { type IdType = PostId; async fn like(pool: &mut DbPool<'_>, post_like_form: &PostLikeForm) -> Result { let conn = &mut get_conn(pool).await?; - let post_like_form = (post_like_form.clone(), post_actions::liked.eq(now())); + let post_like_form = (post_like_form.clone(), post_actions::liked.eq(now().nullable())); insert_into(post_actions::table) - .values(post_like_form) + .values(&post_like_form) .on_conflict((post_actions::post_id, post_actions::person_id)) .do_update() - .set(post_like_form) + .set(&post_like_form) .returning(Self::as_select_unwrap()) .get_result::(conn) .await @@ -333,7 +333,7 @@ impl Saveable for PostSaved { type Form = PostSavedForm; async fn save(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result { let conn = &mut get_conn(pool).await?; - let post_saved_form = (post_saved_form.clone(), post_actions::saved.eq(now())); + let post_saved_form = (post_saved_form.clone(), post_actions::saved.eq(now().nullable())); insert_into(post_actions::table) .values(post_saved_form) .on_conflict((post_actions::post_id, post_actions::person_id)) diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 05663ff3e4..393ff89716 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -30,11 +30,10 @@ pub mod newtypes; pub mod schema; #[cfg(feature = "full")] pub mod aliases { - use crate::schema::{community_moderator, person}; + use crate::schema::{person}; diesel::alias!( person as person1: Person1, person as person2: Person2, - community_moderator as community_moderator1: CommunityModerator1 ); } pub mod source; diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 8fae08e9f2..347b2ea53a 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -150,7 +150,7 @@ pub struct PostSaved { } #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = post_saved))] +#[cfg_attr(feature = "full", diesel(table_name = post_actions))] pub struct PostSavedForm { pub post_id: PostId, pub person_id: PersonId, diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 878b0e5619..e973247a29 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -1,3 +1,4 @@ +use diesel::JoinOnDsl; use crate::{ diesel::Connection, diesel_migrations::MigrationHarness, @@ -8,7 +9,7 @@ use crate::{ use anyhow::Context; use chrono::{DateTime, Utc}; use deadpool::Runtime; -use diesel::{ +use diesel::{NullableExpressionMethods,ExpressionMethods,BoolExpressionMethods, dsl, expression::AsExpression, helper_types::AsExprOf, @@ -20,7 +21,7 @@ use diesel::{ ConnectionResult, Error::{self as DieselError, QueryBuilderError}, }, - sql_types::{self, SqlType, Text, Timestamptz}, + sql_types::{self, SqlType, Timestamptz}, Expression, IntoSql, PgConnection, From fdb9f79820a433c7f655d95a977f12d5b46b9bf2 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Thu, 7 Mar 2024 21:05:49 +0000 Subject: [PATCH 045/241] update cargo.lock --- Cargo.lock | 2 -- 1 file changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4c55f0829..b7990d250d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2577,7 +2577,6 @@ version = "0.19.3" dependencies = [ "activitypub_federation", "actix-web", - "anyhow", "chrono", "encoding", "enum-map", @@ -2599,7 +2598,6 @@ dependencies = [ "serde", "serde_with", "serial_test", - "task-local-extensions", "tokio", "tracing", "ts-rs", From 0d0fa01ae4edc575d9d271e7bd486260a23ec623 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Thu, 7 Mar 2024 21:28:29 +0000 Subject: [PATCH 046/241] fix lemmy_db_schema --- crates/db_schema/src/impls/comment.rs | 3 ++- crates/db_schema/src/impls/person.rs | 4 +++- crates/db_schema/src/impls/post.rs | 8 ++++---- crates/db_schema/src/lib.rs | 2 +- crates/db_schema/src/source/post.rs | 15 ++++++--------- crates/db_schema/src/utils.rs | 7 +++++-- 6 files changed, 21 insertions(+), 18 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 28dc5a5c17..1f5b662552 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -18,7 +18,8 @@ use diesel::{ dsl::{self, insert_into, sql_query}, result::Error, ExpressionMethods, - QueryDsl,NullableExpressionMethods + NullableExpressionMethods, + QueryDsl, }; use diesel_async::RunQueryDsl; use diesel_ltree::Ltree; diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index f59c86f89b..f4fd94a3a9 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -15,9 +15,11 @@ use chrono::{DateTime, Utc}; use diesel::{ dsl::{self, insert_into}, result::Error, + CombineDsl, ExpressionMethods, JoinOnDsl, - QueryDsl,NullableExpressionMethods + NullableExpressionMethods, + QueryDsl, }; use diesel_async::RunQueryDsl; diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 9cfb8cc9a6..7f7d02dfab 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -272,12 +272,12 @@ impl Likeable for PostLike { type IdType = PostId; async fn like(pool: &mut DbPool<'_>, post_like_form: &PostLikeForm) -> Result { let conn = &mut get_conn(pool).await?; - let post_like_form = (post_like_form.clone(), post_actions::liked.eq(now().nullable())); + let post_like_form = (post_like_form, post_actions::liked.eq(now().nullable())); insert_into(post_actions::table) - .values(&post_like_form) + .values(post_like_form) .on_conflict((post_actions::post_id, post_actions::person_id)) .do_update() - .set(&post_like_form) + .set(post_like_form) .returning(Self::as_select_unwrap()) .get_result::(conn) .await @@ -317,7 +317,7 @@ impl Saveable for PostSaved { type Form = PostSavedForm; async fn save(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result { let conn = &mut get_conn(pool).await?; - let post_saved_form = (post_saved_form.clone(), post_actions::saved.eq(now().nullable())); + let post_saved_form = (post_saved_form, post_actions::saved.eq(now().nullable())); insert_into(post_actions::table) .values(post_saved_form) .on_conflict((post_actions::post_id, post_actions::person_id)) diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 393ff89716..c47b0ae2ad 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -30,7 +30,7 @@ pub mod newtypes; pub mod schema; #[cfg(feature = "full")] pub mod aliases { - use crate::schema::{person}; + use crate::schema::person; diesel::alias!( person as person1: Person1, person as person2: Person2, diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index e0bc06ad33..1e980ef348 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -145,7 +145,7 @@ pub struct PostLikeForm { #[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))] -#[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))] +#[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct PostSaved { pub post_id: PostId, @@ -164,7 +164,7 @@ pub struct PostSavedForm { #[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))] -#[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))] +#[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct PostRead { pub post_id: PostId, @@ -180,13 +180,10 @@ pub(crate) struct PostReadForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr( - feature = "full", - derive(Identifiable, Queryable, Selectable, Associations) -)] +#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] -#[cfg_attr(feature = "full", diesel(table_name = post_hide))] -#[cfg_attr(feature = "full", diesel(primary_key(post_id, person_id)))] +#[cfg_attr(feature = "full", diesel(table_name = post_actions))] +#[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct PostHide { pub post_id: PostId, @@ -195,7 +192,7 @@ pub struct PostHide { } #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] -#[cfg_attr(feature = "full", diesel(table_name = post_hide))] +#[cfg_attr(feature = "full", diesel(table_name = post_actions))] pub(crate) struct PostHideForm { pub post_id: PostId, pub person_id: PersonId, diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 66349ea902..aa0874ab8f 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -1,4 +1,3 @@ -use diesel::JoinOnDsl; use crate::{ diesel::Connection, diesel_migrations::MigrationHarness, @@ -9,7 +8,7 @@ use crate::{ use anyhow::Context; use chrono::{DateTime, Utc}; use deadpool::Runtime; -use diesel::{NullableExpressionMethods,ExpressionMethods,BoolExpressionMethods, +use diesel::{ dsl, expression::AsExpression, helper_types::AsExprOf, @@ -22,8 +21,12 @@ use diesel::{NullableExpressionMethods,ExpressionMethods,BoolExpressionMethods, Error::{self as DieselError, QueryBuilderError}, }, sql_types::{self, SqlType, Timestamptz}, + BoolExpressionMethods, Expression, + ExpressionMethods, IntoSql, + JoinOnDsl, + NullableExpressionMethods, PgConnection, Table, }; From db403ecc4b0014aa721c58e643f4918df6d5fb1d Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 8 Mar 2024 03:41:38 +0000 Subject: [PATCH 047/241] post_view --- crates/db_schema/src/lib.rs | 3 +- crates/db_schema/src/utils.rs | 31 +++++++++ crates/db_views/src/post_view.rs | 108 +++++++++++++------------------ 3 files changed, 78 insertions(+), 64 deletions(-) diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index c47b0ae2ad..8ed71e3c23 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -30,8 +30,9 @@ pub mod newtypes; pub mod schema; #[cfg(feature = "full")] pub mod aliases { - use crate::schema::person; + use crate::schema::{community_actions, person}; diesel::alias!( + community_actions as creator_community_actions: CreatorCommunityActions, person as person1: Person1, person as person2: Person2, ); diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index aa0874ab8f..c9dd16d75b 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -15,6 +15,7 @@ use diesel::{ pg::Pg, query_builder::{Query, QueryFragment}, query_dsl::methods::LimitDsl, + query_source::AliasSource, result::{ ConnectionError, ConnectionResult, @@ -513,6 +514,36 @@ where ) } +/*pub fn actions_alias( + actions_table: Alias, + person_id: Option, + target_id: C, +) -> dsl::On, dsl::And>, Option>, dsl::Eq, C>>> +where + AS: AliasSource, + Alias: QuerySource + Clone + Copy, + AS::Target: Table, + K0: Default + Column, + AliasedField: Expression + Sized, + dsl::Nullable>: diesel::ExpressionMethods, + > as Expression>::SqlType: SqlType, + Option: AsExpression<> as Expression>::SqlType>, + K1: Default + Column
, + AliasedField: diesel::ExpressionMethods, + as Expression>::SqlType: SqlType, + C: AsExpression< as Expression>::SqlType>, + dsl::Eq>, Option>: + Expression>, + dsl::Eq, C>: Expression, +{ + actions_table.on( + actions_table.field(K0::default()) + .nullable() + .eq(person_id) + .and(actions_table.field(K1::default()).eq(target_id)), + ) +}*/ + pub type ResultFuture<'a, T> = BoxFuture<'a, Result>; pub trait ReadFn<'a, T, Args>: Fn(DbConn<'a>, Args) -> ResultFuture<'a, T> {} diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 3efc47635c..16306da7a7 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -1,16 +1,12 @@ use crate::structs::{LocalUserView, PaginationCursor, PostView}; -use chrono::{DateTime, Utc}; use diesel::{ debug_query, - dsl::{exists, not, IntervalDsl}, + dsl::{exists, IntervalDsl}, pg::Pg, query_builder::AsQuery, result::Error, - sql_types, BoolExpressionMethods, - BoxableExpression, ExpressionMethods, - IntoSql, JoinOnDsl, NullableExpressionMethods, OptionalExtension, @@ -25,25 +21,19 @@ use lemmy_db_schema::{ newtypes::{CommunityId, LocalUserId, PersonId, PostId}, schema::{ community, - community_block, - community_follower, - community_moderator, - community_person_ban, - instance_block, + community_actions, + instance_actions, local_user, local_user_language, person, - person_block, - person_post_aggregates, + person_actions, post, + post_actions, post_aggregates, - post_hide, - post_like, - post_read, - post_saved, }, source::site::Site, utils::{ + actions, functions::coalesce, fuzzy_search, get_conn, @@ -70,8 +60,7 @@ fn queries<'a>() -> Queries< let all_joins = move |query: post_aggregates::BoxedQuery<'a, Pg>, my_person_id: Option| { query - .inner_join(person::table) - .left_join(local_user::table) + .inner_join(person::table.left_join(local_user::table)) .inner_join(community::table) .inner_join(post::table) .left_join(actions( @@ -94,30 +83,39 @@ fn queries<'a>() -> Queries< my_person_id, post_aggregates::instance_id, )) - .left_join(actions( - creator_community_actions, - post_aggregates::creator_id.nullable(), - post_aggregates::community_id, - )) + .left_join( + creator_community_actions.on( + creator_community_actions + .field(community_actions::person_id) + .eq(post_aggregates::creator_id) + .and( + creator_community_actions + .field(community_actions::community_id) + .eq(post_aggregates::community_id), + ), + ), + ) .select(( post::all_columns, person::all_columns, community::all_columns, creator_community_actions .field(community_actions::received_ban) + .nullable() .is_not_null(), creator_community_actions .field(community_actions::became_moderator) + .nullable() .is_not_null(), - creator_is_admin, + coalesce(local_user::admin.nullable(), false), post_aggregates::all_columns, community_actions::follow_pending.nullable(), - post_actions::saved.is_not_null(), - post_actions::read.is_not_null(), - post_actions::hidden.is_not_null(), - person_actions::blocked.is_not_null(), + post_actions::saved.nullable().is_not_null(), + post_actions::read.nullable().is_not_null(), + post_actions::hidden.nullable().is_not_null(), + person_actions::blocked.nullable().is_not_null(), post_actions::like_score.nullable(), - post_aggregates::comments - coalesce(post_actions::read_comments.nullable(), 0), + post_aggregates::comments - coalesce(post_actions::read_comments_amount.nullable(), 0), )) }; @@ -176,7 +174,6 @@ fn queries<'a>() -> Queries< let my_local_user_id = options.local_user.map(|l| l.local_user.id); // The left join below will return None in this case - let person_id_join = my_person_id.unwrap_or(PersonId(-1)); let local_user_id_join = my_local_user_id.unwrap_or(LocalUserId(-1)); let mut query = all_joins(post_aggregates::table.into_boxed(), my_person_id); @@ -210,30 +207,17 @@ fn queries<'a>() -> Queries< } if let Some(listing_type) = options.listing_type { - if let Some(person_id) = my_person_id { - let is_subscribed = community_actions::followed.is_not_null(); - 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(community_actions::became_moderator.is_not_null()); - } + let is_subscribed = community_actions::followed.is_not_null(); + 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)); } - } - // 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)), + ListingType::All => query = query.filter(community::hidden.eq(false).or(is_subscribed)), + ListingType::ModeratorView => { + query = query.filter(community_actions::became_moderator.is_not_null()); } } } else { @@ -488,13 +472,10 @@ impl<'a> PostQuery<'a> { // // 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, - }, + use lemmy_db_schema::schema::community_aggregates::dsl::{ + community_aggregates, + community_id, + users_active_month, }; let (limit, offset) = limit_and_offset(self.page, self.limit)?; if offset != 0 && self.page_after.is_some() { @@ -509,9 +490,10 @@ impl<'a> PostQuery<'a> { .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))) + community_actions::table + .filter(community_actions::person_id.eq(self_person_id)) + .filter(community_actions::followed.is_not_null()) + .inner_join(community_aggregates.on(community_id.eq(community_actions::community_id))) .order_by(users_active_month.desc()) .select(community_id) .limit(1) From f84e731582ac1b8f0feddc4f36433b3318ecd23b Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 8 Mar 2024 19:52:23 +0000 Subject: [PATCH 048/241] comment_report_view --- crates/db_views/src/comment_report_view.rs | 73 ++++++++++++++-------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index d0851acfdb..40e0a17868 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -11,20 +11,29 @@ use diesel::{ }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ - aliases, + aliases::{self, creator_community_actions}, newtypes::{CommentId, CommentReportId, CommunityId, PersonId}, schema::{ comment, + comment_actions, comment_aggregates, - comment_like, comment_report, community, - community_moderator, - community_person_ban, + community_actions, person, post, }, - utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, + utils::{ + actions, + functions::coalesce, + get_conn, + limit_and_offset, + DbConn, + DbPool, + ListFn, + Queries, + ReadFn, + }, }; fn queries<'a>() -> Queries< @@ -50,11 +59,18 @@ fn queries<'a>() -> Queries< aliases::person2 .on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), ) - .left_join(actions( - creator_community_actions, - comment::creator_id.nullable(), - post_aggregates::community_id, - )) + .left_join( + creator_community_actions.on( + creator_community_actions + .field(community_actions::person_id) + .eq(comment::creator_id) + .and( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id), + ), + ), + ) .select(( comment_report::all_columns, comment::all_columns, @@ -63,15 +79,20 @@ fn queries<'a>() -> Queries< person::all_columns, aliases::person1.fields(person::all_columns), comment_aggregates::all_columns, - creator_community_actions - .field(community_actions::received_ban) - .is_null() - .or( - creator_community_actions - .field(community_actions::ban_expires) - .gt(now), - ), - post_actions::like_score.nullable(), + coalesce( + creator_community_actions + .field(community_actions::received_ban) + .nullable() + .is_null() + .or( + creator_community_actions + .field(community_actions::ban_expires) + .nullable() + .gt(now), + ), + false, + ), + comment_actions::like_score.nullable(), aliases::person2.fields(person::all_columns).nullable(), )) }; @@ -114,10 +135,11 @@ fn queries<'a>() -> Queries< if !user.local_user.admin { query .inner_join( - community_moderator::table.on( - community_moderator::community_id + community_actions::table.on( + community_actions::community_id .eq(post::community_id) - .and(community_moderator::person_id.eq(user.person.id)), + .and(community_actions::person_id.eq(user.person.id)) + .and(community_actions::became_moderator.is_not_null()), ), ) .load::(&mut conn) @@ -167,10 +189,11 @@ impl CommentReportView { if !admin { query .inner_join( - community_moderator::table.on( - community_moderator::community_id + community_actions::table.on( + community_actions::community_id .eq(post::community_id) - .and(community_moderator::person_id.eq(my_person_id)), + .and(community_actions::person_id.eq(my_person_id)) + .and(community_actions::became_moderator.is_not_null()), ), ) .select(count(comment_report::id)) From c46ae9ae872513b163d2a876b206311ba1a37254 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 8 Mar 2024 20:16:33 +0000 Subject: [PATCH 049/241] comment_view --- crates/db_views/src/comment_view.rs | 227 +++++++++------------------- 1 file changed, 73 insertions(+), 154 deletions(-) diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index a72d0b210c..84c318bffd 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -1,14 +1,10 @@ use crate::structs::{CommentView, LocalUserView}; -use chrono::{DateTime, Utc}; use diesel::{ - dsl::{exists, not}, + dsl::exists, pg::Pg, result::Error, - sql_types, BoolExpressionMethods, - BoxableExpression, ExpressionMethods, - IntoSql, JoinOnDsl, NullableExpressionMethods, PgTextExpressionMethods, @@ -17,25 +13,32 @@ use diesel::{ use diesel_async::RunQueryDsl; use diesel_ltree::{nlevel, subpath, Ltree, LtreeExtensions}; use lemmy_db_schema::{ + aliases::creator_community_actions, newtypes::{CommentId, CommunityId, LocalUserId, PersonId, PostId}, schema::{ comment, + comment_actions, comment_aggregates, - comment_like, - comment_saved, community, - community_block, - community_follower, - community_moderator, - community_person_ban, - instance_block, + community_actions, + instance_actions, local_user, local_user_language, person, - person_block, + person_actions, post, }, - utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, + utils::{ + actions, + functions::coalesce, + fuzzy_search, + limit_and_offset, + DbConn, + DbPool, + ListFn, + Queries, + ReadFn, + }, CommentSortType, CommunityVisibility, ListingType, @@ -45,123 +48,63 @@ fn queries<'a>() -> Queries< impl ReadFn<'a, CommentView, (CommentId, Option)>, impl ListFn<'a, CommentView, CommentQuery<'a>>, > { - let is_creator_banned_from_community = exists( - community_person_ban::table.filter( - community::id - .eq(community_person_ban::community_id) - .and(community_person_ban::person_id.eq(comment::creator_id)), - ), - ); - - let is_saved = |person_id| { - comment_saved::table - .filter( - comment::id - .eq(comment_saved::comment_id) - .and(comment_saved::person_id.eq(person_id)), - ) - .select(comment_saved::published.nullable()) - .single_value() - }; - - let is_community_followed = |person_id| { - community_follower::table - .filter( - post::community_id - .eq(community_follower::community_id) - .and(community_follower::person_id.eq(person_id)), - ) - .select(community_follower::pending.nullable()) - .single_value() - }; - - let is_creator_blocked = |person_id| { - exists( - person_block::table.filter( - comment::creator_id - .eq(person_block::target_id) - .and(person_block::person_id.eq(person_id)), - ), - ) - }; - - let score = |person_id| { - comment_like::table - .filter( - comment::id - .eq(comment_like::comment_id) - .and(comment_like::person_id.eq(person_id)), - ) - .select(comment_like::score.nullable()) - .single_value() - }; - - let creator_is_moderator = exists( - community_moderator::table.filter( - community::id - .eq(community_moderator::community_id) - .and(community_moderator::person_id.eq(comment::creator_id)), - ), - ); - - let creator_is_admin = exists( - local_user::table.filter( - comment::creator_id - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ); - let all_joins = move |query: comment::BoxedQuery<'a, Pg>, my_person_id: Option| { - let score_selection: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = if let Some(person_id) = my_person_id { - Box::new(score(person_id)) - } else { - Box::new(None::.into_sql::>()) - }; - - let subscribed_type_selection: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = if let Some(person_id) = my_person_id { - Box::new(is_community_followed(person_id)) - } else { - Box::new(None::.into_sql::>()) - }; - - let is_saved_selection: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = if let Some(person_id) = my_person_id { - Box::new(is_saved(person_id)) - } else { - Box::new(None::>.into_sql::>()) - }; - - let is_creator_blocked_selection: Box> = - if let Some(person_id) = my_person_id { - Box::new(is_creator_blocked(person_id)) - } else { - Box::new(false.into_sql::()) - }; - query - .inner_join(person::table) + .inner_join(person::table.left_join(local_user::table)) .inner_join(post::table) .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(comment_aggregates::table) + .left_join(actions( + community_actions::table, + my_person_id, + post::community_id, + )) + .left_join(actions( + comment_actions::table, + my_person_id, + comment_aggregates::comment_id, + )) + .left_join(actions( + person_actions::table, + my_person_id, + comment::creator_id, + )) + .left_join(actions( + instance_actions::table, + my_person_id, + community::instance_id, + )) + .left_join( + creator_community_actions.on( + creator_community_actions + .field(community_actions::person_id) + .eq(comment::creator_id) + .and( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id), + ), + ), + ) .select(( comment::all_columns, person::all_columns, post::all_columns, community::all_columns, comment_aggregates::all_columns, - is_creator_banned_from_community, - creator_is_moderator, - creator_is_admin, - subscribed_type_selection, - is_saved_selection.is_not_null(), - is_creator_blocked_selection, - score_selection, + creator_community_actions + .field(community_actions::received_ban) + .nullable() + .is_not_null(), + creator_community_actions + .field(community_actions::became_moderator) + .nullable() + .is_not_null(), + coalesce(local_user::admin.nullable(), false), + community_actions::follow_pending.nullable(), + comment_actions::saved.nullable().is_not_null(), + person_actions::blocked.nullable().is_not_null(), + comment_actions::like_score.nullable(), )) }; @@ -180,7 +123,6 @@ fn queries<'a>() -> Queries< let my_local_user_id = options.local_user.map(|l| l.local_user.id); // The left join below will return None in this case - let person_id_join = my_person_id.unwrap_or(PersonId(-1)); let local_user_id_join = my_local_user_id.unwrap_or(LocalUserId(-1)); let mut query = all_joins(comment::table.into_boxed(), my_person_id); @@ -206,13 +148,7 @@ fn queries<'a>() -> Queries< } if let Some(listing_type) = options.listing_type { - let is_subscribed = exists( - community_follower::table.filter( - post::community_id - .eq(community_follower::community_id) - .and(community_follower::person_id.eq(person_id_join)), - ), - ); + let is_subscribed = community_actions::followed.is_not_null(); match listing_type { ListingType::Subscribed => query = query.filter(is_subscribed), // TODO could be this: and(community_follower::person_id.eq(person_id_join)), @@ -223,13 +159,7 @@ fn queries<'a>() -> Queries< } 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_join)), - ), - )); + query = query.filter(community_actions::became_moderator.is_not_null()); } } } @@ -237,14 +167,14 @@ fn queries<'a>() -> Queries< // If its saved only, then filter, and order by the saved time, not the comment creation time. if options.saved_only { query = query - .filter(is_saved(person_id_join).is_not_null()) - .then_order_by(is_saved(person_id_join).desc()); + .filter(comment_actions::saved.is_not_null()) + .then_order_by(comment_actions::saved.desc()); } if options.liked_only { - query = query.filter(score(person_id_join).eq(1)); + query = query.filter(comment_actions::like_score.eq(1)); } else if options.disliked_only { - query = query.filter(score(person_id_join).eq(-1)); + query = query.filter(comment_actions::like_score.eq(-1)); } if !options @@ -268,21 +198,10 @@ fn queries<'a>() -> Queries< )); // Don't show blocked communities or persons - query = query.filter(not(exists( - instance_block::table.filter( - community::instance_id - .eq(instance_block::instance_id) - .and(instance_block::person_id.eq(person_id_join)), - ), - ))); - query = query.filter(not(exists( - community_block::table.filter( - community::id - .eq(community_block::community_id) - .and(community_block::person_id.eq(person_id_join)), - ), - ))); - query = query.filter(not(is_creator_blocked(person_id_join))); + query = query + .filter(instance_actions::blocked.is_null()) + .filter(community_actions::blocked.is_null()) + .filter(person_actions::blocked.is_null()); }; // Hide comments in local only communities from unauthenticated users From 27bb4b1ee84867a08f0ac8fa96525a922ea06b3d Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 8 Mar 2024 20:28:34 +0000 Subject: [PATCH 050/241] post_report_view --- crates/db_views/src/post_report_view.rs | 57 +++++++++++++------------ 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/post_report_view.rs index ddd12d2262..8bbe6015f8 100644 --- a/crates/db_views/src/post_report_view.rs +++ b/crates/db_views/src/post_report_view.rs @@ -10,19 +10,18 @@ use diesel::{ }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ - aliases, + aliases::{self, creator_community_actions}, newtypes::{CommunityId, PersonId, PostId, PostReportId}, schema::{ community, - community_moderator, - community_person_ban, + community_actions, person, post, + post_actions, post_aggregates, - post_like, post_report, }, - utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, + utils::{actions, get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, }; fn queries<'a>() -> Queries< @@ -35,33 +34,35 @@ fn queries<'a>() -> Queries< .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(person::table.on(post_report::creator_id.eq(person::id))) .inner_join(aliases::person1.on(post::creator_id.eq(aliases::person1.field(person::id)))) - .left_join( - community_person_ban::table.on( - post::community_id - .eq(community_person_ban::community_id) - .and(community_person_ban::person_id.eq(post::creator_id)), - ), - ) - .left_join( - post_like::table.on( - post::id - .eq(post_like::post_id) - .and(post_like::person_id.eq(my_person_id)), - ), - ) .inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id))) .left_join( aliases::person2 .on(post_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), ) + .left_join(actions(post_actions::table, Some(my_person_id), post::id)) + .left_join( + creator_community_actions.on( + creator_community_actions + .field(community_actions::person_id) + .eq(post::creator_id) + .and( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id), + ), + ), + ) .select(( post_report::all_columns, post::all_columns, community::all_columns, person::all_columns, aliases::person1.fields(person::all_columns), - community_person_ban::community_id.nullable().is_not_null(), - post_like::score.nullable(), + creator_community_actions + .field(community_actions::received_ban) + .nullable() + .is_not_null(), + post_actions::like_score.nullable(), post_aggregates::all_columns, aliases::person2.fields(person::all_columns.nullable()), )) @@ -104,10 +105,11 @@ fn queries<'a>() -> Queries< if !user.local_user.admin { query .inner_join( - community_moderator::table.on( - community_moderator::community_id + community_actions::table.on( + community_actions::community_id .eq(post::community_id) - .and(community_moderator::person_id.eq(user.person.id)), + .and(community_actions::person_id.eq(user.person.id)) + .and(community_actions::became_moderator.is_not_null()), ), ) .load::(&mut conn) @@ -154,10 +156,11 @@ impl PostReportView { if !admin { query .inner_join( - community_moderator::table.on( - community_moderator::community_id + community_actions::table.on( + community_actions::community_id .eq(post::community_id) - .and(community_moderator::person_id.eq(my_person_id)), + .and(community_actions::person_id.eq(my_person_id)) + .and(community_actions::became_moderator.is_not_null()), ), ) .select(count(post_report::id)) From 2233feacf5aee5cac70a540f67b8382e0171a07e Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 8 Mar 2024 20:35:07 +0000 Subject: [PATCH 051/241] find and replace some selected values --- crates/db_views_actor/src/comment_reply_view.rs | 11 +++++++---- crates/db_views_actor/src/person_mention_view.rs | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index 6d122291a5..805e58eeaf 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -153,11 +153,14 @@ fn queries<'a>() -> Queries< aliases::person1.fields(person::all_columns), comment_aggregates::all_columns, is_creator_banned_from_community, - creator_is_moderator, - creator_is_admin, - subscribed_type_selection, + creator_community_actions + .field(community_actions::became_moderator) + .nullable() + .is_not_null(), + coalesce(local_user::admin, false), + community_actions::follow_pending.nullable(), is_saved_selection, - is_creator_blocked_selection, + person_actions::blocked.nullable().is_not_null(), score_selection, )) }; diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index 21a1bd0de6..c80f96f617 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -153,11 +153,14 @@ fn queries<'a>() -> Queries< aliases::person1.fields(person::all_columns), comment_aggregates::all_columns, is_creator_banned_from_community, - creator_is_moderator, - creator_is_admin, - subscribed_type_selection, + creator_community_actions + .field(community_actions::became_moderator) + .nullable() + .is_not_null(), + coalesce(local_user::admin, false), + community_actions::follow_pending.nullable(), is_saved_selection, - is_creator_blocked_selection, + person_actions::blocked.nullable().is_not_null(), score_selection, )) }; From af5929877e5b650c4ffa9a4ca92d3ccf0b826bca Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 9 Mar 2024 18:25:34 +0000 Subject: [PATCH 052/241] private_message_view --- Cargo.lock | 8 +++ Cargo.toml | 1 + crates/db_schema/Cargo.toml | 1 + crates/db_schema/src/utils.rs | 24 ++++++--- crates/db_views/src/private_message_view.rs | 60 +++++++++------------ 5 files changed, 53 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b7990d250d..ca8466be95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1441,6 +1441,13 @@ dependencies = [ "tokio-postgres", ] +[[package]] +name = "diesel-bind-if-some" +version = "0.1.0" +dependencies = [ + "diesel", +] + [[package]] name = "diesel-derive-enum" version = "2.1.0" @@ -2695,6 +2702,7 @@ dependencies = [ "deadpool 0.10.0", "diesel", "diesel-async", + "diesel-bind-if-some", "diesel-derive-enum", "diesel-derive-newtype", "diesel_ltree", diff --git a/Cargo.toml b/Cargo.toml index 037d03919e..1c79bf6c94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -164,6 +164,7 @@ moka = { version = "0.12.4", features = ["future"] } i-love-jesus = { version = "0.1.0" } clap = { version = "4.4.18", features = ["derive"] } pretty_assertions = "1.4.0" +diesel-bind-if-some = { path = "../diesel-utils/crates/diesel-bind-if-some" } [dependencies] lemmy_api = { workspace = true } diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index d0d66d69f3..3f26d64c2b 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -80,6 +80,7 @@ rustls = { workspace = true, optional = true } uuid = { workspace = true, features = ["v4"] } i-love-jesus = { workspace = true, optional = true } anyhow = { workspace = true } +diesel-bind-if-some = { workspace = true } [dev-dependencies] serial_test = { workspace = true } diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index c9dd16d75b..caf7fc25ee 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -40,6 +40,7 @@ use diesel_async::{ }, SimpleAsyncConnection, }; +use diesel_bind_if_some::BindIfSome; use diesel_migrations::EmbeddedMigrations; use futures_util::{future::BoxFuture, Future, FutureExt}; use i_love_jesus::CursorKey; @@ -487,29 +488,38 @@ pub fn now() -> AsExprOf { diesel::dsl::now.into_sql::() } -// TODO: BindIfSome -pub fn actions( +pub fn actions( actions_table: T, - person_id: Option, + person_id: Option

, target_id: C, -) -> dsl::On, Option>, dsl::Eq>> +) -> dsl::On< + T, + dsl::And< + dsl::Eq, BindIfSome>>, + dsl::Eq, + >, +> where + P: AsExpression, T: Table, K0: Default + Expression + Sized, dsl::Nullable: diesel::ExpressionMethods, as Expression>::SqlType: SqlType, - Option: AsExpression< as Expression>::SqlType>, + BindIfSome>: + AsExpression< as Expression>::SqlType>, K1: Default + diesel::ExpressionMethods, ::SqlType: SqlType, C: AsExpression<::SqlType>, - dsl::Eq, Option>: + dsl::Eq, BindIfSome>>: Expression>, dsl::Eq: Expression, { actions_table.on( K0::default() .nullable() - .eq(person_id) + .eq(BindIfSome( + person_id.map(|id| id.into_sql::()), + )) .and(K1::default().eq(target_id)), ) } diff --git a/crates/db_views/src/private_message_view.rs b/crates/db_views/src/private_message_view.rs index a3e2469c96..2555b6b661 100644 --- a/crates/db_views/src/private_message_view.rs +++ b/crates/db_views/src/private_message_view.rs @@ -12,8 +12,8 @@ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases, newtypes::{PersonId, PrivateMessageId}, - schema::{instance_block, person, person_block, private_message}, - utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, + schema::{instance_actions, person, person_actions, private_message}, + utils::{actions, get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, }; use tracing::debug; @@ -27,20 +27,16 @@ fn queries<'a>() -> Queries< .inner_join( aliases::person1.on(private_message::recipient_id.eq(aliases::person1.field(person::id))), ) - .left_join( - person_block::table.on( - private_message::creator_id - .eq(person_block::target_id) - .and(person_block::person_id.eq(aliases::person1.field(person::id))), - ), - ) - .left_join( - instance_block::table.on( - person::instance_id - .eq(instance_block::instance_id) - .and(instance_block::person_id.eq(aliases::person1.field(person::id))), - ), - ) + .left_join(actions( + person_actions::table, + Some(aliases::person1.field(person::id)), + private_message::creator_id, + )) + .left_join(actions( + instance_actions::table, + Some(aliases::person1.field(person::id)), + person::instance_id, + )) }; let selection = ( @@ -62,9 +58,9 @@ fn queries<'a>() -> Queries< let mut query = all_joins(private_message::table.into_boxed()) .select(selection) // Dont show replies from blocked users - .filter(person_block::person_id.is_null()) + .filter(person_actions::blocked.is_null()) // Dont show replies from blocked instances - .filter(instance_block::person_id.is_null()); + .filter(instance_actions::blocked.is_null()); // If its unread, I only want the ones to me if options.unread_only { @@ -127,24 +123,20 @@ impl PrivateMessageView { private_message::table // Necessary to get the senders instance_id .inner_join(person::table.on(private_message::creator_id.eq(person::id))) - .left_join( - person_block::table.on( - private_message::creator_id - .eq(person_block::target_id) - .and(person_block::person_id.eq(my_person_id)), - ), - ) - .left_join( - instance_block::table.on( - person::instance_id - .eq(instance_block::instance_id) - .and(instance_block::person_id.eq(my_person_id)), - ), - ) + .left_join(actions( + person_actions::table, + Some(my_person_id), + private_message::creator_id, + )) + .left_join(actions( + instance_actions::table, + Some(my_person_id), + person::instance_id, + )) // Dont count replies from blocked users - .filter(person_block::person_id.is_null()) + .filter(person_actions::blocked.is_null()) // Dont count replies from blocked instances - .filter(instance_block::person_id.is_null()) + .filter(instance_actions::blocked.is_null()) .filter(private_message::read.eq(false)) .filter(private_message::recipient_id.eq(my_person_id)) .filter(private_message::deleted.eq(false)) From 40916064aa1e815b143f1584b8ccb56068b1a5d0 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 9 Mar 2024 18:30:07 +0000 Subject: [PATCH 053/241] vote_view --- crates/db_views/src/vote_view.rs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index 723c188f52..7a3d44b1d1 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -1,9 +1,9 @@ use crate::structs::VoteView; -use diesel::{result::Error, ExpressionMethods, QueryDsl}; +use diesel::{result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommentId, PostId}, - schema::{comment_like, person, post_like}, + schema::{comment_actions, person, post_actions}, utils::{get_conn, limit_and_offset, DbPool}, }; @@ -17,11 +17,15 @@ impl VoteView { let conn = &mut get_conn(pool).await?; let (limit, offset) = limit_and_offset(page, limit)?; - post_like::table + post_actions::table .inner_join(person::table) - .filter(post_like::post_id.eq(post_id)) - .select((person::all_columns, post_like::score)) - .order_by(post_like::score) + .filter(post_actions::post_id.eq(post_id)) + .filter(post_actions::like_score.is_not_null()) + .select(( + person::all_columns, + post_actions::like_score.assume_not_null(), + )) + .order_by(post_actions::like_score) .limit(limit) .offset(offset) .load::(conn) @@ -37,11 +41,15 @@ impl VoteView { let conn = &mut get_conn(pool).await?; let (limit, offset) = limit_and_offset(page, limit)?; - comment_like::table + comment_actions::table .inner_join(person::table) - .filter(comment_like::comment_id.eq(comment_id)) - .select((person::all_columns, comment_like::score)) - .order_by(comment_like::score) + .filter(comment_actions::comment_id.eq(comment_id)) + .filter(comment_actions::like_score.is_not_null()) + .select(( + person::all_columns, + comment_actions::like_score.assume_not_null(), + )) + .order_by(comment_actions::like_score) .limit(limit) .offset(offset) .load::(conn) From 0b3090b46dedab3220cedfd454e325cab1d76135 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 9 Mar 2024 20:43:25 +0000 Subject: [PATCH 054/241] comment_reply_view --- .../db_views_actor/src/comment_reply_view.rs | 182 ++++++------------ 1 file changed, 57 insertions(+), 125 deletions(-) diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index 805e58eeaf..3cced4f297 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -1,37 +1,40 @@ use crate::structs::CommentReplyView; use diesel::{ - dsl::exists, pg::Pg, result::Error, - sql_types, BoolExpressionMethods, - BoxableExpression, ExpressionMethods, - IntoSql, JoinOnDsl, NullableExpressionMethods, QueryDsl, }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ - aliases, + aliases::{self, creator_community_actions}, newtypes::{CommentReplyId, PersonId}, schema::{ comment, + comment_actions, comment_aggregates, - comment_like, comment_reply, - comment_saved, community, - community_follower, - community_moderator, - community_person_ban, + community_actions, local_user, person, - person_block, + person_actions, post, }, - utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, + utils::{ + actions, + functions::coalesce, + get_conn, + limit_and_offset, + DbConn, + DbPool, + ListFn, + Queries, + ReadFn, + }, CommentSortType, }; @@ -39,111 +42,39 @@ fn queries<'a>() -> Queries< impl ReadFn<'a, CommentReplyView, (CommentReplyId, Option)>, impl ListFn<'a, CommentReplyView, CommentReplyQuery>, > { - let is_creator_banned_from_community = exists( - community_person_ban::table.filter( - community::id - .eq(community_person_ban::community_id) - .and(community_person_ban::person_id.eq(comment::creator_id)), - ), - ); - - let is_saved = |person_id| { - exists( - comment_saved::table.filter( - comment::id - .eq(comment_saved::comment_id) - .and(comment_saved::person_id.eq(person_id)), - ), - ) - }; - - let is_community_followed = |person_id| { - community_follower::table - .filter( - post::community_id - .eq(community_follower::community_id) - .and(community_follower::person_id.eq(person_id)), - ) - .select(community_follower::pending.nullable()) - .single_value() - }; - - let is_creator_blocked = |person_id| { - exists( - person_block::table.filter( - comment::creator_id - .eq(person_block::target_id) - .and(person_block::person_id.eq(person_id)), - ), - ) - }; - - let score = |person_id| { - comment_like::table - .filter( - comment::id - .eq(comment_like::comment_id) - .and(comment_like::person_id.eq(person_id)), - ) - .select(comment_like::score.nullable()) - .single_value() - }; - - let creator_is_moderator = exists( - community_moderator::table.filter( - community::id - .eq(community_moderator::community_id) - .and(community_moderator::person_id.eq(comment::creator_id)), - ), - ); - - let creator_is_admin = exists( - local_user::table.filter( - comment::creator_id - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ); - let all_joins = move |query: comment_reply::BoxedQuery<'a, Pg>, my_person_id: Option| { - let score_selection: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = if let Some(person_id) = my_person_id { - Box::new(score(person_id)) - } else { - Box::new(None::.into_sql::>()) - }; - - let subscribed_type_selection: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = if let Some(person_id) = my_person_id { - Box::new(is_community_followed(person_id)) - } else { - Box::new(None::.into_sql::>()) - }; - - let is_saved_selection: Box> = - if let Some(person_id) = my_person_id { - Box::new(is_saved(person_id)) - } else { - Box::new(false.into_sql::()) - }; - - let is_creator_blocked_selection: Box> = - if let Some(person_id) = my_person_id { - Box::new(is_creator_blocked(person_id)) - } else { - Box::new(false.into_sql::()) - }; - query - .inner_join(comment::table) - .inner_join(person::table.on(comment::creator_id.eq(person::id))) - .inner_join(post::table.on(comment::post_id.eq(post::id))) - .inner_join(community::table.on(post::community_id.eq(community::id))) + .inner_join( + comment::table + .inner_join(person::table.left_join(local_user::table)) + .inner_join(post::table.inner_join(community::table)) + .inner_join(comment_aggregates::table), + ) .inner_join(aliases::person1) - .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id))) + .left_join(actions(comment_actions::table, my_person_id, comment::id)) + .left_join(actions( + community_actions::table, + my_person_id, + post::community_id, + )) + .left_join(actions( + person_actions::table, + my_person_id, + comment::creator_id, + )) + .left_join( + creator_community_actions.on( + creator_community_actions + .field(community_actions::person_id) + .eq(comment::creator_id) + .and( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id), + ), + ), + ) .select(( comment_reply::all_columns, comment::all_columns, @@ -152,16 +83,19 @@ fn queries<'a>() -> Queries< community::all_columns, aliases::person1.fields(person::all_columns), comment_aggregates::all_columns, - is_creator_banned_from_community, + creator_community_actions + .field(community_actions::received_ban) + .nullable() + .is_not_null(), creator_community_actions .field(community_actions::became_moderator) .nullable() .is_not_null(), - coalesce(local_user::admin, false), + coalesce(local_user::admin.nullable(), false), community_actions::follow_pending.nullable(), - is_saved_selection, + comment_actions::saved.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(), - score_selection, + comment_actions::like_score.nullable(), )) }; @@ -233,15 +167,13 @@ impl CommentReplyView { comment_reply::table .inner_join(comment::table) - .left_join( - person_block::table.on( - comment::creator_id - .eq(person_block::target_id) - .and(person_block::person_id.eq(my_person_id)), - ), - ) + .left_join(actions( + person_actions::table, + Some(my_person_id), + comment::creator_id, + )) // Dont count replies from blocked users - .filter(person_block::person_id.is_null()) + .filter(person_actions::blocked.is_null()) .filter(comment_reply::recipient_id.eq(my_person_id)) .filter(comment_reply::read.eq(false)) .filter(comment::deleted.eq(false)) From 9bde05435d0f1fa3c0f308df1762848334356e03 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sat, 9 Mar 2024 21:01:38 +0000 Subject: [PATCH 055/241] some action views --- .../src/community_block_view.rs | 9 +++-- .../src/community_follower_view.rs | 21 +++++----- .../src/community_moderator_view.rs | 39 +++++++++---------- .../src/community_person_ban_view.rs | 8 ++-- 4 files changed, 40 insertions(+), 37 deletions(-) diff --git a/crates/db_views_actor/src/community_block_view.rs b/crates/db_views_actor/src/community_block_view.rs index c7d3d18367..15b3851055 100644 --- a/crates/db_views_actor/src/community_block_view.rs +++ b/crates/db_views_actor/src/community_block_view.rs @@ -3,21 +3,22 @@ use diesel::{result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::PersonId, - schema::{community, community_block, person}, + schema::{community, community_actions, person}, utils::{get_conn, DbPool}, }; impl CommunityBlockView { pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { let conn = &mut get_conn(pool).await?; - community_block::table + community_actions::table .inner_join(person::table) .inner_join(community::table) .select((person::all_columns, community::all_columns)) - .filter(community_block::person_id.eq(person_id)) + .filter(community_actions::person_id.eq(person_id)) + .filter(community_actions::blocked.is_not_null()) .filter(community::deleted.eq(false)) .filter(community::removed.eq(false)) - .order_by(community_block::published) + .order_by(community_actions::blocked) .load::(conn) .await } diff --git a/crates/db_views_actor/src/community_follower_view.rs b/crates/db_views_actor/src/community_follower_view.rs index b2eeda29e9..c935bb2183 100644 --- a/crates/db_views_actor/src/community_follower_view.rs +++ b/crates/db_views_actor/src/community_follower_view.rs @@ -9,7 +9,7 @@ use diesel::{ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, - schema::{community, community_follower, person}, + schema::{community, community_actions, person}, utils::{functions::coalesce, get_conn, DbPool}, }; @@ -27,13 +27,13 @@ impl CommunityFollowerView { // that would work for all instances that support fully shared inboxes. // It would be a bit more complicated though to keep it in sync. - community_follower::table + community_actions::table .inner_join(community::table) .inner_join(person::table) .filter(person::instance_id.eq(instance_id)) .filter(community::local) // this should be a no-op since community_followers table only has local-person+remote-community or remote-person+local-community .filter(not(person::local)) - .filter(community_follower::published.gt(published_since.naive_utc())) + .filter(community_actions::followed.gt(published_since.naive_utc())) .select(( community::id, coalesce(person::shared_inbox_url, person::inbox_url), @@ -47,8 +47,9 @@ impl CommunityFollowerView { community_id: CommunityId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - let res = community_follower::table - .filter(community_follower::community_id.eq(community_id)) + let res = community_actions::table + .filter(community_actions::community_id.eq(community_id)) + .filter(community_actions::followed.is_not_null()) .filter(not(person::local)) .inner_join(person::table) .select(coalesce(person::shared_inbox_url, person::inbox_url)) @@ -63,8 +64,9 @@ impl CommunityFollowerView { community_id: CommunityId, ) -> Result { let conn = &mut get_conn(pool).await?; - let res = community_follower::table - .filter(community_follower::community_id.eq(community_id)) + let res = community_actions::table + .filter(community_actions::community_id.eq(community_id)) + .filter(community_actions::followed.is_not_null()) .select(count_star()) .first::(conn) .await?; @@ -74,11 +76,12 @@ impl CommunityFollowerView { pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { let conn = &mut get_conn(pool).await?; - community_follower::table + community_actions::table .inner_join(community::table) .inner_join(person::table) .select((community::all_columns, person::all_columns)) - .filter(community_follower::person_id.eq(person_id)) + .filter(community_actions::person_id.eq(person_id)) + .filter(community_actions::followed.is_not_null()) .filter(community::deleted.eq(false)) .filter(community::removed.eq(false)) .order_by(community::title) diff --git a/crates/db_views_actor/src/community_moderator_view.rs b/crates/db_views_actor/src/community_moderator_view.rs index 808fc03407..c0345ef3e6 100644 --- a/crates/db_views_actor/src/community_moderator_view.rs +++ b/crates/db_views_actor/src/community_moderator_view.rs @@ -3,7 +3,7 @@ use diesel::{dsl::exists, result::Error, select, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, PersonId}, - schema::{community, community_moderator, person}, + schema::{community, community_actions, person}, utils::{get_conn, DbPool}, CommunityVisibility, }; @@ -14,16 +14,11 @@ impl CommunityModeratorView { find_community_id: CommunityId, find_person_id: PersonId, ) -> Result { - use lemmy_db_schema::schema::community_moderator::dsl::{ - community_id, - community_moderator, - person_id, - }; let conn = &mut get_conn(pool).await?; select(exists( - community_moderator - .filter(community_id.eq(find_community_id)) - .filter(person_id.eq(find_person_id)), + community_actions::table + .find((find_person_id, find_community_id)) + .filter(community_actions::became_moderator.is_not_null()), )) .get_result::(conn) .await @@ -33,10 +28,11 @@ impl CommunityModeratorView { pool: &mut DbPool<'_>, find_person_id: PersonId, ) -> Result { - use lemmy_db_schema::schema::community_moderator::dsl::{community_moderator, person_id}; let conn = &mut get_conn(pool).await?; select(exists( - community_moderator.filter(person_id.eq(find_person_id)), + community_actions::table + .filter(community_actions::person_id.eq(find_person_id)) + .filter(community_actions::became_moderator.is_not_null()), )) .get_result::(conn) .await @@ -47,12 +43,13 @@ impl CommunityModeratorView { community_id: CommunityId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - community_moderator::table + community_actions::table .inner_join(community::table) .inner_join(person::table) - .filter(community_moderator::community_id.eq(community_id)) + .filter(community_actions::community_id.eq(community_id)) + .filter(community_actions::became_moderator.is_not_null()) .select((community::all_columns, person::all_columns)) - .order_by(community_moderator::published) + .order_by(community_actions::became_moderator) .load::(conn) .await } @@ -63,10 +60,11 @@ impl CommunityModeratorView { is_authenticated: bool, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - let mut query = community_moderator::table + let mut query = community_actions::table .inner_join(community::table) .inner_join(person::table) - .filter(community_moderator::person_id.eq(person_id)) + .filter(community_actions::person_id.eq(person_id)) + .filter(community_actions::became_moderator.is_not_null()) .filter(community::deleted.eq(false)) .filter(community::removed.eq(false)) .select((community::all_columns, person::all_columns)) @@ -81,16 +79,17 @@ impl CommunityModeratorView { /// Ideally this should be a group by, but diesel doesn't support it yet pub async fn get_community_first_mods(pool: &mut DbPool<'_>) -> Result, Error> { let conn = &mut get_conn(pool).await?; - community_moderator::table + community_actions::table .inner_join(community::table) .inner_join(person::table) + .filter(community_actions::became_moderator.is_not_null()) .select((community::all_columns, person::all_columns)) // A hacky workaround instead of group_bys // https://stackoverflow.com/questions/24042359/how-to-join-only-one-row-in-joined-table-with-postgres - .distinct_on(community_moderator::community_id) + .distinct_on(community_actions::community_id) .order_by(( - community_moderator::community_id, - community_moderator::person_id, + community_actions::community_id, + community_actions::person_id, )) .load::(conn) .await diff --git a/crates/db_views_actor/src/community_person_ban_view.rs b/crates/db_views_actor/src/community_person_ban_view.rs index 712bb2d3ac..66062d2438 100644 --- a/crates/db_views_actor/src/community_person_ban_view.rs +++ b/crates/db_views_actor/src/community_person_ban_view.rs @@ -3,7 +3,7 @@ use diesel::{dsl::exists, result::Error, select, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, PersonId}, - schema::community_person_ban, + schema::community_actions, utils::{get_conn, DbPool}, }; @@ -15,9 +15,9 @@ impl CommunityPersonBanView { ) -> Result { let conn = &mut get_conn(pool).await?; select(exists( - community_person_ban::table - .filter(community_person_ban::community_id.eq(from_community_id)) - .filter(community_person_ban::person_id.eq(from_person_id)), + community_actions::table + .find((from_person_id, from_community_id)) + .filter(community_actions::received_ban.is_not_null()), )) .get_result::(conn) .await From 5c68ec08be6ec57d571babcb8b626b068f3dd07f Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 10 Mar 2024 02:39:52 +0000 Subject: [PATCH 056/241] action_query, find_action --- .../src/aggregates/person_post_aggregates.rs | 6 +-- crates/db_schema/src/impls/community.rs | 19 +++---- crates/db_schema/src/impls/community_block.rs | 11 ++--- crates/db_schema/src/impls/instance_block.rs | 11 ++--- crates/db_schema/src/impls/local_user.rs | 19 +++---- crates/db_schema/src/impls/person.rs | 5 +- crates/db_schema/src/impls/person_block.rs | 11 ++--- crates/db_schema/src/utils.rs | 49 ++++++++++++++++--- crates/db_views/src/post_view.rs | 6 +-- crates/db_views/src/vote_view.rs | 8 ++- .../src/community_block_view.rs | 5 +- .../src/community_follower_view.rs | 11 ++--- .../src/community_moderator_view.rs | 25 ++++------ .../src/community_person_ban_view.rs | 7 +-- 14 files changed, 100 insertions(+), 93 deletions(-) diff --git a/crates/db_schema/src/aggregates/person_post_aggregates.rs b/crates/db_schema/src/aggregates/person_post_aggregates.rs index 53dc4ed4e5..b959a5abdc 100644 --- a/crates/db_schema/src/aggregates/person_post_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_post_aggregates.rs @@ -2,7 +2,7 @@ use crate::{ aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm}, newtypes::{PersonId, PostId}, schema::post_actions, - utils::{get_conn, now, DbPool}, + utils::{find_action, get_conn, now, DbPool}, }; use diesel::{ dsl, @@ -50,9 +50,7 @@ impl PersonPostAggregates { post_id_: PostId, ) -> Result { let conn = &mut get_conn(pool).await?; - post_actions::table - .find((person_id_, post_id_)) - .filter(post_actions::read_comments.is_not_null()) + find_action(post_actions::read_comments, (person_id_, post_id_)) .select(Self::as_select_unwrap()) .first::(conn) .await diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index a976bb138c..11f648612f 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -17,7 +17,7 @@ use crate::{ post::Post, }, traits::{ApubActor, Bannable, Crud, Followable, Joinable}, - utils::{functions::lower, get_conn, now, DbPool}, + utils::{action_query, find_action, functions::lower, get_conn, now, DbPool}, SubscribedType, }; use chrono::{DateTime, Utc}; @@ -227,9 +227,8 @@ impl CommunityModerator { for_person_id: PersonId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - community_actions::table + action_query(community_actions::became_moderator) .filter(community_actions::person_id.eq(for_person_id)) - .filter(community_actions::became_moderator.is_not_null()) .select(community_actions::community_id) .load::(conn) .await @@ -336,9 +335,8 @@ impl CommunityFollower { ) -> Result { let conn = &mut get_conn(pool).await?; select(exists( - community_actions::table - .filter(community_actions::community_id.eq(remote_community_id)) - .filter(community_actions::followed.is_not_null()), + action_query(community_actions::followed) + .filter(community_actions::community_id.eq(remote_community_id)), )) .get_result(conn) .await @@ -380,11 +378,10 @@ impl Followable for CommunityFollower { person_id: PersonId, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update( - community_actions::table - .find((person_id, community_id)) - .filter(community_actions::follow_pending.is_not_null()), - ) + diesel::update(find_action( + community_actions::follow_pending, + (person_id, community_id), + )) .set(community_actions::follow_pending.eq(Some(false))) .returning(Self::as_select_unwrap()) .get_result::(conn) diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index 0d015dc56c..4b5b05605e 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -3,7 +3,7 @@ use crate::{ schema::community_actions, source::community_block::{CommunityBlock, CommunityBlockForm}, traits::Blockable, - utils::{get_conn, now, DbPool}, + utils::{find_action, get_conn, now, DbPool}, }; use chrono::{DateTime, Utc}; use diesel::{ @@ -35,11 +35,10 @@ impl CommunityBlock { for_community_id: CommunityId, ) -> Result { let conn = &mut get_conn(pool).await?; - select(exists( - community_actions::table - .find((for_person_id, for_community_id)) - .filter(community_actions::blocked.is_not_null()), - )) + select(exists(find_action( + community_actions::blocked, + (for_person_id, for_community_id), + ))) .get_result(conn) .await } diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index f6f92b4bd1..6076cca47a 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -3,7 +3,7 @@ use crate::{ schema::instance_actions, source::instance_block::{InstanceBlock, InstanceBlockForm}, traits::Blockable, - utils::{get_conn, now, DbPool}, + utils::{find_action, get_conn, now, DbPool}, }; use chrono::{DateTime, Utc}; use diesel::{ @@ -35,11 +35,10 @@ impl InstanceBlock { for_instance_id: InstanceId, ) -> Result { let conn = &mut get_conn(pool).await?; - select(exists( - instance_actions::table - .find((for_person_id, for_instance_id)) - .filter(instance_actions::blocked.is_not_null()), - )) + select(exists(find_action( + instance_actions::blocked, + (for_person_id, for_instance_id), + ))) .get_result(conn) .await } diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index 5afc0d0ed5..bbbc016f43 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -7,6 +7,7 @@ use crate::{ }, traits::Crud, utils::{ + action_query, functions::{coalesce, lower}, get_conn, now, @@ -109,49 +110,43 @@ impl LocalUser { }; let conn = &mut get_conn(pool).await?; - let followed_communities = community_actions::table + let followed_communities = action_query(community_actions::followed) .filter(community_actions::person_id.eq(person_id_)) - .filter(community_actions::followed.is_not_null()) .inner_join(community::table.on(community_actions::community_id.eq(community::id))) .select(community::actor_id) .get_results(conn) .await?; - let saved_posts = post_actions::table + let saved_posts = action_query(post_actions::saved) .filter(post_actions::person_id.eq(person_id_)) - .filter(post_actions::saved.is_not_null()) .inner_join(post::table.on(post_actions::post_id.eq(post::id))) .select(post::ap_id) .get_results(conn) .await?; - let saved_comments = comment_actions::table + let saved_comments = action_query(comment_actions::saved) .filter(comment_actions::person_id.eq(person_id_)) - .filter(comment_actions::saved.is_not_null()) .inner_join(comment::table.on(comment_actions::comment_id.eq(comment::id))) .select(comment::ap_id) .get_results(conn) .await?; - let blocked_communities = community_actions::table + let blocked_communities = action_query(community_actions::blocked) .filter(community_actions::person_id.eq(person_id_)) - .filter(community_actions::blocked.is_not_null()) .inner_join(community::table) .select(community::actor_id) .get_results(conn) .await?; - let blocked_users = person_actions::table + let blocked_users = action_query(person_actions::blocked) .filter(person_actions::person_id.eq(person_id_)) - .filter(person_actions::blocked.is_not_null()) .inner_join(person::table.on(person_actions::target_id.eq(person::id))) .select(person::actor_id) .get_results(conn) .await?; - let blocked_instances = instance_actions::table + let blocked_instances = action_query(instance_actions::blocked) .filter(instance_actions::person_id.eq(person_id_)) - .filter(instance_actions::blocked.is_not_null()) .inner_join(instance::table) .select(instance::domain) .get_results(conn) diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index f4fd94a3a9..f7c3b91929 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -9,7 +9,7 @@ use crate::{ PersonUpdateForm, }, traits::{ApubActor, Crud, Followable}, - utils::{functions::lower, get_conn, naive_now, now, DbPool}, + utils::{action_query, functions::lower, get_conn, naive_now, now, DbPool}, }; use chrono::{DateTime, Utc}; use diesel::{ @@ -231,10 +231,9 @@ impl PersonFollower { for_person_id: PersonId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - person_actions::table + action_query(person_actions::followed) .inner_join(person::table.on(person_actions::person_id.eq(person::id))) .filter(person_actions::target_id.eq(for_person_id)) - .filter(person_actions::followed.is_not_null()) .select(person::all_columns) .load(conn) .await diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index 15c4d721ab..cabfcaf21f 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -3,7 +3,7 @@ use crate::{ schema::person_actions, source::person_block::{PersonBlock, PersonBlockForm}, traits::Blockable, - utils::{get_conn, now, DbPool}, + utils::{find_action, get_conn, now, DbPool}, }; use chrono::{DateTime, Utc}; use diesel::{ @@ -36,11 +36,10 @@ impl PersonBlock { for_recipient_id: PersonId, ) -> Result { let conn = &mut get_conn(pool).await?; - select(exists( - person_actions::table - .find((for_person_id, for_recipient_id)) - .filter(person_actions::blocked.is_not_null()), - )) + select(exists(find_action( + person_actions::blocked, + (for_person_id, for_recipient_id), + ))) .get_result(conn) .await } diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index caf7fc25ee..45ffc811bb 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -1,7 +1,7 @@ use crate::{ diesel::Connection, diesel_migrations::MigrationHarness, - newtypes::{DbUrl, PersonId}, + newtypes::DbUrl, CommentSortType, SortType, }; @@ -14,15 +14,15 @@ use diesel::{ helper_types::AsExprOf, pg::Pg, query_builder::{Query, QueryFragment}, - query_dsl::methods::LimitDsl, - query_source::AliasSource, + query_dsl::methods::{FilterDsl, FindDsl, LimitDsl}, result::{ ConnectionError, ConnectionResult, Error::{self as DieselError, QueryBuilderError}, }, - sql_types::{self, SqlType, Timestamptz}, + sql_types::{self, SingleValue, SqlType, Timestamptz}, BoolExpressionMethods, + Column, Expression, ExpressionMethods, IntoSql, @@ -517,9 +517,7 @@ where actions_table.on( K0::default() .nullable() - .eq(BindIfSome( - person_id.map(|id| id.into_sql::()), - )) + .eq(BindIfSome(person_id.map(diesel::IntoSql::into_sql))) .and(K1::default().eq(target_id)), ) } @@ -554,6 +552,43 @@ where ) }*/ +/// `action_query(table_name::action_name)` is the same as +/// `table_name::table.filter(table_name::action_name.is_not_null())`. +pub fn action_query(column: C) -> dsl::Filter> +where + C: Column, + C::Table: Default + FilterDsl>, + C::SqlType: SingleValue, +{ + action_query_with_fn(column, |t| t) +} + +pub fn find_action( + column: C, + key: K, +) -> dsl::Filter, dsl::IsNotNull> +where + C: Column, + C::Table: Default + FindDsl, + dsl::Find: FilterDsl>, + C::SqlType: SingleValue, +{ + action_query_with_fn(column, |t| t.find(key)) +} + +fn action_query_with_fn( + column: C, + f: impl FnOnce(C::Table) -> Q, +) -> dsl::Filter> +where + C: Column, + C::Table: Default, + C::SqlType: SingleValue, + Q: FilterDsl>, +{ + f(C::Table::default()).filter(column.is_not_null()) +} + pub type ResultFuture<'a, T> = BoxFuture<'a, Result>; pub trait ReadFn<'a, T, Args>: Fn(DbConn<'a>, Args) -> ResultFuture<'a, T> {} diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 16306da7a7..dab6d2f9b5 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -33,6 +33,7 @@ use lemmy_db_schema::{ }, source::site::Site, utils::{ + action_query, actions, functions::coalesce, fuzzy_search, @@ -272,7 +273,7 @@ fn queries<'a>() -> Queries< { // Do not hide read posts when it is a user profile view // Or, only hide read posts on non-profile views - if let None = options.creator_id { + if options.creator_id.is_none() { query = query.filter(post_actions::read.is_null()); } } @@ -490,9 +491,8 @@ impl<'a> PostQuery<'a> { .person_id; let largest_subscribed = { let conn = &mut get_conn(pool).await?; - community_actions::table + action_query(community_actions::followed) .filter(community_actions::person_id.eq(self_person_id)) - .filter(community_actions::followed.is_not_null()) .inner_join(community_aggregates.on(community_id.eq(community_actions::community_id))) .order_by(users_active_month.desc()) .select(community_id) diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index 7a3d44b1d1..be6ff72ba4 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -4,7 +4,7 @@ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommentId, PostId}, schema::{comment_actions, person, post_actions}, - utils::{get_conn, limit_and_offset, DbPool}, + utils::{action_query, get_conn, limit_and_offset, DbPool}, }; impl VoteView { @@ -17,10 +17,9 @@ impl VoteView { let conn = &mut get_conn(pool).await?; let (limit, offset) = limit_and_offset(page, limit)?; - post_actions::table + action_query(post_actions::like_score) .inner_join(person::table) .filter(post_actions::post_id.eq(post_id)) - .filter(post_actions::like_score.is_not_null()) .select(( person::all_columns, post_actions::like_score.assume_not_null(), @@ -41,10 +40,9 @@ impl VoteView { let conn = &mut get_conn(pool).await?; let (limit, offset) = limit_and_offset(page, limit)?; - comment_actions::table + action_query(comment_actions::like_score) .inner_join(person::table) .filter(comment_actions::comment_id.eq(comment_id)) - .filter(comment_actions::like_score.is_not_null()) .select(( person::all_columns, comment_actions::like_score.assume_not_null(), diff --git a/crates/db_views_actor/src/community_block_view.rs b/crates/db_views_actor/src/community_block_view.rs index 15b3851055..f959b57011 100644 --- a/crates/db_views_actor/src/community_block_view.rs +++ b/crates/db_views_actor/src/community_block_view.rs @@ -4,18 +4,17 @@ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::PersonId, schema::{community, community_actions, person}, - utils::{get_conn, DbPool}, + utils::{action_query, get_conn, DbPool}, }; impl CommunityBlockView { pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { let conn = &mut get_conn(pool).await?; - community_actions::table + action_query(community_actions::blocked) .inner_join(person::table) .inner_join(community::table) .select((person::all_columns, community::all_columns)) .filter(community_actions::person_id.eq(person_id)) - .filter(community_actions::blocked.is_not_null()) .filter(community::deleted.eq(false)) .filter(community::removed.eq(false)) .order_by(community_actions::blocked) diff --git a/crates/db_views_actor/src/community_follower_view.rs b/crates/db_views_actor/src/community_follower_view.rs index c935bb2183..ecd2558d63 100644 --- a/crates/db_views_actor/src/community_follower_view.rs +++ b/crates/db_views_actor/src/community_follower_view.rs @@ -10,7 +10,7 @@ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, schema::{community, community_actions, person}, - utils::{functions::coalesce, get_conn, DbPool}, + utils::{action_query, functions::coalesce, get_conn, DbPool}, }; impl CommunityFollowerView { @@ -47,9 +47,8 @@ impl CommunityFollowerView { community_id: CommunityId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - let res = community_actions::table + let res = action_query(community_actions::followed) .filter(community_actions::community_id.eq(community_id)) - .filter(community_actions::followed.is_not_null()) .filter(not(person::local)) .inner_join(person::table) .select(coalesce(person::shared_inbox_url, person::inbox_url)) @@ -64,9 +63,8 @@ impl CommunityFollowerView { community_id: CommunityId, ) -> Result { let conn = &mut get_conn(pool).await?; - let res = community_actions::table + let res = action_query(community_actions::followed) .filter(community_actions::community_id.eq(community_id)) - .filter(community_actions::followed.is_not_null()) .select(count_star()) .first::(conn) .await?; @@ -76,12 +74,11 @@ impl CommunityFollowerView { pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { let conn = &mut get_conn(pool).await?; - community_actions::table + action_query(community_actions::followed) .inner_join(community::table) .inner_join(person::table) .select((community::all_columns, person::all_columns)) .filter(community_actions::person_id.eq(person_id)) - .filter(community_actions::followed.is_not_null()) .filter(community::deleted.eq(false)) .filter(community::removed.eq(false)) .order_by(community::title) diff --git a/crates/db_views_actor/src/community_moderator_view.rs b/crates/db_views_actor/src/community_moderator_view.rs index c0345ef3e6..e145394d47 100644 --- a/crates/db_views_actor/src/community_moderator_view.rs +++ b/crates/db_views_actor/src/community_moderator_view.rs @@ -4,7 +4,7 @@ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, PersonId}, schema::{community, community_actions, person}, - utils::{get_conn, DbPool}, + utils::{action_query, find_action, get_conn, DbPool}, CommunityVisibility, }; @@ -15,11 +15,10 @@ impl CommunityModeratorView { find_person_id: PersonId, ) -> Result { let conn = &mut get_conn(pool).await?; - select(exists( - community_actions::table - .find((find_person_id, find_community_id)) - .filter(community_actions::became_moderator.is_not_null()), - )) + select(exists(find_action( + community_actions::became_moderator, + (find_person_id, find_community_id), + ))) .get_result::(conn) .await } @@ -30,9 +29,8 @@ impl CommunityModeratorView { ) -> Result { let conn = &mut get_conn(pool).await?; select(exists( - community_actions::table - .filter(community_actions::person_id.eq(find_person_id)) - .filter(community_actions::became_moderator.is_not_null()), + action_query(community_actions::became_moderator) + .filter(community_actions::person_id.eq(find_person_id)), )) .get_result::(conn) .await @@ -43,11 +41,10 @@ impl CommunityModeratorView { community_id: CommunityId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - community_actions::table + action_query(community_actions::became_moderator) .inner_join(community::table) .inner_join(person::table) .filter(community_actions::community_id.eq(community_id)) - .filter(community_actions::became_moderator.is_not_null()) .select((community::all_columns, person::all_columns)) .order_by(community_actions::became_moderator) .load::(conn) @@ -60,11 +57,10 @@ impl CommunityModeratorView { is_authenticated: bool, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - let mut query = community_actions::table + let mut query = action_query(community_actions::became_moderator) .inner_join(community::table) .inner_join(person::table) .filter(community_actions::person_id.eq(person_id)) - .filter(community_actions::became_moderator.is_not_null()) .filter(community::deleted.eq(false)) .filter(community::removed.eq(false)) .select((community::all_columns, person::all_columns)) @@ -79,10 +75,9 @@ impl CommunityModeratorView { /// Ideally this should be a group by, but diesel doesn't support it yet pub async fn get_community_first_mods(pool: &mut DbPool<'_>) -> Result, Error> { let conn = &mut get_conn(pool).await?; - community_actions::table + action_query(community_actions::became_moderator) .inner_join(community::table) .inner_join(person::table) - .filter(community_actions::became_moderator.is_not_null()) .select((community::all_columns, person::all_columns)) // A hacky workaround instead of group_bys // https://stackoverflow.com/questions/24042359/how-to-join-only-one-row-in-joined-table-with-postgres diff --git a/crates/db_views_actor/src/community_person_ban_view.rs b/crates/db_views_actor/src/community_person_ban_view.rs index 66062d2438..d37eb1b78b 100644 --- a/crates/db_views_actor/src/community_person_ban_view.rs +++ b/crates/db_views_actor/src/community_person_ban_view.rs @@ -4,7 +4,7 @@ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, PersonId}, schema::community_actions, - utils::{get_conn, DbPool}, + utils::{find_action, get_conn, DbPool}, }; impl CommunityPersonBanView { @@ -14,10 +14,7 @@ impl CommunityPersonBanView { from_community_id: CommunityId, ) -> Result { let conn = &mut get_conn(pool).await?; - select(exists( - community_actions::table - .find((from_person_id, from_community_id)) - .filter(community_actions::received_ban.is_not_null()), + select(exists(find_action(community_actions::received_ban, (from_person_id, from_community_id)) )) .get_result::(conn) .await From 1650fa8ec79300cfd406ebb2ad6bd255f98de827 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 10 Mar 2024 02:45:04 +0000 Subject: [PATCH 057/241] community_view --- .../src/community_person_ban_view.rs | 8 ++- crates/db_views_actor/src/community_view.rs | 65 +++++-------------- 2 files changed, 23 insertions(+), 50 deletions(-) diff --git a/crates/db_views_actor/src/community_person_ban_view.rs b/crates/db_views_actor/src/community_person_ban_view.rs index d37eb1b78b..fa5a07f876 100644 --- a/crates/db_views_actor/src/community_person_ban_view.rs +++ b/crates/db_views_actor/src/community_person_ban_view.rs @@ -1,5 +1,5 @@ use crate::structs::CommunityPersonBanView; -use diesel::{dsl::exists, result::Error, select, ExpressionMethods, QueryDsl}; +use diesel::{dsl::exists, result::Error, select}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, PersonId}, @@ -14,8 +14,10 @@ impl CommunityPersonBanView { from_community_id: CommunityId, ) -> Result { let conn = &mut get_conn(pool).await?; - select(exists(find_action(community_actions::received_ban, (from_person_id, from_community_id)) - )) + select(exists(find_action( + community_actions::received_ban, + (from_person_id, from_community_id), + ))) .get_result::(conn) .await } diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs index c1cb6eee15..8ab15957ac 100644 --- a/crates/db_views_actor/src/community_view.rs +++ b/crates/db_views_actor/src/community_view.rs @@ -12,17 +12,9 @@ use diesel::{ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, PersonId}, - schema::{ - community, - community_aggregates, - community_block, - community_follower, - community_person_ban, - instance_block, - local_user, - }, + schema::{community, community_actions, community_aggregates, instance_actions, local_user}, source::{community::CommunityFollower, local_user::LocalUser, site::Site}, - utils::{fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, + utils::{actions, fuzzy_search, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, CommunityVisibility, ListingType, SortType, @@ -33,47 +25,26 @@ fn queries<'a>() -> Queries< impl ListFn<'a, CommunityView, (CommunityQuery<'a>, &'a Site)>, > { let all_joins = |query: community::BoxedQuery<'a, Pg>, my_person_id: Option| { - // The left join below will return None in this case - let person_id_join = my_person_id.unwrap_or(PersonId(-1)); - query .inner_join(community_aggregates::table) - .left_join( - community_follower::table.on( - community::id - .eq(community_follower::community_id) - .and(community_follower::person_id.eq(person_id_join)), - ), - ) - .left_join( - instance_block::table.on( - community::instance_id - .eq(instance_block::instance_id) - .and(instance_block::person_id.eq(person_id_join)), - ), - ) - .left_join( - community_block::table.on( - community::id - .eq(community_block::community_id) - .and(community_block::person_id.eq(person_id_join)), - ), - ) - .left_join( - community_person_ban::table.on( - community::id - .eq(community_person_ban::community_id) - .and(community_person_ban::person_id.eq(person_id_join)), - ), - ) + .left_join(actions( + community_actions::table, + my_person_id, + community::id, + )) + .left_join(actions( + instance_actions::table, + my_person_id, + community::instance_id, + )) }; let selection = ( community::all_columns, CommunityFollower::select_subscribed_type(), - community_block::community_id.nullable().is_not_null(), + community_actions::blocked.nullable().is_not_null(), community_aggregates::all_columns, - community_person_ban::person_id.nullable().is_not_null(), + community_actions::received_ban.nullable().is_not_null(), ); let not_removed_or_deleted = community::removed @@ -129,7 +100,7 @@ fn queries<'a>() -> Queries< query = query.filter(not_removed_or_deleted).filter( community::hidden .eq(false) - .or(community_follower::person_id.eq(person_id_join)), + .or(community_actions::follow_pending.is_not_null()), ); } @@ -154,7 +125,7 @@ fn queries<'a>() -> Queries< if let Some(listing_type) = options.listing_type { query = match listing_type { - ListingType::Subscribed => query.filter(community_follower::pending.is_not_null()), // TODO could be this: and(community_follower::person_id.eq(person_id_join)), + ListingType::Subscribed => query.filter(community_actions::follow_pending.is_not_null()), ListingType::Local => query.filter(community::local.eq(true)), _ => query, }; @@ -163,8 +134,8 @@ fn queries<'a>() -> Queries< // Don't show blocked communities and communities on blocked instances. nsfw communities are // also hidden (based on profile setting) if options.local_user.is_some() { - query = query.filter(instance_block::person_id.is_null()); - query = query.filter(community_block::person_id.is_null()); + query = query.filter(instance_actions::blocked.is_null()); + query = query.filter(community_actions::blocked.is_null()); query = query.filter(community::nsfw.eq(false).or(local_user::show_nsfw.eq(true))); } else { // No person in request, only show nsfw communities if show_nsfw is passed into request or if From 82414e184d13aa533e4c04532a3146601098bf32 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 10 Mar 2024 15:26:17 +0000 Subject: [PATCH 058/241] block views --- crates/db_views_actor/src/instance_block_view.rs | 10 +++++----- crates/db_views_actor/src/person_block_view.rs | 14 +++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/crates/db_views_actor/src/instance_block_view.rs b/crates/db_views_actor/src/instance_block_view.rs index 05820862a2..91d7fafbbc 100644 --- a/crates/db_views_actor/src/instance_block_view.rs +++ b/crates/db_views_actor/src/instance_block_view.rs @@ -3,14 +3,14 @@ use diesel::{result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMeth use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::PersonId, - schema::{instance, instance_block, person, site}, - utils::{get_conn, DbPool}, + schema::{instance, instance_actions, person, site}, + utils::{action_query, get_conn, DbPool}, }; impl InstanceBlockView { pub async fn for_person(pool: &mut DbPool<'_>, person_id: PersonId) -> Result, Error> { let conn = &mut get_conn(pool).await?; - instance_block::table + action_query(instance_actions::blocked) .inner_join(person::table) .inner_join(instance::table) .left_join(site::table.on(site::instance_id.eq(instance::id))) @@ -19,8 +19,8 @@ impl InstanceBlockView { instance::all_columns, site::all_columns.nullable(), )) - .filter(instance_block::person_id.eq(person_id)) - .order_by(instance_block::published) + .filter(instance_actions::person_id.eq(person_id)) + .order_by(instance_actions::blocked) .load::(conn) .await } diff --git a/crates/db_views_actor/src/person_block_view.rs b/crates/db_views_actor/src/person_block_view.rs index 5f028acd84..fdb5b1dbfc 100644 --- a/crates/db_views_actor/src/person_block_view.rs +++ b/crates/db_views_actor/src/person_block_view.rs @@ -3,8 +3,8 @@ use diesel::{result::Error, ExpressionMethods, JoinOnDsl, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::PersonId, - schema::{person, person_block}, - utils::{get_conn, DbPool}, + schema::{person, person_actions}, + utils::{action_query, get_conn, DbPool}, }; impl PersonBlockView { @@ -12,18 +12,18 @@ impl PersonBlockView { let conn = &mut get_conn(pool).await?; let target_person_alias = diesel::alias!(person as person1); - person_block::table - .inner_join(person::table.on(person_block::person_id.eq(person::id))) + action_query(person_actions::blocked) + .inner_join(person::table.on(person_actions::person_id.eq(person::id))) .inner_join( - target_person_alias.on(person_block::target_id.eq(target_person_alias.field(person::id))), + target_person_alias.on(person_actions::target_id.eq(target_person_alias.field(person::id))), ) .select(( person::all_columns, target_person_alias.fields(person::all_columns), )) - .filter(person_block::person_id.eq(person_id)) + .filter(person_actions::person_id.eq(person_id)) .filter(target_person_alias.field(person::deleted).eq(false)) - .order_by(person_block::published) + .order_by(person_actions::blocked) .load::(conn) .await } From 4b9291c6ce82e79a3c8c3fbf9ff87e3987eafeca Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 10 Mar 2024 16:03:15 +0000 Subject: [PATCH 059/241] person_mention_view --- crates/db_views/src/comment_view.rs | 3 +- crates/db_views/src/post_view.rs | 4 +- .../db_views_actor/src/comment_reply_view.rs | 3 +- .../db_views_actor/src/person_mention_view.rs | 171 ++++++------------ 4 files changed, 59 insertions(+), 122 deletions(-) diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 84c318bffd..c51b4dd38b 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -28,6 +28,7 @@ use lemmy_db_schema::{ person_actions, post, }, + source::community::CommunityFollower, utils::{ actions, functions::coalesce, @@ -101,7 +102,7 @@ fn queries<'a>() -> Queries< .nullable() .is_not_null(), coalesce(local_user::admin.nullable(), false), - community_actions::follow_pending.nullable(), + CommunityFollower::select_subscribed_type(), comment_actions::saved.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(), comment_actions::like_score.nullable(), diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index dab6d2f9b5..3183b841d7 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -31,7 +31,7 @@ use lemmy_db_schema::{ post_actions, post_aggregates, }, - source::site::Site, + source::{community::CommunityFollower, site::Site}, utils::{ action_query, actions, @@ -110,7 +110,7 @@ fn queries<'a>() -> Queries< .is_not_null(), coalesce(local_user::admin.nullable(), false), post_aggregates::all_columns, - community_actions::follow_pending.nullable(), + CommunityFollower::select_subscribed_type(), post_actions::saved.nullable().is_not_null(), post_actions::read.nullable().is_not_null(), post_actions::hidden.nullable().is_not_null(), diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index 3cced4f297..6373ea4f95 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -24,6 +24,7 @@ use lemmy_db_schema::{ person_actions, post, }, + source::community::CommunityFollower, utils::{ actions, functions::coalesce, @@ -92,7 +93,7 @@ fn queries<'a>() -> Queries< .nullable() .is_not_null(), coalesce(local_user::admin.nullable(), false), - community_actions::follow_pending.nullable(), + CommunityFollower::select_subscribed_type(), comment_actions::saved.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(), comment_actions::like_score.nullable(), diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index c80f96f617..b66c8a60d9 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -1,37 +1,41 @@ use crate::structs::PersonMentionView; use diesel::{ - dsl::exists, pg::Pg, result::Error, - sql_types, BoolExpressionMethods, - BoxableExpression, ExpressionMethods, - IntoSql, JoinOnDsl, NullableExpressionMethods, QueryDsl, }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ - aliases, + aliases::{self, creator_community_actions}, newtypes::{PersonId, PersonMentionId}, schema::{ comment, + comment_actions, comment_aggregates, - comment_like, - comment_saved, community, - community_follower, - community_moderator, - community_person_ban, + community_actions, local_user, person, - person_block, + person_actions, person_mention, post, }, - utils::{get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, + source::community::CommunityFollower, + utils::{ + actions, + functions::coalesce, + get_conn, + limit_and_offset, + DbConn, + DbPool, + ListFn, + Queries, + ReadFn, + }, CommentSortType, }; @@ -39,111 +43,39 @@ fn queries<'a>() -> Queries< impl ReadFn<'a, PersonMentionView, (PersonMentionId, Option)>, impl ListFn<'a, PersonMentionView, PersonMentionQuery>, > { - let is_creator_banned_from_community = exists( - community_person_ban::table.filter( - community::id - .eq(community_person_ban::community_id) - .and(community_person_ban::person_id.eq(comment::creator_id)), - ), - ); - - let is_saved = |person_id| { - exists( - comment_saved::table.filter( - comment::id - .eq(comment_saved::comment_id) - .and(comment_saved::person_id.eq(person_id)), - ), - ) - }; - - let is_community_followed = |person_id| { - community_follower::table - .filter( - post::community_id - .eq(community_follower::community_id) - .and(community_follower::person_id.eq(person_id)), - ) - .select(community_follower::pending.nullable()) - .single_value() - }; - - let is_creator_blocked = |person_id| { - exists( - person_block::table.filter( - comment::creator_id - .eq(person_block::target_id) - .and(person_block::person_id.eq(person_id)), - ), - ) - }; - - let score = |person_id| { - comment_like::table - .filter( - comment::id - .eq(comment_like::comment_id) - .and(comment_like::person_id.eq(person_id)), - ) - .select(comment_like::score.nullable()) - .single_value() - }; - - let creator_is_moderator = exists( - community_moderator::table.filter( - community::id - .eq(community_moderator::community_id) - .and(community_moderator::person_id.eq(comment::creator_id)), - ), - ); - - let creator_is_admin = exists( - local_user::table.filter( - comment::creator_id - .eq(local_user::person_id) - .and(local_user::admin.eq(true)), - ), - ); - let all_joins = move |query: person_mention::BoxedQuery<'a, Pg>, my_person_id: Option| { - let score_selection: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = if let Some(person_id) = my_person_id { - Box::new(score(person_id)) - } else { - Box::new(None::.into_sql::>()) - }; - - let subscribed_type_selection: Box< - dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable>, - > = if let Some(person_id) = my_person_id { - Box::new(is_community_followed(person_id)) - } else { - Box::new(None::.into_sql::>()) - }; - - let is_saved_selection: Box> = - if let Some(person_id) = my_person_id { - Box::new(is_saved(person_id)) - } else { - Box::new(false.into_sql::()) - }; - - let is_creator_blocked_selection: Box> = - if let Some(person_id) = my_person_id { - Box::new(is_creator_blocked(person_id)) - } else { - Box::new(false.into_sql::()) - }; - query - .inner_join(comment::table) - .inner_join(person::table.on(comment::creator_id.eq(person::id))) - .inner_join(post::table.on(comment::post_id.eq(post::id))) - .inner_join(community::table.on(post::community_id.eq(community::id))) + .inner_join( + comment::table + .inner_join(person::table.left_join(local_user::table)) + .inner_join(post::table.inner_join(community::table)) + .inner_join(comment_aggregates::table), + ) .inner_join(aliases::person1) - .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id))) + .left_join(actions( + community_actions::table, + my_person_id, + post::community_id, + )) + .left_join(actions(comment_actions::table, my_person_id, comment::id)) + .left_join(actions( + person_actions::table, + my_person_id, + comment::creator_id, + )) + .left_join( + creator_community_actions.on( + creator_community_actions + .field(community_actions::person_id) + .eq(comment::creator_id) + .and( + creator_community_actions + .field(community_actions::community_id) + .eq(post::community_id), + ), + ), + ) .select(( person_mention::all_columns, comment::all_columns, @@ -152,16 +84,19 @@ fn queries<'a>() -> Queries< community::all_columns, aliases::person1.fields(person::all_columns), comment_aggregates::all_columns, - is_creator_banned_from_community, + creator_community_actions + .field(community_actions::received_ban) + .nullable() + .is_not_null(), creator_community_actions .field(community_actions::became_moderator) .nullable() .is_not_null(), - coalesce(local_user::admin, false), - community_actions::follow_pending.nullable(), - is_saved_selection, + coalesce(local_user::admin.nullable(), false), + CommunityFollower::select_subscribed_type(), + comment_actions::saved.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(), - score_selection, + comment_actions::like_score.nullable(), )) }; From 5e27ed7ff64c00b4fcbda9e079c9cd22af539f8a Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 10 Mar 2024 16:04:40 +0000 Subject: [PATCH 060/241] remove struct update --- crates/api_crud/src/post/read.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/api_crud/src/post/read.rs b/crates/api_crud/src/post/read.rs index e701008b73..affae4e876 100644 --- a/crates/api_crud/src/post/read.rs +++ b/crates/api_crud/src/post/read.rs @@ -78,7 +78,6 @@ pub async fn get_post( person_id, post_id, read_comments, - ..PersonPostAggregatesForm::default() }; PersonPostAggregates::upsert(&mut context.pool(), &person_post_agg_form) .await From 56e02626b6dca727660737e8ee1db8674b77e9fd Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 10 Mar 2024 16:49:26 +0000 Subject: [PATCH 061/241] refactor actions function --- crates/db_schema/src/utils.rs | 78 ++++++++++++----------------------- 1 file changed, 27 insertions(+), 51 deletions(-) diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 45ffc811bb..16e9a2ca47 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -488,70 +488,46 @@ pub fn now() -> AsExprOf { diesel::dsl::now.into_sql::() } +pub type AsRecordOutput = dsl::AsExprOf::SqlType>>; + +pub trait AsRecord: Expression + AsExpression> +where + Self::SqlType: 'static, +{ +} + +impl>> AsRecord for T where + T::SqlType: 'static +{ +} + +// TODO: call `default` on whole tuple when impl for `Nullable` is added pub fn actions( actions_table: T, person_id: Option

, target_id: C, ) -> dsl::On< T, - dsl::And< - dsl::Eq, BindIfSome>>, - dsl::Eq, + dsl::Eq< + AsRecordOutput<(dsl::Nullable, K1)>, + (BindIfSome>, C), >, > where - P: AsExpression, T: Table, - K0: Default + Expression + Sized, - dsl::Nullable: diesel::ExpressionMethods, - as Expression>::SqlType: SqlType, - BindIfSome>: - AsExpression< as Expression>::SqlType>, - K1: Default + diesel::ExpressionMethods, - ::SqlType: SqlType, - C: AsExpression<::SqlType>, - dsl::Eq, BindIfSome>>: - Expression>, - dsl::Eq: Expression, + K0: Default + Expression, + K1: Default, + P: AsExpression, + (dsl::Nullable, K1): AsRecord, + (BindIfSome>, C): + AsExpression<, K1)> as Expression>::SqlType>, { - actions_table.on( - K0::default() - .nullable() - .eq(BindIfSome(person_id.map(diesel::IntoSql::into_sql))) - .and(K1::default().eq(target_id)), - ) + actions_table.on((K0::default().nullable(), K1::default()).into_sql().eq(( + BindIfSome(person_id.map(diesel::IntoSql::into_sql)), + target_id, + ))) } -/*pub fn actions_alias( - actions_table: Alias, - person_id: Option, - target_id: C, -) -> dsl::On, dsl::And>, Option>, dsl::Eq, C>>> -where - AS: AliasSource, - Alias: QuerySource + Clone + Copy, - AS::Target: Table, - K0: Default + Column

, - AliasedField: Expression + Sized, - dsl::Nullable>: diesel::ExpressionMethods, - > as Expression>::SqlType: SqlType, - Option: AsExpression<> as Expression>::SqlType>, - K1: Default + Column
, - AliasedField: diesel::ExpressionMethods, - as Expression>::SqlType: SqlType, - C: AsExpression< as Expression>::SqlType>, - dsl::Eq>, Option>: - Expression>, - dsl::Eq, C>: Expression, -{ - actions_table.on( - actions_table.field(K0::default()) - .nullable() - .eq(person_id) - .and(actions_table.field(K1::default()).eq(target_id)), - ) -}*/ - /// `action_query(table_name::action_name)` is the same as /// `table_name::table.filter(table_name::action_name.is_not_null())`. pub fn action_query(column: C) -> dsl::Filter> From dfe1c745a92063795f45bc12872039687d5c8894 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 10 Mar 2024 21:43:53 +0000 Subject: [PATCH 062/241] actions_alias --- crates/db_schema/src/utils.rs | 35 +++++++++++++++++-- crates/db_views/src/comment_report_view.rs | 18 ++++------ crates/db_views/src/comment_view.rs | 18 ++++------ crates/db_views/src/post_report_view.rs | 29 ++++++++------- crates/db_views/src/post_view.rs | 18 ++++------ .../db_views_actor/src/comment_reply_view.rs | 28 ++++----------- .../db_views_actor/src/person_mention_view.rs | 28 ++++----------- 7 files changed, 81 insertions(+), 93 deletions(-) diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 16e9a2ca47..5f0f3f6894 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -15,13 +15,13 @@ use diesel::{ pg::Pg, query_builder::{Query, QueryFragment}, query_dsl::methods::{FilterDsl, FindDsl, LimitDsl}, + query_source::{Alias, AliasSource, AliasedField}, result::{ ConnectionError, ConnectionResult, Error::{self as DieselError, QueryBuilderError}, }, - sql_types::{self, SingleValue, SqlType, Timestamptz}, - BoolExpressionMethods, + sql_types::{self, SingleValue, Timestamptz}, Column, Expression, ExpressionMethods, @@ -29,6 +29,7 @@ use diesel::{ JoinOnDsl, NullableExpressionMethods, PgConnection, + QuerySource, Table, }; use diesel_async::{ @@ -528,6 +529,36 @@ where ))) } +//type TargetPrimaryKey = AliasedFields::Target as Table>::PrimaryKey>; + +/// Like `actions` but `actions_table` is an alias and person id is not nullable +pub fn actions_alias( + actions_table: Alias, + person_id: P, + target_id: C, +) -> dsl::On, dsl::Eq, AliasedField)>, (P, C)>> +where + Alias: QuerySource + Copy, + T: AliasSource, + T::Target: Table, + K0: Default + Column
, + K1: Default + Column
, + (AliasedField, AliasedField): AsRecord, + (P, C): AsExpression< + , AliasedField)> as Expression>::SqlType, + >, +{ + // TODO: Use `default` for whole tuple when `AliasedField` when impl is added + actions_table.on( + ( + actions_table.field(K0::default()), + actions_table.field(K1::default()), + ) + .into_sql() + .eq((person_id, target_id)), + ) +} + /// `action_query(table_name::action_name)` is the same as /// `table_name::table.filter(table_name::action_name.is_not_null())`. pub fn action_query(column: C) -> dsl::Filter> diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index 40e0a17868..847ea5b443 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -25,6 +25,7 @@ use lemmy_db_schema::{ }, utils::{ actions, + actions_alias, functions::coalesce, get_conn, limit_and_offset, @@ -59,18 +60,11 @@ fn queries<'a>() -> Queries< aliases::person2 .on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), ) - .left_join( - creator_community_actions.on( - creator_community_actions - .field(community_actions::person_id) - .eq(comment::creator_id) - .and( - creator_community_actions - .field(community_actions::community_id) - .eq(post::community_id), - ), - ), - ) + .left_join(actions_alias( + creator_community_actions, + comment::creator_id, + post::community_id, + )) .select(( comment_report::all_columns, comment::all_columns, diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index c51b4dd38b..f862dbdea2 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -31,6 +31,7 @@ use lemmy_db_schema::{ source::community::CommunityFollower, utils::{ actions, + actions_alias, functions::coalesce, fuzzy_search, limit_and_offset, @@ -75,18 +76,11 @@ fn queries<'a>() -> Queries< my_person_id, community::instance_id, )) - .left_join( - creator_community_actions.on( - creator_community_actions - .field(community_actions::person_id) - .eq(comment::creator_id) - .and( - creator_community_actions - .field(community_actions::community_id) - .eq(post::community_id), - ), - ), - ) + .left_join(actions_alias( + creator_community_actions, + comment::creator_id, + post::community_id, + )) .select(( comment::all_columns, person::all_columns, diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/post_report_view.rs index 8bbe6015f8..19ab03f483 100644 --- a/crates/db_views/src/post_report_view.rs +++ b/crates/db_views/src/post_report_view.rs @@ -21,7 +21,17 @@ use lemmy_db_schema::{ post_aggregates, post_report, }, - utils::{actions, get_conn, limit_and_offset, DbConn, DbPool, ListFn, Queries, ReadFn}, + utils::{ + actions, + actions_alias, + get_conn, + limit_and_offset, + DbConn, + DbPool, + ListFn, + Queries, + ReadFn, + }, }; fn queries<'a>() -> Queries< @@ -40,18 +50,11 @@ fn queries<'a>() -> Queries< .on(post_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), ) .left_join(actions(post_actions::table, Some(my_person_id), post::id)) - .left_join( - creator_community_actions.on( - creator_community_actions - .field(community_actions::person_id) - .eq(post::creator_id) - .and( - creator_community_actions - .field(community_actions::community_id) - .eq(post::community_id), - ), - ), - ) + .left_join(actions_alias( + creator_community_actions, + post::creator_id, + post::community_id, + )) .select(( post_report::all_columns, post::all_columns, diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 3183b841d7..f4c1e75f9f 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -35,6 +35,7 @@ use lemmy_db_schema::{ utils::{ action_query, actions, + actions_alias, functions::coalesce, fuzzy_search, get_conn, @@ -84,18 +85,11 @@ fn queries<'a>() -> Queries< my_person_id, post_aggregates::instance_id, )) - .left_join( - creator_community_actions.on( - creator_community_actions - .field(community_actions::person_id) - .eq(post_aggregates::creator_id) - .and( - creator_community_actions - .field(community_actions::community_id) - .eq(post_aggregates::community_id), - ), - ), - ) + .left_join(actions_alias( + creator_community_actions, + post_aggregates::creator_id, + post_aggregates::community_id, + )) .select(( post::all_columns, person::all_columns, diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index 6373ea4f95..467f047aa6 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -1,13 +1,5 @@ use crate::structs::CommentReplyView; -use diesel::{ - pg::Pg, - result::Error, - BoolExpressionMethods, - ExpressionMethods, - JoinOnDsl, - NullableExpressionMethods, - QueryDsl, -}; +use diesel::{pg::Pg, result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, @@ -27,6 +19,7 @@ use lemmy_db_schema::{ source::community::CommunityFollower, utils::{ actions, + actions_alias, functions::coalesce, get_conn, limit_and_offset, @@ -64,18 +57,11 @@ fn queries<'a>() -> Queries< my_person_id, comment::creator_id, )) - .left_join( - creator_community_actions.on( - creator_community_actions - .field(community_actions::person_id) - .eq(comment::creator_id) - .and( - creator_community_actions - .field(community_actions::community_id) - .eq(post::community_id), - ), - ), - ) + .left_join(actions_alias( + creator_community_actions, + comment::creator_id, + post::community_id, + )) .select(( comment_reply::all_columns, comment::all_columns, diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index b66c8a60d9..99039e6ee6 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -1,13 +1,5 @@ use crate::structs::PersonMentionView; -use diesel::{ - pg::Pg, - result::Error, - BoolExpressionMethods, - ExpressionMethods, - JoinOnDsl, - NullableExpressionMethods, - QueryDsl, -}; +use diesel::{pg::Pg, result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, @@ -27,6 +19,7 @@ use lemmy_db_schema::{ source::community::CommunityFollower, utils::{ actions, + actions_alias, functions::coalesce, get_conn, limit_and_offset, @@ -64,18 +57,11 @@ fn queries<'a>() -> Queries< my_person_id, comment::creator_id, )) - .left_join( - creator_community_actions.on( - creator_community_actions - .field(community_actions::person_id) - .eq(comment::creator_id) - .and( - creator_community_actions - .field(community_actions::community_id) - .eq(post::community_id), - ), - ), - ) + .left_join(actions_alias( + creator_community_actions, + comment::creator_id, + post::community_id, + )) .select(( person_mention::all_columns, comment::all_columns, From 231fadb6d64f37cc20691109b1b1648f85a5e4eb Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 12 Mar 2024 14:27:09 +0000 Subject: [PATCH 063/241] clean up return types and trait bounds --- crates/db_schema/src/utils.rs | 37 +++++++++++++---------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 5f0f3f6894..fb030eceaa 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -502,58 +502,49 @@ impl>> AsRecord for T { } -// TODO: call `default` on whole tuple when impl for `Nullable` is added +type OnTupleEq = dsl::On, (R0, R1)>>; + pub fn actions( actions_table: T, person_id: Option

, target_id: C, -) -> dsl::On< - T, - dsl::Eq< - AsRecordOutput<(dsl::Nullable, K1)>, - (BindIfSome>, C), - >, -> +) -> OnTupleEq, K1, BindIfSome>, C> where - T: Table, - K0: Default + Expression, - K1: Default, + T: Table + Copy, + K0: Expression, P: AsExpression, (dsl::Nullable, K1): AsRecord, (BindIfSome>, C): AsExpression<, K1)> as Expression>::SqlType>, { - actions_table.on((K0::default().nullable(), K1::default()).into_sql().eq(( + let (k0, k1) = actions_table.primary_key(); + actions_table.on((k0.nullable(), k1).into_sql().eq(( BindIfSome(person_id.map(diesel::IntoSql::into_sql)), target_id, ))) } -//type TargetPrimaryKey = AliasedFields::Target as Table>::PrimaryKey>; - /// Like `actions` but `actions_table` is an alias and person id is not nullable +#[allow(clippy::type_complexity)] pub fn actions_alias( actions_table: Alias, person_id: P, target_id: C, -) -> dsl::On, dsl::Eq, AliasedField)>, (P, C)>> +) -> OnTupleEq, AliasedField, AliasedField, P, C> where Alias: QuerySource + Copy, - T: AliasSource, + T: AliasSource + Default, T::Target: Table, - K0: Default + Column

, - K1: Default + Column
, + K0: Column
, + K1: Column
, (AliasedField, AliasedField): AsRecord, (P, C): AsExpression< , AliasedField)> as Expression>::SqlType, >, { - // TODO: Use `default` for whole tuple when `AliasedField` when impl is added + let (k0, k1) = T::default().target().primary_key(); actions_table.on( - ( - actions_table.field(K0::default()), - actions_table.field(K1::default()), - ) + (actions_table.field(k0), actions_table.field(k1)) .into_sql() .eq((person_id, target_id)), ) From 1f96d7d13b6c363edae933d3ef9a5ce78ad72668 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 12 Mar 2024 14:34:52 +0000 Subject: [PATCH 064/241] fix all rust code --- src/scheduled_tasks.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scheduled_tasks.rs b/src/scheduled_tasks.rs index 38eb4ece29..647061ae89 100644 --- a/src/scheduled_tasks.rs +++ b/src/scheduled_tasks.rs @@ -15,7 +15,7 @@ use lemmy_db_schema::{ schema::{ captcha_answer, comment, - community_person_ban, + community_actions, instance, person, post, @@ -437,7 +437,7 @@ async fn update_banned_when_expired(pool: &mut DbPool<'_>) { .ok(); diesel::delete( - community_person_ban::table.filter(community_person_ban::expires.lt(now().nullable())), + community_actions::table.filter(community_actions::ban_expires.lt(now().nullable())), ) .execute(&mut conn) .await From 0f3df354cde110e0f006ede764cd036d6a56bd56 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 29 Mar 2024 16:35:46 +0000 Subject: [PATCH 065/241] fmt --- crates/db_views/src/comment_view.rs | 4 +--- crates/db_views/src/post_view.rs | 4 +--- crates/db_views/src/vote_view.rs | 25 ++++++++++++++++++++----- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 323c7bdf45..a216bd44bd 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -91,9 +91,7 @@ fn queries<'a>() -> Queries< .field(community_actions::received_ban) .nullable() .is_not_null(), - community_actions::received_ban - .nullable() - .is_not_null(), + community_actions::received_ban.nullable().is_not_null(), creator_community_actions .field(community_actions::became_moderator) .nullable() diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index c5a8ccb224..c5c7caa371 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -98,9 +98,7 @@ fn queries<'a>() -> Queries< .field(community_actions::received_ban) .nullable() .is_not_null(), - community_actions::received_ban - .nullable() - .is_not_null(), + community_actions::received_ban.nullable().is_not_null(), creator_community_actions .field(community_actions::became_moderator) .nullable() diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index f4909a0fb3..ecc4132dcd 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -9,9 +9,10 @@ use diesel::{ }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ + aliases::creator_community_actions, newtypes::{CommentId, PostId}, schema::{comment_actions, community_actions, person, post, post_actions}, - utils::{action_query, actions_alias, get_conn, limit_and_offset, DbPool},aliases::creator_community_actions + utils::{action_query, actions_alias, get_conn, limit_and_offset, DbPool}, }; impl VoteView { @@ -27,11 +28,18 @@ impl VoteView { action_query(post_actions::like_score) .inner_join(person::table) .inner_join(post::table) - .left_join(actions_alias(creator_community_actions, post_actions::person_id, post::community_id)) + .left_join(actions_alias( + creator_community_actions, + post_actions::person_id, + post::community_id, + )) .filter(post_actions::post_id.eq(post_id)) .select(( person::all_columns, - creator_community_actions.field(community_actions::received_ban).nullable().is_not_null(), + creator_community_actions + .field(community_actions::received_ban) + .nullable() + .is_not_null(), post_actions::like_score.assume_not_null(), )) .order_by(post_actions::like_score) @@ -53,11 +61,18 @@ impl VoteView { action_query(comment_actions::like_score) .inner_join(person::table) .inner_join(post::table) - .left_join(actions_alias(creator_community_actions, comment_actions::person_id, post::community_id)) + .left_join(actions_alias( + creator_community_actions, + comment_actions::person_id, + post::community_id, + )) .filter(comment_actions::comment_id.eq(comment_id)) .select(( person::all_columns, - creator_community_actions.field(community_actions::received_ban).nullable().is_not_null(), + creator_community_actions + .field(community_actions::received_ban) + .nullable() + .is_not_null(), comment_actions::like_score.assume_not_null(), )) .order_by(comment_actions::like_score) From f71e8b4f8c708cdaaf11e7d1dd1a3267a6562386 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 29 Mar 2024 16:42:50 +0000 Subject: [PATCH 066/241] clippy fix --- crates/db_views/src/vote_view.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index ecc4132dcd..6353bcfafb 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -1,9 +1,7 @@ use crate::structs::VoteView; use diesel::{ result::Error, - BoolExpressionMethods, ExpressionMethods, - JoinOnDsl, NullableExpressionMethods, QueryDsl, }; From 7cf8684eacb1b4a41b33bf3c8ca0c16db520c51e Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Mon, 1 Apr 2024 21:09:46 +0000 Subject: [PATCH 067/241] Migrate tables --- crates/db_schema/src/schema.rs | 18 +- crates/db_views/src/vote_view.rs | 7 +- .../down.sql | 258 ++++++++++++++++++ .../up.sql | 248 +++++++++++++++++ 4 files changed, 516 insertions(+), 15 deletions(-) create mode 100644 migrations/2024-03-17-134311_smoosh-tables-together/down.sql create mode 100644 migrations/2024-03-17-134311_smoosh-tables-together/up.sql diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 3c40780457..4499ec5a86 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -103,9 +103,9 @@ diesel::table! { person_id -> Int4, comment_id -> Int4, post_id -> Int4, - saved -> Nullable, - liked -> Nullable, like_score -> Nullable, + liked -> Nullable, + saved -> Nullable, } } @@ -189,11 +189,11 @@ diesel::table! { diesel::table! { community_actions (person_id, community_id) { - person_id -> Int4, community_id -> Int4, - blocked -> Nullable, + person_id -> Int4, followed -> Nullable, follow_pending -> Nullable, + blocked -> Nullable, became_moderator -> Nullable, received_ban -> Nullable, ban_expires -> Nullable, @@ -618,11 +618,11 @@ diesel::table! { diesel::table! { person_actions (person_id, target_id) { - person_id -> Int4, target_id -> Int4, - blocked -> Nullable, + person_id -> Int4, followed -> Nullable, follow_pending -> Nullable, + blocked -> Nullable, } } @@ -685,12 +685,12 @@ diesel::table! { } diesel::table! { - post_actions (person_id, post_id) { - person_id -> Int4, + post_actions (person_id, post_id) { post_id -> Int4, + person_id -> Int4, + read -> Nullable, read_comments -> Nullable, read_comments_amount -> Int8, - read -> Nullable, saved -> Nullable, liked -> Nullable, like_score -> Nullable, diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index 6353bcfafb..0a0748df80 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -1,10 +1,5 @@ use crate::structs::VoteView; -use diesel::{ - result::Error, - ExpressionMethods, - NullableExpressionMethods, - QueryDsl, -}; +use diesel::{result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::creator_community_actions, diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql new file mode 100644 index 0000000000..ac2e45ea7f --- /dev/null +++ b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql @@ -0,0 +1,258 @@ +-- For each new actions table: +-- * Create tables that are dropped in up.sql, and insert into them +-- * Do the opposite of the `ALTER TABLE` commands in up.sql, with `DELETE` being used to +-- only keep rows where the preserved action is not null +-- +-- Create comment_like from comment_actions +CREATE TABLE comment_saved ( + person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + comment_id int REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + published timestamptz DEFAULT now() NOT NULL, + PRIMARY KEY (person_id, comment_id) +); + +INSERT INTO comment_saved (person_id, comment_id, published) +SELECT + person_id, + comment_id, + saved +FROM + comment_actions +WHERE + saved IS NOT NULL; + +DELETE FROM comment_actions +WHERE liked IS NULL; + +ALTER TABLE comment_actions RENAME TO comment_like; + +ALTER TABLE comment_like RENAME COLUMN liked TO published; + +ALTER TABLE comment_like RENAME COLUMN like_score TO score; + +ALTER TABLE comment_like + DROP CONSTRAINT comment_actions_check_liked, + ALTER COLUMN published SET NOT NULL, + ALTER COLUMN published SET DEFAULT now(), + ALTER COLUMN score SET NOT NULL, + DROP COLUMN saved; + +-- Create community_follower from community_actions +CREATE TABLE community_block ( + person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + published timestamptz DEFAULT now() NOT NULL, + PRIMARY KEY (person_id, community_id) +); + +INSERT INTO community_block (person_id, community_id, published) +SELECT + person_id, + community_id, + blocked +FROM + community_actions +WHERE + blocked IS NOT NULL; + +CREATE TABLE community_moderator ( + community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + published timestamptz DEFAULT now() NOT NULL, + PRIMARY KEY (person_id, community_id) +); + +INSERT INTO community_moderator (community_id, person_id, published) +SELECT + community_id, + person_id, + became_moderator +FROM + community_actions +WHERE + became_moderator IS NOT NULL; + +CREATE TABLE community_person_ban ( + community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + published timestamptz DEFAULT now() NOT NULL, + expires timestamptz, + PRIMARY KEY (person_id, community_id) +); + +INSERT INTO community_person_ban (community_id, person_id, published, expires) +SELECT + community_id, + person_id, + received_ban, + ban_expires +FROM + community_actions +WHERE + received_ban IS NOT NULL; + +DELETE FROM community_actions +WHERE followed IS NULL; + +ALTER TABLE community_actions RENAME TO community_follower; + +ALTER TABLE community_follower RENAME COLUMN followed TO published; + +ALTER TABLE community_follower RENAME follow_pending TO pending; + +ALTER TABLE community_follower + DROP CONSTRAINT community_actions_check_followed, + DROP CONSTRAINT community_actions_check_received_ban, + ALTER COLUMN published SET NOT NULL, + ALTER COLUMN published SET DEFAULT now(), + ALTER COLUMN pending SET NOT NULL, + -- This `SET DEFAULT` is done for community follow, but not person follow. It's not a mistake + -- in this migration. Believe it or not, `pending` only had a default value in community follow. + ALTER COLUMN pending SET DEFAULT FALSE, + DROP COLUMN blocked, + DROP COLUMN became_moderator, + DROP COLUMN received_ban, + DROP COLUMN ban_expires; + +-- Create instance_block from instance_actions +DELETE FROM instance_actions +WHERE blocked IS NULL; + +ALTER TABLE instance_actions RENAME TO instance_block; + +ALTER TABLE instance_block RENAME COLUMN blocked TO published; + +ALTER TABLE instance_block + ALTER COLUMN published SET NOT NULL, + ALTER COLUMN published SET DEFAULT now(); + +-- Create person_follower from person_actions +CREATE TABLE person_block ( + person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + target_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + published timestamptz DEFAULT now() NOT NULL, + PRIMARY KEY (person_id, target_id) +); + +INSERT INTO person_block (person_id, target_id, published) +SELECT + person_id, + target_id, + blocked +FROM + person_actions +WHERE + blocked IS NOT NULL; + +DELETE FROM person_actions +WHERE followed IS NULL; + +ALTER TABLE person_actions RENAME TO person_follower; + +ALTER TABLE person_follower RENAME COLUMN person_id TO follower_id; + +ALTER TABLE person_follower RENAME COLUMN target_id TO person_id; + +ALTER TABLE person_follower RENAME COLUMN followed TO published; + +ALTER TABLE person_follower RENAME COLUMN follow_pending TO pending; + +ALTER TABLE person_follower + DROP CONSTRAINT person_actions_check_followed, + ALTER COLUMN published SET NOT NULL, + ALTER COLUMN published SET DEFAULT now(), + ALTER COLUMN pending SET NOT NULL, + DROP COLUMN blocked; + +-- Create post_read from post_actions +CREATE TABLE person_post_aggregates ( + person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + read_comments bigint DEFAULT 0 NOT NULL, + published timestamptz NOT NULL, + PRIMARY KEY (person_id, post_id) +); + +INSERT INTO person_post_aggregates (person_id, post_id, read_comments, published) +SELECT + person_id, + post_id, + read_comments_amount, + read_comments +FROM + post_actions +WHERE + read_comments IS NOT NULL; + +CREATE TABLE post_hide ( + post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + published timestamptz DEFAULT now() NOT NULL, + PRIMARY KEY (person_id, post_id) +); + +INSERT INTO post_hide (post_id, person_id, published) +SELECT + post_id, + person_id, + hidden +FROM + post_actions +WHERE + hidden IS NOT NULL; + +CREATE TABLE post_like ( + post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + score smallint NOT NULL, + published timestamptz DEFAULT now() NOT NULL, + PRIMARY KEY (person_id, post_id) +); + +INSERT INTO post_like (post_id, person_id, score, published) +SELECT + post_id, + person_id, + like_score, + liked +FROM + post_actions +WHERE + liked IS NOT NULL; + +CREATE TABLE post_saved ( + post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, + published timestamptz DEFAULT now() NOT NULL, + PRIMARY KEY (person_id, post_id) +); + +INSERT INTO post_saved (post_id, person_id, published) +SELECT + post_id, + person_id, + saved +FROM + post_actions +WHERE + saved IS NOT NULL; + +DELETE FROM post_actions +WHERE read IS NULL; + +ALTER TABLE post_actions RENAME TO post_read; + +ALTER TABLE post_read RENAME COLUMN read TO published; + +ALTER TABLE post_read + DROP CONSTRAINT post_actions_check_read_comments, + DROP CONSTRAINT post_actions_check_liked, + ALTER COLUMN published SET NOT NULL, + ALTER COLUMN published SET DEFAULT now(), + DROP COLUMN read_comments, + DROP COLUMN read_comments_amount, + DROP COLUMN saved, + DROP COLUMN liked, + DROP COLUMN like_score, + DROP COLUMN hidden; + diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql new file mode 100644 index 0000000000..54471e3265 --- /dev/null +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -0,0 +1,248 @@ +-- For each new actions table: +-- * Transform the table previously used for the most common action type into the new actions table, +-- which should only change the table's metadata instead of rewriting the rows +-- * Add actions from other old tables to the new table +-- +-- Create comment_actions from comment_like +ALTER TABLE comment_like RENAME TO comment_actions; + +ALTER TABLE comment_actions RENAME COLUMN published TO liked; + +ALTER TABLE comment_actions RENAME COLUMN score TO like_score; + +ALTER TABLE comment_actions + ALTER COLUMN liked DROP NOT NULL, + ALTER COLUMN liked DROP DEFAULT, + ALTER COLUMN like_score DROP NOT NULL, + ADD COLUMN saved timestamptz, + ADD CONSTRAINT comment_actions_check_liked CHECK ((liked IS NULL) = (like_score IS NULL)); + +WITH old_comment_saved AS ( + DELETE FROM comment_saved +RETURNING + *) + INSERT INTO comment_actions (person_id, comment_id, saved, post_id) + SELECT + old_comment_saved.person_id, + old_comment_saved.comment_id, + old_comment_saved.published, + comment.post_id + FROM + old_comment_saved + INNER JOIN COMMENT ON comment.id = old_comment_saved.comment_id + ON CONFLICT (person_id, + comment_id) + DO UPDATE SET + saved = excluded.saved; + +-- Create community_actions from community_follower +ALTER TABLE community_follower RENAME TO community_actions; + +ALTER TABLE community_actions RENAME COLUMN published TO followed; + +ALTER TABLE community_actions RENAME pending TO follow_pending; + +ALTER TABLE community_actions + ALTER COLUMN followed DROP NOT NULL, + ALTER COLUMN followed DROP DEFAULT, + ALTER COLUMN follow_pending DROP NOT NULL, + -- This `DROP DEFAULT` is done for community follow, but not person follow. It's not a mistake + -- in this migration. Believe it or not, `pending` only had a default value in community follow. + ALTER COLUMN follow_pending DROP DEFAULT, + ADD COLUMN blocked timestamptz, + ADD COLUMN became_moderator timestamptz, + ADD COLUMN received_ban timestamptz, + ADD COLUMN ban_expires timestamptz, + ADD CONSTRAINT community_actions_check_followed CHECK ((followed IS NULL) = (follow_pending IS NULL)), + ADD CONSTRAINT community_actions_check_received_ban CHECK ((received_ban IS NULL, ban_expires IS NULL) != (FALSE, TRUE)); + +WITH old_community_block AS ( + DELETE FROM community_block +RETURNING + *) + INSERT INTO community_actions (person_id, community_id, blocked) + SELECT + person_id, + community_id, + published + FROM + old_community_block + ON CONFLICT (person_id, + community_id) + DO UPDATE SET + person_id = excluded.person_id, + community_id = excluded.community_id, + blocked = excluded.blocked; + +WITH old_community_moderator AS ( + DELETE FROM community_moderator +RETURNING + *) + INSERT INTO community_actions (person_id, community_id, became_moderator) + SELECT + person_id, + community_id, + published + FROM + old_community_moderator + ON CONFLICT (person_id, + community_id) + DO UPDATE SET + person_id = excluded.person_id, + community_id = excluded.community_id, + became_moderator = excluded.became_moderator; + +WITH old_community_person_ban AS ( + DELETE FROM community_person_ban +RETURNING + *) + INSERT INTO community_actions (person_id, community_id, received_ban, ban_expires) + SELECT + person_id, + community_id, + published, + expires + FROM + old_community_person_ban + ON CONFLICT (person_id, + community_id) + DO UPDATE SET + person_id = excluded.person_id, + community_id = excluded.community_id, + received_ban = excluded.received_ban, + ban_expires = excluded.ban_expires; + +-- Create instance_actions from instance_block +ALTER TABLE instance_block RENAME TO instance_actions; + +ALTER TABLE instance_actions RENAME COLUMN published TO blocked; + +ALTER TABLE instance_actions + ALTER COLUMN blocked DROP NOT NULL, + ALTER COLUMN blocked DROP DEFAULT; + +-- Create person_actions from person_follower +ALTER TABLE person_follower RENAME TO person_actions; + +ALTER TABLE person_actions RENAME COLUMN person_id TO target_id; + +ALTER TABLE person_actions RENAME COLUMN follower_id TO person_id; + +ALTER TABLE person_actions RENAME COLUMN published TO followed; + +ALTER TABLE person_actions RENAME COLUMN pending TO follow_pending; + +ALTER TABLE person_actions + ALTER COLUMN followed DROP NOT NULL, + ALTER COLUMN followed DROP DEFAULT, + ALTER COLUMN follow_pending DROP NOT NULL, + ADD COLUMN blocked timestamptz, + ADD CONSTRAINT person_actions_check_followed CHECK ((followed IS NULL) = (follow_pending IS NULL)); + +WITH old_person_block AS ( + DELETE FROM person_block +RETURNING + *) + INSERT INTO person_actions (person_id, target_id, blocked) + SELECT + person_id, + target_id, + published + FROM + old_person_block + ON CONFLICT (person_id, + target_id) + DO UPDATE SET + person_id = excluded.person_id, + target_id = excluded.target_id, + blocked = excluded.blocked; + +-- Create post_actions from post_read +ALTER TABLE post_read RENAME TO post_actions; + +ALTER TABLE post_actions RENAME COLUMN published TO read; + +ALTER TABLE post_actions + ALTER COLUMN read DROP NOT NULL, + ALTER COLUMN read DROP DEFAULT, + ADD COLUMN read_comments timestamptz, + -- TODO make nullable + ADD COLUMN read_comments_amount bigint NOT NULL, + ADD COLUMN saved timestamptz, + ADD COLUMN liked timestamptz, + ADD COLUMN like_score smallint, + ADD COLUMN hidden timestamptz, + ADD CONSTRAINT post_actions_check_read_comments CHECK ((read_comments IS NULL) = (read_comments_amount IS NULL)), + ADD CONSTRAINT post_actions_check_liked CHECK ((liked IS NULL) = (like_score IS NULL)); + +WITH old_person_post_aggregates AS ( + DELETE FROM person_post_aggregates +RETURNING + *) + INSERT INTO post_actions (person_id, post_id, read_comments, read_comments_amount) + SELECT + person_id, + post_id, + published, + read_comments + FROM + old_person_post_aggregates + ON CONFLICT (person_id, + post_id) + DO UPDATE SET + read_comments = excluded.read_comments, + read_comments_amount = excluded.read_comments_amount; + +WITH old_post_hide AS ( + DELETE FROM post_hide +RETURNING + *) + INSERT INTO post_actions (person_id, post_id, hidden) + SELECT + person_id, + post_id, + published + FROM + old_post_hide + ON CONFLICT (person_id, + post_id) + DO UPDATE SET + hidden = excluded.hidden; + +WITH old_post_like AS ( + DELETE FROM post_like +RETURNING + *) + INSERT INTO post_actions (person_id, post_id, liked, like_score) + SELECT + person_id, + post_id, + published, + score + FROM + old_post_like + ON CONFLICT (person_id, + post_id) + DO UPDATE SET + liked = excluded.liked, + like_score = excluded.like_score; + +WITH old_post_saved AS ( + DELETE FROM post_saved +RETURNING + *) + INSERT INTO post_actions (person_id, post_id, saved) + SELECT + person_id, + post_id, + published + FROM + old_post_saved + ON CONFLICT (person_id, + post_id) + DO UPDATE SET + saved = excluded.saved; + +-- Drop old tables +DROP TABLE comment_saved, community_block, community_moderator, community_person_ban, person_block, person_post_aggregates, post_hide, post_like, post_saved; + From 530926f04396c2a076eea586a2a5e4f08f8203bc Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Thu, 4 Apr 2024 17:27:04 +0000 Subject: [PATCH 068/241] migrate indexes and contraints, and add statistics --- .../down.sql | 73 +++++++++ .../up.sql | 141 ++++++++++++++++++ 2 files changed, 214 insertions(+) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql index ac2e45ea7f..685cd39d68 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql @@ -256,3 +256,76 @@ ALTER TABLE post_read DROP COLUMN like_score, DROP COLUMN hidden; +-- Rename associated stuff +ALTER INDEX comment_actions_pkey RENAME TO comment_like_pkey; + +ALTER INDEX idx_comment_actions_comment RENAME TO idx_comment_like_comment; + +ALTER INDEX idx_comment_actions_post RENAME TO idx_comment_like_post; + +ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_comment_id_fkey TO comment_like_comment_id_fkey; + +ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_person_id_fkey TO comment_like_person_id_fkey; + +ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_post_id_fkey TO comment_like_post_id_fkey; + +ALTER INDEX community_actions_pkey RENAME TO community_follower_pkey; + +ALTER INDEX idx_community_actions_community RENAME TO idx_community_follower_community; + +ALTER TABLE community_follower RENAME CONSTRAINT community_actions_community_id_fkey TO community_follower_community_id_fkey; + +ALTER TABLE community_follower RENAME CONSTRAINT community_actions_person_id_fkey TO community_follower_person_id_fkey; + +ALTER INDEX instance_actions_pkey RENAME TO instance_block_pkey; + +ALTER TABLE instance_block RENAME CONSTRAINT instance_actions_instance_id_fkey TO instance_block_instance_id_fkey; + +ALTER TABLE instance_block RENAME CONSTRAINT instance_actions_person_id_fkey TO instance_block_person_id_fkey; + +ALTER INDEX person_actions_pkey RENAME TO person_follower_pkey; + +ALTER TABLE person_follower RENAME CONSTRAINT person_actions_target_id_fkey TO person_follower_person_id_fkey; + +ALTER TABLE person_follower RENAME CONSTRAINT person_actions_person_id_fkey TO person_follower_follower_id_fkey; + +ALTER INDEX post_actions_pkey RENAME TO post_read_pkey; + +ALTER TABLE post_read RENAME CONSTRAINT post_actions_person_id_fkey TO post_read_person_id_fkey; + +ALTER TABLE post_read RENAME CONSTRAINT post_actions_post_id_fkey TO post_read_post_id_fkey; + +-- Rename idx_community_actions_followed and remove filter +CREATE INDEX idx_community_follower_published ON community_follower (published); + +DROP INDEX idx_community_actions_followed; + +-- Move indexes back to their original tables +CREATE INDEX idx_comment_saved_comment ON comment_saved (comment_id); + +CREATE INDEX idx_comment_saved_person ON comment_saved (person_id); + +CREATE INDEX idx_community_block_community ON community_block (community_id); + +CREATE INDEX idx_community_moderator_community ON community_moderator (community_id); + +CREATE INDEX idx_community_moderator_published ON community_moderator (published); + +CREATE INDEX idx_person_block_person ON person_block (person_id); + +CREATE INDEX idx_person_block_target ON person_block (target_id); + +CREATE INDEX idx_person_post_aggregates_person ON person_post_aggregates (person_id); + +CREATE INDEX idx_person_post_aggregates_post ON person_post_aggregates (post_id); + +CREATE INDEX idx_post_like_post ON post_like (post_id); + +DROP INDEX idx_person_actions_person, idx_person_actions_target, idx_post_actions_person, idx_post_actions_post; + +-- Drop `NOT NULL` indexes of columns that still exist +DROP INDEX idx_comment_actions_liked_not_null, idx_community_actions_followed_not_null, idx_person_actions_followed_not_null, idx_post_actions_read_not_null, idx_instance_actions_blocked_not_null; + +-- Drop statistics of columns that still exist +DROP statistics comment_actions_liked_stat, community_actions_followed_stat, person_actions_followed_stat; + diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index d6035dde8c..7a21d6a050 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -245,3 +245,144 @@ RETURNING -- Drop old tables DROP TABLE comment_saved, community_block, community_moderator, community_person_ban, person_block, person_post_aggregates, post_hide, post_like, post_saved; +-- Rename associated stuff +ALTER INDEX comment_like_pkey RENAME TO comment_actions_pkey; + +ALTER INDEX idx_comment_like_comment RENAME TO idx_comment_actions_comment; + +ALTER INDEX idx_comment_like_post RENAME TO idx_comment_actions_post; + +ALTER TABLE comment_actions RENAME CONSTRAINT comment_like_comment_id_fkey TO comment_actions_comment_id_fkey; + +ALTER TABLE comment_actions RENAME CONSTRAINT comment_like_person_id_fkey TO comment_actions_person_id_fkey; + +ALTER TABLE comment_actions RENAME CONSTRAINT comment_like_post_id_fkey TO comment_actions_post_id_fkey; + +ALTER INDEX community_follower_pkey RENAME TO community_actions_pkey; + +ALTER INDEX idx_community_follower_community RENAME TO idx_community_actions_community; + +ALTER TABLE community_actions RENAME CONSTRAINT community_follower_community_id_fkey TO community_actions_community_id_fkey; + +ALTER TABLE community_actions RENAME CONSTRAINT community_follower_person_id_fkey TO community_actions_person_id_fkey; + +ALTER INDEX instance_block_pkey RENAME TO instance_actions_pkey; + +ALTER TABLE instance_actions RENAME CONSTRAINT instance_block_instance_id_fkey TO instance_actions_instance_id_fkey; + +ALTER TABLE instance_actions RENAME CONSTRAINT instance_block_person_id_fkey TO instance_actions_person_id_fkey; + +ALTER INDEX person_follower_pkey RENAME TO person_actions_pkey; + +ALTER TABLE person_actions RENAME CONSTRAINT person_follower_person_id_fkey TO person_actions_target_id_fkey; + +ALTER TABLE person_actions RENAME CONSTRAINT person_follower_follower_id_fkey TO person_actions_person_id_fkey; + +ALTER INDEX post_read_pkey RENAME TO post_actions_pkey; + +ALTER TABLE post_actions RENAME CONSTRAINT post_read_person_id_fkey TO post_actions_person_id_fkey; + +ALTER TABLE post_actions RENAME CONSTRAINT post_read_post_id_fkey TO post_actions_post_id_fkey; + +-- Rename idx_community_follower_published and add filter +CREATE INDEX idx_community_actions_followed ON community_actions (followed) +WHERE + followed IS NOT NULL; + +DROP INDEX idx_community_follower_published; + +-- Restore indexes of dropped tables +CREATE INDEX idx_community_actions_became_moderator ON community_actions (became_moderator) +WHERE + became_moderator IS NOT NULL; + +CREATE INDEX idx_person_actions_person ON person_actions (person_id); + +CREATE INDEX idx_person_actions_target ON person_actions (target_id); + +CREATE INDEX idx_post_actions_person ON post_actions (person_id); + +CREATE INDEX idx_post_actions_post ON post_actions (post_id); + +-- Create new indexes, with `OR` being used to allow `IS NOT NULL` filters in queries to use either column in +-- a group (e.g. `liked IS NOT NULL` and `like_score IS NOT NULL` both work) +CREATE INDEX idx_comment_actions_liked_not_null ON comment_actions (person_id, comment_id) +WHERE + liked IS NOT NULL OR like_score IS NOT NULL; + +CREATE INDEX idx_comment_actions_saved_not_null ON comment_actions (person_id, comment_id) +WHERE + saved IS NOT NULL; + +CREATE INDEX idx_community_actions_followed_not_null ON community_actions (person_id, community_id) +WHERE + followed IS NOT NULL OR follow_pending IS NOT NULL; + +CREATE INDEX idx_community_actions_blocked_not_null ON community_actions (person_id, community_id) +WHERE + blocked IS NOT NULL; + +CREATE INDEX idx_community_actions_became_moderator_not_null ON community_actions (person_id, community_id) +WHERE + became_moderator IS NOT NULL; + +CREATE INDEX idx_community_actions_received_ban_not_null ON community_actions (person_id, community_id) +WHERE + received_ban IS NOT NULL; + +CREATE INDEX idx_person_actions_followed_not_null ON person_actions (person_id, target_id) +WHERE + followed IS NOT NULL OR follow_pending IS NOT NULL; + +CREATE INDEX idx_person_actions_blocked_not_null ON person_actions (person_id, target_id) +WHERE + blocked IS NOT NULL; + +CREATE INDEX idx_post_actions_read_not_null ON post_actions (person_id, post_id) +WHERE + read IS NOT NULL; + +CREATE INDEX idx_post_actions_read_comments_not_null ON post_actions (person_id, post_id) +WHERE + read_comments IS NOT NULL OR read_comments IS NOT NULL; + +CREATE INDEX idx_post_actions_saved_not_null ON post_actions (person_id, post_id) +WHERE + saved IS NOT NULL; + +CREATE INDEX idx_post_actions_liked_not_null ON post_actions (person_id, post_id) +WHERE + liked IS NOT NULL OR like_score IS NOT NULL; + +CREATE INDEX idx_post_actions_hidden_not_null ON post_actions (person_id, post_id) +WHERE + hidden IS NOT NULL; + +-- This index is currently redundant because instance_actions only has 1 action type, but inconsistency +-- with other tables would make it harder to do everything correctly when adding another action type. If +-- omitting this index is significantly beneficial, then it should be done when indexes are declared outside +-- of migrations in `lemmy_db_schema::schema_setup` and adding this index doesn't require changing code that +-- isn't right next to the code that needs to be changed to add another action type to instance_actions. +CREATE INDEX idx_instance_actions_blocked_not_null ON instance_actions (person_id, instance_id) +WHERE + blocked IS NOT NULL; + +-- Create new statistics for more accurate estimations of how much of an index will be read (e.g. for +-- `(liked, like_score)`, the query planner might othewise assume that `(TRUE, FALSE)` and `(TRUE, TRUE)` +-- are equally likely when only `(TRUE, TRUE)` is possible, which would make it severely underestimate +-- the efficiency of using the index) +CREATE statistics comment_actions_liked_stat ON (liked IS NULL), (like_score IS NULL) +FROM comment_actions; + +CREATE statistics community_actions_followed_stat ON (followed IS NULL), (follow_pending IS NULL) +FROM community_actions; + +CREATE statistics person_actions_followed_stat ON (followed IS NULL), (follow_pending IS NULL) +FROM person_actions; + +CREATE statistics post_actions_read_comments_stat ON (read_comments IS NULL), (read_comments_amount IS NULL) +FROM post_actions; + +CREATE statistics post_actions_liked_stat ON (liked IS NULL), (like_score IS NULL) +FROM post_actions; + From ded71ddd19fbc705116e9257bf11affd59817666 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 5 Apr 2024 20:35:34 +0000 Subject: [PATCH 069/241] fix what appears to be a messed up merge --- crates/api_common/src/build_response.rs | 103 +++++++++++++----------- 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/crates/api_common/src/build_response.rs b/crates/api_common/src/build_response.rs index 6fb96e1130..19e0bb46bb 100644 --- a/crates/api_common/src/build_response.rs +++ b/crates/api_common/src/build_response.rs @@ -112,7 +112,7 @@ pub async fn send_local_notifs( if let Ok(mention_user_view) = user_view { // TODO // At some point, make it so you can't tag the parent creator either - // This can cause two notifications, one for reply and the other for mention + // Potential duplication of notifications, one for reply and the other for mention, is handled below by checking recipient ids recipient_ids.push(mention_user_view.local_user.id); let user_mention_form = PersonMentionInsertForm { @@ -163,30 +163,33 @@ pub async fn send_local_notifs( if parent_comment.creator_id != person.id && !check_blocks { let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await; if let Ok(parent_user_view) = user_view { - recipient_ids.push(parent_user_view.local_user.id); - - let comment_reply_form = CommentReplyInsertForm { - recipient_id: parent_user_view.person.id, - comment_id: comment.id, - read: None, - }; - - // Allow this to fail softly, since comment edits might re-update or replace it - // Let the uniqueness handle this fail - CommentReply::create(&mut context.pool(), &comment_reply_form) - .await - .ok(); - - if do_send_email { - let lang = get_interface_language(&parent_user_view); - let content = markdown_to_html(&comment.content); - send_email_to_user( - &parent_user_view, - &lang.notification_comment_reply_subject(&person.name), - &lang.notification_comment_reply_body(&content, &inbox_link, &person.name), - context.settings(), - ) - .await + // Don't duplicate notif if already mentioned by checking recipient ids + if !recipient_ids.contains(&parent_user_view.local_user.id) { + recipient_ids.push(parent_user_view.local_user.id); + + let comment_reply_form = CommentReplyInsertForm { + recipient_id: parent_user_view.person.id, + comment_id: comment.id, + read: None, + }; + + // Allow this to fail softly, since comment edits might re-update or replace it + // Let the uniqueness handle this fail + CommentReply::create(&mut context.pool(), &comment_reply_form) + .await + .ok(); + + if do_send_email { + let lang = get_interface_language(&parent_user_view); + let content = markdown_to_html(&comment.content); + send_email_to_user( + &parent_user_view, + &lang.notification_comment_reply_subject(&person.name), + &lang.notification_comment_reply_body(&content, &inbox_link, &person.name), + context.settings(), + ) + .await + } } } } @@ -205,30 +208,32 @@ pub async fn send_local_notifs( let creator_id = post.creator_id; let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await; if let Ok(parent_user_view) = parent_user { - recipient_ids.push(parent_user_view.local_user.id); - - let comment_reply_form = CommentReplyInsertForm { - recipient_id: parent_user_view.person.id, - comment_id: comment.id, - read: None, - }; - - // Allow this to fail softly, since comment edits might re-update or replace it - // Let the uniqueness handle this fail - CommentReply::create(&mut context.pool(), &comment_reply_form) - .await - .ok(); - - if do_send_email { - let lang = get_interface_language(&parent_user_view); - let content = markdown_to_html(&comment.content); - send_email_to_user( - &parent_user_view, - &lang.notification_post_reply_subject(&person.name), - &lang.notification_post_reply_body(&content, &inbox_link, &person.name), - context.settings(), - ) - .await + if !recipient_ids.contains(&parent_user_view.local_user.id) { + recipient_ids.push(parent_user_view.local_user.id); + + let comment_reply_form = CommentReplyInsertForm { + recipient_id: parent_user_view.person.id, + comment_id: comment.id, + read: None, + }; + + // Allow this to fail softly, since comment edits might re-update or replace it + // Let the uniqueness handle this fail + CommentReply::create(&mut context.pool(), &comment_reply_form) + .await + .ok(); + + if do_send_email { + let lang = get_interface_language(&parent_user_view); + let content = markdown_to_html(&comment.content); + send_email_to_user( + &parent_user_view, + &lang.notification_post_reply_subject(&person.name), + &lang.notification_post_reply_body(&content, &inbox_link, &person.name), + context.settings(), + ) + .await + } } } } From afec4c0cfdf589a29bbd93f1bc51fdc44cee0f20 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 21 Apr 2024 14:38:46 +0000 Subject: [PATCH 070/241] commented thing --- .../up.sql | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index 7a21d6a050..73fcc55bd4 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -386,3 +386,39 @@ FROM post_actions; CREATE statistics post_actions_liked_stat ON (liked IS NULL), (like_score IS NULL) FROM post_actions; +-- TODO: move to replaceable_schema +/*CREATE SCHEMA IF NOT EXISTS r; + +CREATE FUNCTION r.delete_empty_comment_actions () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + DELETE FROM comment_actions USING ( + SELECT + (person_id, + comment_id, + post_id, + NULL, + NULL, + NULL) AS row_with_nulls + FROM + new_row + WHERE (new_row.*) = row_with_nulls) AS rows_to_check +WHERE (comment_actions.*) = row_with_nulls +END; +$$; +uplete::delete_empty(comment_actions::table, (comment_actions::person_id, comment_actions::comment_id, comment_actions::post_id, ))*/ + +/*WITH + update_result AS (UPDATE post_actions SET like_score = NULL, liked = NULL RETURNING *) +DELETE FROM post_actions +USING ( + -- I tried to put these selected values in a tuple with a name that can be referenced in the `WHERE` clause, but the name throws a parse error in the `WHERE` clause + SELECT post_actions.post_id, post_actions.person_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL + FROM update_result AS post_actions + -- This check only affects performance, and it can't replace the `(post_actions.*) = (rows_with_nulls.*)` check below because this one is done before locking the rows + WHERE (post_actions.*) = (post_actions.post_id, post_actions.person_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL) +) AS rows_with_nulls +WHERE (post_actions.*) = (rows_with_nulls.*);*/ + From a09f8a8e7ee3aabf4067f0042dc5db3798f2104d Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 07:18:05 -0700 Subject: [PATCH 071/241] Create uplete.rs --- crates/db_schema/src/utils/uplete.rs | 44 ++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 crates/db_schema/src/utils/uplete.rs diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs new file mode 100644 index 0000000000..bd5205aa00 --- /dev/null +++ b/crates/db_schema/src/utils/uplete.rs @@ -0,0 +1,44 @@ +use diesel::{ + dsl, + expression::{is_aggregate, ValidGrouping}, + pg::Pg, + query_builder::{AsQuery, AstPass, QueryFragment}, + result::Error, + sql_types, + AppearsOnTable, + Expression, + Insertable, + QueryId, + SelectableExpression, + Table, +}; + +#[derive(QueryId)] +pub struct Uplete { + pub table: T, + pub update_values: U, + pub empty_row: E, +} + +impl + Table, U: QueryFragment, E: QueryFragment> QueryFragment for Uplete { + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { + out.push_sql("WITH update_result AS (UPDATE "); + self.update_values.walk_ast(out.reborrow())?; + self.push_sql(" RETURNING "); + self.table.all_columns().walk_ast(out.reborrow())?; + self.push_sql(") DELETE FROM "); + self.table.walk_ast(out.reborrow())?; + self.push_sql(" WHERE ("); + self.table.primary_key().walk_ast(out.reborrow())?; + self.push_sql(") = ANY (SELECT ("); + // In the parts below, `self.table` refers to `update_result` + self.table.primary_key().walk_ast(out.reborrow())?; + self.push_sql(") FROM update_result AS "); + self.table.walk_ast(out.reborrow())?; + self.push_sql(" WHERE ("); + self.table.all_columns().walk_ast(out.reborrow())?; + self.push_sql(") = ("); + self.empty_row.walk_ast(out.reborrow())?; + self.push_sql("))"); + } +} From bf541d7adb3664843a25bcbac4bc1cc75f3d1cb5 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 10:47:16 -0700 Subject: [PATCH 072/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 53 +++++++++++++++++++++------- 1 file changed, 40 insertions(+), 13 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index bd5205aa00..1d299f2571 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -1,8 +1,8 @@ use diesel::{ dsl, - expression::{is_aggregate, ValidGrouping}, + expression::{is_aggregate, NonAggregate, ValidGrouping}, pg::Pg, - query_builder::{AsQuery, AstPass, QueryFragment}, + query_builder::{AsQuery, AstPass, QueryFragment, UpdateStatement}, result::Error, sql_types, AppearsOnTable, @@ -13,30 +13,57 @@ use diesel::{ Table, }; +pub trait UpleteTable: Table + Default { + type EmptyRow: Default + AsExpression>; +} + +pub trait OrDelete { + type Output; + + fn or_delete() -> Self::Output; +} + +impl OrDelete for UpdateStatement { + type Output = SetOrDeleteQuery; + + fn or_delete() -> Self::Output { + SetOrDeleteQuery { + table: T::default(), + primary_key: T::primary_key(), + all_columns: T::all_columns(), + update_values: + } + } +} + +impl Uplete + #[derive(QueryId)] -pub struct Uplete { - pub table: T, - pub update_values: U, - pub empty_row: E, +struct SetOrDeleteQuery { + table: T, + primary_key: PK, + all_columns: C, + update_statement: U, + empty_row: E, } -impl + Table, U: QueryFragment, E: QueryFragment> QueryFragment for Uplete { +impl, PK: QueryFragment, C: QueryFragment, U: QueryFragment, E: QueryFragment> QueryFragment for SetOrDeleteQuery { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { - out.push_sql("WITH update_result AS (UPDATE "); - self.update_values.walk_ast(out.reborrow())?; + out.push_sql("WITH update_result AS ("); + self.update_statement.walk_ast(out.reborrow())?; self.push_sql(" RETURNING "); - self.table.all_columns().walk_ast(out.reborrow())?; + self.all_columns.walk_ast(out.reborrow())?; self.push_sql(") DELETE FROM "); self.table.walk_ast(out.reborrow())?; self.push_sql(" WHERE ("); - self.table.primary_key().walk_ast(out.reborrow())?; + self.primary_key.walk_ast(out.reborrow())?; self.push_sql(") = ANY (SELECT ("); // In the parts below, `self.table` refers to `update_result` - self.table.primary_key().walk_ast(out.reborrow())?; + self.primary_key.walk_ast(out.reborrow())?; self.push_sql(") FROM update_result AS "); self.table.walk_ast(out.reborrow())?; self.push_sql(" WHERE ("); - self.table.all_columns().walk_ast(out.reborrow())?; + self.all_columns.walk_ast(out.reborrow())?; self.push_sql(") = ("); self.empty_row.walk_ast(out.reborrow())?; self.push_sql("))"); From 43813cfcd76b57163ff326aed4ff4967a81b7c74 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 10:57:47 -0700 Subject: [PATCH 073/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 1d299f2571..abc34f4926 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -20,26 +20,25 @@ pub trait UpleteTable: Table + Default { pub trait OrDelete { type Output; - fn or_delete() -> Self::Output; + fn or_delete(self) -> Self::Output; } impl OrDelete for UpdateStatement { - type Output = SetOrDeleteQuery; + type Output = SetOrDeleteQuery>; - fn or_delete() -> Self::Output { + fn or_delete(self) -> Self::Output { SetOrDeleteQuery { table: T::default(), primary_key: T::primary_key(), all_columns: T::all_columns(), - update_values: + update_statement: self, + empty_row: T::EmptyRow::default().as_expression(); } } } -impl Uplete - #[derive(QueryId)] -struct SetOrDeleteQuery { +pub struct SetOrDeleteQuery { table: T, primary_key: PK, all_columns: C, @@ -49,23 +48,31 @@ struct SetOrDeleteQuery { impl, PK: QueryFragment, C: QueryFragment, U: QueryFragment, E: QueryFragment> QueryFragment for SetOrDeleteQuery { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { + // `update_result` CTE with new rows out.push_sql("WITH update_result AS ("); self.update_statement.walk_ast(out.reborrow())?; self.push_sql(" RETURNING "); self.all_columns.walk_ast(out.reborrow())?; + + // Delete self.push_sql(") DELETE FROM "); self.table.walk_ast(out.reborrow())?; self.push_sql(" WHERE ("); self.primary_key.walk_ast(out.reborrow())?; + + // Select from `update_result` with an alias that matches the original table's name self.push_sql(") = ANY (SELECT ("); - // In the parts below, `self.table` refers to `update_result` self.primary_key.walk_ast(out.reborrow())?; self.push_sql(") FROM update_result AS "); self.table.walk_ast(out.reborrow())?; + + // Filter the select statement self.push_sql(" WHERE ("); self.all_columns.walk_ast(out.reborrow())?; - self.push_sql(") = ("); + self.push_sql(") IS NOT DISTINCT FROM ("); self.empty_row.walk_ast(out.reborrow())?; self.push_sql("))"); + + Ok(()) } } From 3ae5eef294b0e8a2153ee18d984c4537d919588f Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 11:02:26 -0700 Subject: [PATCH 074/241] Update up.sql --- .../up.sql | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index 73fcc55bd4..7a21d6a050 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -386,39 +386,3 @@ FROM post_actions; CREATE statistics post_actions_liked_stat ON (liked IS NULL), (like_score IS NULL) FROM post_actions; --- TODO: move to replaceable_schema -/*CREATE SCHEMA IF NOT EXISTS r; - -CREATE FUNCTION r.delete_empty_comment_actions () - RETURNS TRIGGER - LANGUAGE plpgsql - AS $$ -BEGIN - DELETE FROM comment_actions USING ( - SELECT - (person_id, - comment_id, - post_id, - NULL, - NULL, - NULL) AS row_with_nulls - FROM - new_row - WHERE (new_row.*) = row_with_nulls) AS rows_to_check -WHERE (comment_actions.*) = row_with_nulls -END; -$$; -uplete::delete_empty(comment_actions::table, (comment_actions::person_id, comment_actions::comment_id, comment_actions::post_id, ))*/ - -/*WITH - update_result AS (UPDATE post_actions SET like_score = NULL, liked = NULL RETURNING *) -DELETE FROM post_actions -USING ( - -- I tried to put these selected values in a tuple with a name that can be referenced in the `WHERE` clause, but the name throws a parse error in the `WHERE` clause - SELECT post_actions.post_id, post_actions.person_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL - FROM update_result AS post_actions - -- This check only affects performance, and it can't replace the `(post_actions.*) = (rows_with_nulls.*)` check below because this one is done before locking the rows - WHERE (post_actions.*) = (post_actions.post_id, post_actions.person_id, NULL, NULL, NULL, NULL, NULL, NULL, NULL) -) AS rows_with_nulls -WHERE (post_actions.*) = (rows_with_nulls.*);*/ - From 10b30095395e5f7043f3b35ee65f2fd879cd6a6f Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 11:27:26 -0700 Subject: [PATCH 075/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 5cca0de23a..001e4068bf 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -12,7 +12,15 @@ use crate::{ CommentUpdateForm, }, traits::{Crud, Likeable, Saveable}, - utils::{functions::coalesce, get_conn, naive_now, now, DbPool, DELETED_REPLACEMENT_TEXT}, + utils::{ + functions::coalesce, + get_conn, + naive_now, + now, + uplete::{OrDelete, UpleteTable}, + DbPool, + DELETED_REPLACEMENT_TEXT, + }, }; use chrono::{DateTime, Utc}; use diesel::{ @@ -26,6 +34,17 @@ use diesel_async::RunQueryDsl; use diesel_ltree::Ltree; use url::Url; +impl UpleteTable for comment_actions::table { + type EmptyRow = ( + comment_actions::person_id, + comment_actions::comment_id, + comment_actions::post_id, + None::, + None::, + None::, + ); +} + impl Comment { pub async fn permadelete_for_creator( pool: &mut DbPool<'_>, @@ -189,6 +208,7 @@ impl Likeable for CommentLike { comment_actions::like_score.eq(None::), comment_actions::liked.eq(None::>), )) + .or_delete() .execute(conn) .await } @@ -238,6 +258,7 @@ impl Saveable for CommentSaved { comment_actions::table.find((comment_saved_form.person_id, comment_saved_form.comment_id)), ) .set(comment_actions::saved.eq(None::>)) + .or_delete() .execute(conn) .await } From 59f41b20acf2a73bd625f1eea1c7ddee34cab37f Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 11:39:09 -0700 Subject: [PATCH 076/241] Update Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index f1809592d4..c95e7350cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -167,7 +167,7 @@ moka = { version = "0.12.7", features = ["future"] } i-love-jesus = { version = "0.1.0" } clap = { version = "4.5.4", features = ["derive", "env"] } pretty_assertions = "1.4.0" -diesel-bind-if-some = { path = "../diesel-utils/crates/diesel-bind-if-some" } +diesel-bind-if-some = "0.1.0" [dependencies] lemmy_api = { workspace = true } From 3dc483c70816c2078bfed304790fb011f30218a5 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 11:41:35 -0700 Subject: [PATCH 077/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 001e4068bf..a500e4f816 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -39,9 +39,9 @@ impl UpleteTable for comment_actions::table { comment_actions::person_id, comment_actions::comment_id, comment_actions::post_id, - None::, - None::, - None::, + None, + None, + None, ); } From 0174029794de2f770aa53ec766275916aad02f3c Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 11:42:42 -0700 Subject: [PATCH 078/241] Update post.rs --- crates/db_schema/src/impls/post.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 2b7046114e..a3b389a51a 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -246,8 +246,8 @@ impl Post { } } -// TODO: replace `as_select_unwrap` definitions with `Default::default()` after next Diesel release which should -// implement `Default` for `AssumeNotNull` +// TODO: replace `as_select_unwrap` definitions with `Default::default()` after next Diesel release +// which should implement `Default` for `AssumeNotNull` impl PostLike { fn as_select_unwrap() -> ( post_actions::post_id, From 2b584e25f553610ea6cc17e6af18efc54d787948 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 11:44:25 -0700 Subject: [PATCH 079/241] Update comment_view.rs --- crates/db_views/src/comment_view.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 46c1d053a3..8b4a6f2a04 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -174,9 +174,13 @@ fn queries<'a>() -> Queries< if let Some(my_id) = my_person_id { let not_creator_filter = comment::creator_id.ne(my_id); if options.liked_only { - query = query.filter(not_creator_filter).filter(comment_actions::like_score.eq(1)); + query = query + .filter(not_creator_filter) + .filter(comment_actions::like_score.eq(1)); } else if options.disliked_only { - query = query.filter(not_creator_filter).filter(comment_actions::like_score.eq(-1)); + query = query + .filter(not_creator_filter) + .filter(comment_actions::like_score.eq(-1)); } } From c4fba2840f3d95c4de970709cfb9772884202946 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 11:45:11 -0700 Subject: [PATCH 080/241] Update post_view.rs --- crates/db_views/src/post_view.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 52c27499ef..713e8392d9 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -283,9 +283,13 @@ fn queries<'a>() -> Queries< if let Some(my_id) = my_person_id { let not_creator_filter = post_aggregates::creator_id.ne(my_id); if options.liked_only { - query = query.filter(not_creator_filter).filter(post_actions::like_score.eq(1)); + query = query + .filter(not_creator_filter) + .filter(post_actions::like_score.eq(1)); } else if options.disliked_only { - query = query.filter(not_creator_filter).filter(post_actions::like_score.eq(-1)); + query = query + .filter(not_creator_filter) + .filter(post_actions::like_score.eq(-1)); } }; From 2de949985820e8aab3fe0fc97d6269b89d90781d Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 11:46:16 -0700 Subject: [PATCH 081/241] Update comment_reply_view.rs --- crates/db_views_actor/src/comment_reply_view.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index f2de64b6b3..86c5a0e91f 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -1,5 +1,12 @@ use crate::structs::CommentReplyView; -use diesel::{dsl::not, pg::Pg, result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl}; +use diesel::{ + dsl::not, + pg::Pg, + result::Error, + ExpressionMethods, + NullableExpressionMethods, + QueryDsl, +}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, From ed7fcc620d29f12a1b913d2ad37d680c853f3a94 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 12:05:56 -0700 Subject: [PATCH 082/241] Update person_mention_view.rs --- crates/db_views_actor/src/person_mention_view.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index 071b5ccba8..eca95f30c1 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -1,5 +1,12 @@ use crate::structs::PersonMentionView; -use diesel::{dsl::not, pg::Pg, result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl}; +use diesel::{ + dsl::not, + pg::Pg, + result::Error, + ExpressionMethods, + NullableExpressionMethods, + QueryDsl, +}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, From db475a9b263aef7cad23343973f3278c36ebdb00 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 12:10:49 -0700 Subject: [PATCH 083/241] Update Cargo.toml --- crates/db_schema/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index 15df6e39ab..5adc04dc1f 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -38,6 +38,7 @@ full = [ "tokio-postgres-rustls", "rustls", "i-love-jesus", + "diesel-bind-if-some", ] [dependencies] @@ -80,7 +81,7 @@ rustls = { workspace = true, optional = true } uuid = { workspace = true, features = ["v4"] } i-love-jesus = { workspace = true, optional = true } anyhow = { workspace = true } -diesel-bind-if-some = { workspace = true } +diesel-bind-if-some = { workspace = true, optional = true } moka.workspace = true [dev-dependencies] From 4f70eb18f5519f93cd4b075325185f3f2672aad0 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 12:16:20 -0700 Subject: [PATCH 084/241] Update utils.rs --- crates/db_schema/src/utils.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index da9c1129d3..f781760361 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -1,3 +1,5 @@ +pub mod uplete; + use crate::{newtypes::DbUrl, CommentSortType, SortType}; use chrono::{DateTime, TimeDelta, Utc}; use deadpool::Runtime; From 7de09157453d516da129fc4da2777d3182955f2b Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 12:17:47 -0700 Subject: [PATCH 085/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index a500e4f816..7dce1d02ec 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -24,7 +24,7 @@ use crate::{ }; use chrono::{DateTime, Utc}; use diesel::{ - dsl::{self, insert_into, sql_query}, + dsl::{self, insert_into}, result::Error, ExpressionMethods, NullableExpressionMethods, @@ -39,9 +39,9 @@ impl UpleteTable for comment_actions::table { comment_actions::person_id, comment_actions::comment_id, comment_actions::post_id, - None, - None, - None, + Option, + Option, + Option, ); } From 3d5140e0a78682bb26a28b9918d417f6b391e4e5 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 12:19:41 -0700 Subject: [PATCH 086/241] Update utils.rs --- crates/db_schema/src/utils.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index f781760361..c44f4576e3 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -24,7 +24,6 @@ use diesel::{ JoinOnDsl, NullableExpressionMethods, OptionalExtension, - PgConnection, QuerySource, Table, }; From 88a7ab596eb0c86ecfd49137a80c00c63b2d68f3 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 12:24:39 -0700 Subject: [PATCH 087/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index abc34f4926..92d7e15912 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -32,7 +32,7 @@ impl OrDelete for UpdateStatement { primary_key: T::primary_key(), all_columns: T::all_columns(), update_statement: self, - empty_row: T::EmptyRow::default().as_expression(); + empty_row: T::EmptyRow::default().as_expression(), } } } @@ -46,7 +46,13 @@ pub struct SetOrDeleteQuery { empty_row: E, } -impl, PK: QueryFragment, C: QueryFragment, U: QueryFragment, E: QueryFragment> QueryFragment for SetOrDeleteQuery { +impl< + T: QueryFragment, + PK: QueryFragment, + C: QueryFragment, + U: QueryFragment, + E: QueryFragment, +> QueryFragment for SetOrDeleteQuery { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { // `update_result` CTE with new rows out.push_sql("WITH update_result AS ("); From dacdda2a921dad72caeaeb90e023c113b704a302 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 12:29:14 -0700 Subject: [PATCH 088/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 92d7e15912..af635939ee 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -24,7 +24,8 @@ pub trait OrDelete { } impl OrDelete for UpdateStatement { - type Output = SetOrDeleteQuery>; + type Output = + SetOrDeleteQuery>; fn or_delete(self) -> Self::Output { SetOrDeleteQuery { @@ -47,12 +48,13 @@ pub struct SetOrDeleteQuery { } impl< - T: QueryFragment, - PK: QueryFragment, - C: QueryFragment, - U: QueryFragment, - E: QueryFragment, -> QueryFragment for SetOrDeleteQuery { + T: QueryFragment, + PK: QueryFragment, + C: QueryFragment, + U: QueryFragment, + E: QueryFragment, + > QueryFragment for SetOrDeleteQuery +{ fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { // `update_result` CTE with new rows out.push_sql("WITH update_result AS ("); From 0b1132d6458b6167e3c44e9808e506f5a1f607b8 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 13:05:07 -0700 Subject: [PATCH 089/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index af635939ee..858b03e1d1 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -1,15 +1,11 @@ use diesel::{ dsl, - expression::{is_aggregate, NonAggregate, ValidGrouping}, + expression::AsExpression, pg::Pg, query_builder::{AsQuery, AstPass, QueryFragment, UpdateStatement}, result::Error, sql_types, - AppearsOnTable, - Expression, - Insertable, QueryId, - SelectableExpression, Table, }; @@ -53,7 +49,7 @@ impl< C: QueryFragment, U: QueryFragment, E: QueryFragment, - > QueryFragment for SetOrDeleteQuery + > QueryFragment for SetOrDeleteQuery { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { // `update_result` CTE with new rows From 235e5471702faa7ae4c18b735c6f6c18585a693b Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 13:05:43 -0700 Subject: [PATCH 090/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 7dce1d02ec..b1c6bf9ca0 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -40,8 +40,8 @@ impl UpleteTable for comment_actions::table { comment_actions::comment_id, comment_actions::post_id, Option, - Option, - Option, + Option>, + Option>, ); } From 3980cf1aec773c38a00b9388f45bbd0265c2de44 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 13:21:32 -0700 Subject: [PATCH 091/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 858b03e1d1..9d36ed864e 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -2,7 +2,7 @@ use diesel::{ dsl, expression::AsExpression, pg::Pg, - query_builder::{AsQuery, AstPass, QueryFragment, UpdateStatement}, + query_builder::{AstPass, QueryFragment, UpdateStatement}, result::Error, sql_types, QueryId, @@ -10,7 +10,7 @@ use diesel::{ }; pub trait UpleteTable: Table + Default { - type EmptyRow: Default + AsExpression>; + type EmptyRow; } pub trait OrDelete { @@ -19,7 +19,10 @@ pub trait OrDelete { fn or_delete(self) -> Self::Output; } -impl OrDelete for UpdateStatement { +impl OrDelete for UpdateStatement +where + T::EmptyRow: Default + AsExpression>, +{ type Output = SetOrDeleteQuery>; From 9d5f9caec05785e687671e0fd8cb19c066264f5f Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 13:36:39 -0700 Subject: [PATCH 092/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 9d36ed864e..18ca134ba3 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -1,6 +1,6 @@ use diesel::{ dsl, - expression::AsExpression, + expression::{AsExpression, SqlType, TypedExpressionType}, pg::Pg, query_builder::{AstPass, QueryFragment, UpdateStatement}, result::Error, @@ -21,6 +21,7 @@ pub trait OrDelete { impl OrDelete for UpdateStatement where + T::SqlType: SqlType + TypedExpressionType, T::EmptyRow: Default + AsExpression>, { type Output = From 237a38a1285a539498e36de54e07254882693717 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 13:53:59 -0700 Subject: [PATCH 093/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 18ca134ba3..7f9c07de8a 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -1,6 +1,6 @@ use diesel::{ dsl, - expression::{AsExpression, SqlType, TypedExpressionType}, + expression::{AsExpression, TypedExpressionType}, pg::Pg, query_builder::{AstPass, QueryFragment, UpdateStatement}, result::Error, @@ -21,7 +21,7 @@ pub trait OrDelete { impl OrDelete for UpdateStatement where - T::SqlType: SqlType + TypedExpressionType, + T::SqlType: sql_types::SqlType + TypedExpressionType, T::EmptyRow: Default + AsExpression>, { type Output = From df2d77693014d9cc1f9a1c8be2f4c46a2b0da8e4 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 14:11:09 -0700 Subject: [PATCH 094/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 7f9c07de8a..4ceb8feffa 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -25,7 +25,7 @@ where T::EmptyRow: Default + AsExpression>, { type Output = - SetOrDeleteQuery>; + SetOrDeleteQuery>>; fn or_delete(self) -> Self::Output { SetOrDeleteQuery { @@ -77,9 +77,9 @@ impl< // Filter the select statement self.push_sql(" WHERE ("); self.all_columns.walk_ast(out.reborrow())?; - self.push_sql(") IS NOT DISTINCT FROM ("); + self.push_sql(") IS NOT DISTINCT FROM "); self.empty_row.walk_ast(out.reborrow())?; - self.push_sql("))"); + self.push_sql(")"); Ok(()) } From e1aaf802ad41cbcaba01063a19a0d7df8ca4fd68 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 14:21:19 -0700 Subject: [PATCH 095/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 4ceb8feffa..41c86fe034 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -24,8 +24,13 @@ where T::SqlType: sql_types::SqlType + TypedExpressionType, T::EmptyRow: Default + AsExpression>, { - type Output = - SetOrDeleteQuery>>; + type Output = SetOrDeleteQuery< + T, + T::PrimaryKey, + T::AllColumns, + Self, + dsl::AsExprOf>, + >; fn or_delete(self) -> Self::Output { SetOrDeleteQuery { From d68b3ad41f72bd9e12aedb679606efdb7b093a7c Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 14:47:16 -0700 Subject: [PATCH 096/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 41c86fe034..f199840c7f 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -35,7 +35,7 @@ where fn or_delete(self) -> Self::Output { SetOrDeleteQuery { table: T::default(), - primary_key: T::primary_key(), + primary_key: T::default().primary_key(), all_columns: T::all_columns(), update_statement: self, empty_row: T::EmptyRow::default().as_expression(), @@ -64,27 +64,27 @@ impl< // `update_result` CTE with new rows out.push_sql("WITH update_result AS ("); self.update_statement.walk_ast(out.reborrow())?; - self.push_sql(" RETURNING "); + out.push_sql(" RETURNING "); self.all_columns.walk_ast(out.reborrow())?; // Delete - self.push_sql(") DELETE FROM "); + out.push_sql(") DELETE FROM "); self.table.walk_ast(out.reborrow())?; - self.push_sql(" WHERE ("); + out.push_sql(" WHERE ("); self.primary_key.walk_ast(out.reborrow())?; // Select from `update_result` with an alias that matches the original table's name - self.push_sql(") = ANY (SELECT ("); + out.push_sql(") = ANY (SELECT ("); self.primary_key.walk_ast(out.reborrow())?; - self.push_sql(") FROM update_result AS "); + out.push_sql(") FROM update_result AS "); self.table.walk_ast(out.reborrow())?; // Filter the select statement - self.push_sql(" WHERE ("); + out.push_sql(" WHERE ("); self.all_columns.walk_ast(out.reborrow())?; - self.push_sql(") IS NOT DISTINCT FROM "); + out.push_sql(") IS NOT DISTINCT FROM "); self.empty_row.walk_ast(out.reborrow())?; - self.push_sql(")"); + out.push_sql(")"); Ok(()) } From 0945a760950f41abdf2f30e54d6f1e459189bf9f Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 15:13:18 -0700 Subject: [PATCH 097/241] Update comment_view.rs --- crates/db_views/src/comment_view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 8b4a6f2a04..4a67ef8a2a 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -1,6 +1,6 @@ use crate::structs::{CommentView, LocalUserView}; use diesel::{ - dsl::exists, + dsl::{exists, not}, pg::Pg, result::Error, BoolExpressionMethods, From efc47c9bd8c6be5069a90485b27acc1f72a10769 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 15:13:39 -0700 Subject: [PATCH 098/241] Update post_view.rs --- crates/db_views/src/post_view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 713e8392d9..1cf1aad1da 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -1,7 +1,7 @@ use crate::structs::{LocalUserView, PaginationCursor, PostView}; use diesel::{ debug_query, - dsl::{exists, IntervalDsl}, + dsl::{exists, not, IntervalDsl}, pg::Pg, query_builder::AsQuery, result::Error, From beb3b9f5d95450c042215d6ca86b3060db974768 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 18:24:46 -0700 Subject: [PATCH 099/241] Update triggers.sql --- crates/db_schema/replaceable_schema/triggers.sql | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/db_schema/replaceable_schema/triggers.sql b/crates/db_schema/replaceable_schema/triggers.sql index fa5b010189..1ab62a66f9 100644 --- a/crates/db_schema/replaceable_schema/triggers.sql +++ b/crates/db_schema/replaceable_schema/triggers.sql @@ -33,7 +33,7 @@ AS $a$ BEGIN EXECUTE replace($b$ -- When a thing gets a vote, update its aggregates and its creator's aggregates - CALL r.create_triggers ('thing_like', $$ + CALL r.create_triggers ('thing_actions', $$ BEGIN WITH thing_diff AS ( UPDATE thing_aggregates AS a @@ -41,7 +41,7 @@ BEGIN score = a.score + diff.upvotes - diff.downvotes, upvotes = a.upvotes + diff.upvotes, downvotes = a.downvotes + diff.downvotes, controversy_rank = r.controversy_rank ((a.upvotes + diff.upvotes)::numeric, (a.downvotes + diff.downvotes)::numeric) FROM ( SELECT - (thing_like).thing_id, coalesce(sum(count_diff) FILTER (WHERE (thing_like).score = 1), 0) AS upvotes, coalesce(sum(count_diff) FILTER (WHERE (thing_like).score != 1), 0) AS downvotes FROM select_old_and_new_rows AS old_and_new_rows GROUP BY (thing_like).thing_id) AS diff + (thing_actions).thing_id, coalesce(sum(count_diff) FILTER (WHERE (thing_actions).like_score = 1), 0) AS upvotes, coalesce(sum(count_diff) FILTER (WHERE (thing_actions).like_score != 1), 0) AS downvotes FROM select_old_and_new_rows AS old_and_new_rows WHERE (thing_actions).like_score IS NOT NULL GROUP BY (thing_actions).thing_id) AS diff WHERE a.thing_id = diff.thing_id AND (diff.upvotes, diff.downvotes) != (0, 0) @@ -355,7 +355,7 @@ CREATE TRIGGER comment_count -- Count subscribers for communities. -- subscribers should be updated only when a local community is followed by a local or remote person. -- subscribers_local should be updated only when a local person follows a local or remote community. -CALL r.create_triggers ('community_follower', $$ +CALL r.create_triggers ('community_actions', $$ BEGIN UPDATE community_aggregates AS a @@ -363,10 +363,10 @@ BEGIN subscribers = a.subscribers + diff.subscribers, subscribers_local = a.subscribers_local + diff.subscribers_local FROM ( SELECT - (community_follower).community_id, coalesce(sum(count_diff) FILTER (WHERE community.local), 0) AS subscribers, coalesce(sum(count_diff) FILTER (WHERE person.local), 0) AS subscribers_local + (community_actions).community_id, coalesce(sum(count_diff) FILTER (WHERE community.local), 0) AS subscribers, coalesce(sum(count_diff) FILTER (WHERE person.local), 0) AS subscribers_local FROM select_old_and_new_rows AS old_and_new_rows - LEFT JOIN community ON community.id = (community_follower).community_id - LEFT JOIN person ON person.id = (community_follower).person_id GROUP BY (community_follower).community_id) AS diff + LEFT JOIN community ON community.id = (community_actions).community_id + LEFT JOIN person ON person.id = (community_actions).person_id WHERE (community_actions).followed IS NOT NULL GROUP BY (community_actions).community_id) AS diff WHERE a.community_id = diff.community_id AND (diff.subscribers, diff.subscribers_local) != (0, 0); From a8215385544c0640b5137ffd11ff8572cd6dd89e Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 18:37:54 -0700 Subject: [PATCH 100/241] Update triggers.sql --- crates/db_schema/replaceable_schema/triggers.sql | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/replaceable_schema/triggers.sql b/crates/db_schema/replaceable_schema/triggers.sql index 1ab62a66f9..033b33ddff 100644 --- a/crates/db_schema/replaceable_schema/triggers.sql +++ b/crates/db_schema/replaceable_schema/triggers.sql @@ -41,7 +41,8 @@ BEGIN score = a.score + diff.upvotes - diff.downvotes, upvotes = a.upvotes + diff.upvotes, downvotes = a.downvotes + diff.downvotes, controversy_rank = r.controversy_rank ((a.upvotes + diff.upvotes)::numeric, (a.downvotes + diff.downvotes)::numeric) FROM ( SELECT - (thing_actions).thing_id, coalesce(sum(count_diff) FILTER (WHERE (thing_actions).like_score = 1), 0) AS upvotes, coalesce(sum(count_diff) FILTER (WHERE (thing_actions).like_score != 1), 0) AS downvotes FROM select_old_and_new_rows AS old_and_new_rows WHERE (thing_actions).like_score IS NOT NULL GROUP BY (thing_actions).thing_id) AS diff + (thing_actions).thing_id, coalesce(sum(count_diff) FILTER (WHERE (thing_actions).like_score = 1), 0) AS upvotes, coalesce(sum(count_diff) FILTER (WHERE (thing_actions).like_score != 1), 0) AS downvotes FROM select_old_and_new_rows AS old_and_new_rows + WHERE (thing_actions).like_score IS NOT NULL GROUP BY (thing_actions).thing_id) AS diff WHERE a.thing_id = diff.thing_id AND (diff.upvotes, diff.downvotes) != (0, 0) @@ -366,7 +367,8 @@ BEGIN (community_actions).community_id, coalesce(sum(count_diff) FILTER (WHERE community.local), 0) AS subscribers, coalesce(sum(count_diff) FILTER (WHERE person.local), 0) AS subscribers_local FROM select_old_and_new_rows AS old_and_new_rows LEFT JOIN community ON community.id = (community_actions).community_id - LEFT JOIN person ON person.id = (community_actions).person_id WHERE (community_actions).followed IS NOT NULL GROUP BY (community_actions).community_id) AS diff + LEFT JOIN person ON person.id = (community_actions).person_id + WHERE (community_actions).followed IS NOT NULL GROUP BY (community_actions).community_id) AS diff WHERE a.community_id = diff.community_id AND (diff.subscribers, diff.subscribers_local) != (0, 0); From 6bbe0a939a7ead2661d2fb21e15a0a53f5890a32 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 18:46:39 -0700 Subject: [PATCH 101/241] Update triggers.sql --- crates/db_schema/replaceable_schema/triggers.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/db_schema/replaceable_schema/triggers.sql b/crates/db_schema/replaceable_schema/triggers.sql index 033b33ddff..ada6f124cb 100644 --- a/crates/db_schema/replaceable_schema/triggers.sql +++ b/crates/db_schema/replaceable_schema/triggers.sql @@ -533,21 +533,21 @@ CREATE TRIGGER delete_comments FOR EACH ROW EXECUTE FUNCTION r.delete_comments_before_post (); -CREATE FUNCTION r.delete_follow_before_person () +CREATE FUNCTION r.delete_community_actions_before_person () RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN - DELETE FROM community_follower AS c + DELETE FROM community_actions AS c WHERE c.person_id = OLD.id; RETURN OLD; END; $$; -CREATE TRIGGER delete_follow +CREATE TRIGGER delete_community_actions BEFORE DELETE ON person FOR EACH ROW - EXECUTE FUNCTION r.delete_follow_before_person (); + EXECUTE FUNCTION r.delete_community_actions_before_person (); -- Triggers that change values before insert or update CREATE FUNCTION r.comment_change_values () From eccd72207c90e2e1bc82894a7cf1687589195829 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 19:25:29 -0700 Subject: [PATCH 102/241] Update comment_reply_view.rs --- crates/db_views_actor/src/comment_reply_view.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index 86c5a0e91f..83cf9f290f 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -1,6 +1,5 @@ use crate::structs::CommentReplyView; use diesel::{ - dsl::not, pg::Pg, result::Error, ExpressionMethods, @@ -131,9 +130,7 @@ fn queries<'a>() -> Queries< }; // Don't show replies from blocked persons - if let Some(my_person_id) = options.my_person_id { - query = query.filter(not(is_creator_blocked(my_person_id))); - } + query = query.filter(person_actions::blocked.is_null()); let (limit, offset) = limit_and_offset(options.page, options.limit)?; From 5bc8e792621aebd078512825b12649ab73079242 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 19:29:44 -0700 Subject: [PATCH 103/241] Update person_mention_view.rs --- .../db_views_actor/src/person_mention_view.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index eca95f30c1..b9c390d374 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -1,6 +1,5 @@ use crate::structs::PersonMentionView; use diesel::{ - dsl::not, pg::Pg, result::Error, ExpressionMethods, @@ -131,9 +130,7 @@ fn queries<'a>() -> Queries< }; // Don't show mentions from blocked persons - if let Some(my_person_id) = options.my_person_id { - query = query.filter(not(is_creator_blocked(my_person_id))); - } + query = query.filter(person_actions::blocked.is_null()); let (limit, offset) = limit_and_offset(options.page, options.limit)?; @@ -168,15 +165,13 @@ impl PersonMentionView { person_mention::table .inner_join(comment::table) - .left_join( - person_block::table.on( - comment::creator_id - .eq(person_block::target_id) - .and(person_block::person_id.eq(my_person_id)), - ), - ) + .left_join(actions( + person_actions::table, + Some(my_person_id), + comment::creator_id, + )) // Dont count replies from blocked users - .filter(person_block::person_id.is_null()) + .filter(person_actions::blocked.is_null()) .filter(person_mention::recipient_id.eq(my_person_id)) .filter(person_mention::read.eq(false)) .filter(comment::deleted.eq(false)) From 4ae63c6ce251cb7593cc056b5b15fcd50cbef970 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 19:37:02 -0700 Subject: [PATCH 104/241] Update person_mention_view.rs --- crates/db_views_actor/src/person_mention_view.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index b9c390d374..3a39943ae3 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -1,11 +1,5 @@ use crate::structs::PersonMentionView; -use diesel::{ - pg::Pg, - result::Error, - ExpressionMethods, - NullableExpressionMethods, - QueryDsl, -}; +use diesel::{pg::Pg, result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, From 66628717a42aa70a3c1c6c6fabafacfdbe5f5a4a Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 19:37:37 -0700 Subject: [PATCH 105/241] Update comment_reply_view.rs --- crates/db_views_actor/src/comment_reply_view.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index 83cf9f290f..7c9bf14681 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -1,11 +1,5 @@ use crate::structs::CommentReplyView; -use diesel::{ - pg::Pg, - result::Error, - ExpressionMethods, - NullableExpressionMethods, - QueryDsl, -}; +use diesel::{pg::Pg, result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, From c73ab7ff4d71a9e1a13d78911f6282d6b87b4090 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 20:05:37 -0700 Subject: [PATCH 106/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index f199840c7f..2ae8c3fe5d 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -74,9 +74,9 @@ impl< self.primary_key.walk_ast(out.reborrow())?; // Select from `update_result` with an alias that matches the original table's name - out.push_sql(") = ANY (SELECT ("); + out.push_sql(") = ANY (SELECT "); self.primary_key.walk_ast(out.reborrow())?; - out.push_sql(") FROM update_result AS "); + out.push_sql(" FROM update_result AS "); self.table.walk_ast(out.reborrow())?; // Filter the select statement From 34cba5be3994107c1e66a958ef267bf23d41ab12 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 20:08:00 -0700 Subject: [PATCH 107/241] start removing post_id column --- migrations/2024-03-17-134311_smoosh-tables-together/up.sql | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index 7a21d6a050..5c4d9ab08b 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -386,3 +386,7 @@ FROM post_actions; CREATE statistics post_actions_liked_stat ON (liked IS NULL), (like_score IS NULL) FROM post_actions; +-- TODO: drop the column +ALTER TABLE comment_actions + ALTER COLUMN post_id DROP NOT NULL; + From 4771e6992af1b0458ccd9d5d080a0d3b1a02dea6 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 20:08:31 -0700 Subject: [PATCH 108/241] Update down.sql --- migrations/2024-03-17-134311_smoosh-tables-together/down.sql | 3 +++ 1 file changed, 3 insertions(+) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql index 685cd39d68..3e1cde50be 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql @@ -329,3 +329,6 @@ DROP INDEX idx_comment_actions_liked_not_null, idx_community_actions_followed_no -- Drop statistics of columns that still exist DROP statistics comment_actions_liked_stat, community_actions_followed_stat, person_actions_followed_stat; +ALTER TABLE comment_actions + ALTER COLUMN post_id SET NOT NULL; + From 9dd9057fbf4d19429c244db1855a24a253ba4690 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 20:09:15 -0700 Subject: [PATCH 109/241] Update schema.rs --- crates/db_schema/src/schema.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 6af8b0b98a..1c6652ee60 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -102,7 +102,7 @@ diesel::table! { comment_actions (person_id, comment_id) { person_id -> Int4, comment_id -> Int4, - post_id -> Int4, + post_id -> Nullable, like_score -> Nullable, liked -> Nullable, saved -> Nullable, From 2935ff4f6e3e59b207da74e6b2499079d30d0e4c Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 20:10:09 -0700 Subject: [PATCH 110/241] Update comment.rs --- crates/db_schema/src/source/comment.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index d5568dd08d..194fcff17a 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -96,7 +96,6 @@ pub struct CommentUpdateForm { pub struct CommentLike { pub person_id: PersonId, pub comment_id: CommentId, - pub post_id: PostId, // TODO this is redundant pub score: i16, pub published: DateTime, } From 7c00bcc8a535a0ab75b57859f2be225b04d117fa Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 24 May 2024 20:11:40 -0700 Subject: [PATCH 111/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index b1c6bf9ca0..482ba9fe4d 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -375,7 +375,6 @@ mod tests { let expected_comment_like = CommentLike { comment_id: inserted_comment.id, - post_id: inserted_post.id, person_id: inserted_person.id, published: inserted_comment_like.published, score: 1, From 1cd396930c6726c540a11f25a6917ebd974eb658 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 08:01:46 -0700 Subject: [PATCH 112/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 482ba9fe4d..7e897535ae 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -164,14 +164,12 @@ impl CommentLike { fn as_select_unwrap() -> ( comment_actions::person_id, comment_actions::comment_id, - comment_actions::post_id, dsl::AssumeNotNull, dsl::AssumeNotNull, ) { ( comment_actions::person_id, comment_actions::comment_id, - comment_actions::post_id, comment_actions::like_score.assume_not_null(), comment_actions::liked.assume_not_null(), ) From d5740bc02148e52c52578ffc751dfa77dbca24f6 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 08:29:14 -0700 Subject: [PATCH 113/241] Update schema.rs --- crates/db_schema/src/schema.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 1c6652ee60..2ed170b0e3 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -102,7 +102,6 @@ diesel::table! { comment_actions (person_id, comment_id) { person_id -> Int4, comment_id -> Int4, - post_id -> Nullable, like_score -> Nullable, liked -> Nullable, saved -> Nullable, From bfb5698375eeb982e137f88886e5852277196e71 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 08:30:06 -0700 Subject: [PATCH 114/241] Update comment.rs --- crates/db_schema/src/source/comment.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index 194fcff17a..2f235932ee 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -106,7 +106,6 @@ pub struct CommentLike { pub struct CommentLikeForm { pub person_id: PersonId, pub comment_id: CommentId, - pub post_id: PostId, // TODO this is redundant #[cfg_attr(feature = "full", diesel(column_name = like_score))] pub score: i16, } From b7a904b765ef12576783c9ca4da78e5757a4f41c Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 08:32:25 -0700 Subject: [PATCH 115/241] Update like.rs --- crates/api/src/comment/like.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/api/src/comment/like.rs b/crates/api/src/comment/like.rs index d0aa4a6c26..aa76b91e68 100644 --- a/crates/api/src/comment/like.rs +++ b/crates/api/src/comment/like.rs @@ -59,7 +59,6 @@ pub async fn like_comment( let like_form = CommentLikeForm { comment_id: data.comment_id, - post_id: orig_comment.post.id, person_id: local_user_view.person.id, score: data.score, }; From 71b0fe7a41bc9ed0b031e254e4c998ac53c92f47 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 08:34:02 -0700 Subject: [PATCH 116/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 7e897535ae..6f86f9dd53 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -364,7 +364,6 @@ mod tests { // Comment Like let comment_like_form = CommentLikeForm { comment_id: inserted_comment.id, - post_id: inserted_post.id, person_id: inserted_person.id, score: 1, }; From 67eadceac2c0a711f4864344a9d281f0aef68ac3 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 08:35:42 -0700 Subject: [PATCH 117/241] Update up.sql --- migrations/2024-03-17-134311_smoosh-tables-together/up.sql | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index 5c4d9ab08b..753c18dec7 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -11,6 +11,7 @@ ALTER TABLE comment_actions RENAME COLUMN published TO liked; ALTER TABLE comment_actions RENAME COLUMN score TO like_score; ALTER TABLE comment_actions + DROP COLUMN post_id, ALTER COLUMN liked DROP NOT NULL, ALTER COLUMN liked DROP DEFAULT, ALTER COLUMN like_score DROP NOT NULL, @@ -386,7 +387,3 @@ FROM post_actions; CREATE statistics post_actions_liked_stat ON (liked IS NULL), (like_score IS NULL) FROM post_actions; --- TODO: drop the column -ALTER TABLE comment_actions - ALTER COLUMN post_id DROP NOT NULL; - From 398718b2f052c07b89a6110dee2c07ac868bf50a Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 08:41:44 -0700 Subject: [PATCH 118/241] Update down.sql --- .../2024-03-17-134311_smoosh-tables-together/down.sql | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql index 3e1cde50be..45c493d7ab 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql @@ -35,7 +35,15 @@ ALTER TABLE comment_like ALTER COLUMN published SET NOT NULL, ALTER COLUMN published SET DEFAULT now(), ALTER COLUMN score SET NOT NULL, - DROP COLUMN saved; + DROP COLUMN saved, + ADD COLUMN post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE; + +UPDATE comment_like +SET + post_id = (SELECT post_id FROM comment WHERE comment.id = comment_like.comment_id); + +ALTER TABLE comment_like + ALTER COLUMN post_id SET NOT NULL; -- Create community_follower from community_actions CREATE TABLE community_block ( From b2d0ff5c83d3d879a1bfac2dd0378169b7df75ae Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 08:55:21 -0700 Subject: [PATCH 119/241] Update down.sql --- .../2024-03-17-134311_smoosh-tables-together/down.sql | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql index 45c493d7ab..9143d3623e 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql @@ -38,9 +38,16 @@ ALTER TABLE comment_like DROP COLUMN saved, ADD COLUMN post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE; -UPDATE comment_like +UPDATE + comment_like SET - post_id = (SELECT post_id FROM comment WHERE comment.id = comment_like.comment_id); + post_id = ( + SELECT + post_id + FROM + comment + WHERE + comment.id = comment_like.comment_id); ALTER TABLE comment_like ALTER COLUMN post_id SET NOT NULL; From 1e112c05235b9b229a447489842ca58c22208d06 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 09:09:48 -0700 Subject: [PATCH 120/241] Update up.sql --- .../2024-03-17-134311_smoosh-tables-together/up.sql | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index 753c18dec7..234aa25484 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -22,15 +22,13 @@ WITH old_comment_saved AS ( DELETE FROM comment_saved RETURNING *) - INSERT INTO comment_actions (person_id, comment_id, saved, post_id) + INSERT INTO comment_actions (person_id, comment_id, saved) SELECT - old_comment_saved.person_id, - old_comment_saved.comment_id, - old_comment_saved.published, - comment.post_id + person_id, + comment_id, + published FROM old_comment_saved - INNER JOIN COMMENT ON comment.id = old_comment_saved.comment_id ON CONFLICT (person_id, comment_id) DO UPDATE SET From b6de369993dbe6fdcce07beb40002740dc8842ed Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 09:21:43 -0700 Subject: [PATCH 121/241] Update up.sql --- migrations/2024-03-17-134311_smoosh-tables-together/up.sql | 4 ---- 1 file changed, 4 deletions(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index 234aa25484..8a36b209d2 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -249,14 +249,10 @@ ALTER INDEX comment_like_pkey RENAME TO comment_actions_pkey; ALTER INDEX idx_comment_like_comment RENAME TO idx_comment_actions_comment; -ALTER INDEX idx_comment_like_post RENAME TO idx_comment_actions_post; - ALTER TABLE comment_actions RENAME CONSTRAINT comment_like_comment_id_fkey TO comment_actions_comment_id_fkey; ALTER TABLE comment_actions RENAME CONSTRAINT comment_like_person_id_fkey TO comment_actions_person_id_fkey; -ALTER TABLE comment_actions RENAME CONSTRAINT comment_like_post_id_fkey TO comment_actions_post_id_fkey; - ALTER INDEX community_follower_pkey RENAME TO community_actions_pkey; ALTER INDEX idx_community_follower_community RENAME TO idx_community_actions_community; From 0f2916c45d5cc027ebf9a98e934efaec701ae799 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 09:22:45 -0700 Subject: [PATCH 122/241] Update down.sql --- migrations/2024-03-17-134311_smoosh-tables-together/down.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql index 9143d3623e..68b9d9070e 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql @@ -320,6 +320,8 @@ CREATE INDEX idx_comment_saved_comment ON comment_saved (comment_id); CREATE INDEX idx_comment_saved_person ON comment_saved (person_id); +CREATE INDEX idx_comment_like_post ON comment_like (post_id); + CREATE INDEX idx_community_block_community ON community_block (community_id); CREATE INDEX idx_community_moderator_community ON community_moderator (community_id); From f25205ce0f800e3b6795d399ef47becb12d55ad0 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 09:47:19 -0700 Subject: [PATCH 123/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 6f86f9dd53..edfc3e3003 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -38,7 +38,6 @@ impl UpleteTable for comment_actions::table { type EmptyRow = ( comment_actions::person_id, comment_actions::comment_id, - comment_actions::post_id, Option, Option>, Option>, From 5e3ce5ab1c24da1a30ba00336ffd63abcb1f7d04 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 09:57:43 -0700 Subject: [PATCH 124/241] Update vote_view.rs --- crates/db_views/src/vote_view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index 0a0748df80..a893ff1dd2 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -53,7 +53,7 @@ impl VoteView { action_query(comment_actions::like_score) .inner_join(person::table) - .inner_join(post::table) + .inner_join(comment::table.inner_join(post::table)) .left_join(actions_alias( creator_community_actions, comment_actions::person_id, From a309aaa199cd58a2dd3ef05273c315599ec3ae99 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 10:03:43 -0700 Subject: [PATCH 125/241] Update vote_view.rs --- crates/db_views/src/vote_view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index a893ff1dd2..d8e5cce4ba 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -4,7 +4,7 @@ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::creator_community_actions, newtypes::{CommentId, PostId}, - schema::{comment_actions, community_actions, person, post, post_actions}, + schema::{comment, comment_actions, community_actions, person, post, post_actions}, utils::{action_query, actions_alias, get_conn, limit_and_offset, DbPool}, }; From 86838e2951bea8749f6d372b897d3cd9839734be Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 10:18:41 -0700 Subject: [PATCH 126/241] Update comment_aggregates.rs --- crates/db_schema/src/aggregates/comment_aggregates.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/db_schema/src/aggregates/comment_aggregates.rs b/crates/db_schema/src/aggregates/comment_aggregates.rs index 915d17b1d0..1baa488817 100644 --- a/crates/db_schema/src/aggregates/comment_aggregates.rs +++ b/crates/db_schema/src/aggregates/comment_aggregates.rs @@ -118,7 +118,6 @@ mod tests { let comment_like = CommentLikeForm { comment_id: inserted_comment.id, - post_id: inserted_post.id, person_id: inserted_person.id, score: 1, }; @@ -137,7 +136,6 @@ mod tests { // Add a post dislike from the other person let comment_dislike = CommentLikeForm { comment_id: inserted_comment.id, - post_id: inserted_post.id, person_id: another_inserted_person.id, score: -1, }; From 7a8e53d3d70e8105d684213ec82fbb3a41c94765 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 10:19:47 -0700 Subject: [PATCH 127/241] Update person_aggregates.rs --- crates/db_schema/src/aggregates/person_aggregates.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/db_schema/src/aggregates/person_aggregates.rs b/crates/db_schema/src/aggregates/person_aggregates.rs index 03295173f4..8315b5afc1 100644 --- a/crates/db_schema/src/aggregates/person_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_aggregates.rs @@ -101,7 +101,6 @@ mod tests { let mut comment_like = CommentLikeForm { comment_id: inserted_comment.id, person_id: inserted_person.id, - post_id: inserted_post.id, score: 1, }; @@ -121,7 +120,6 @@ mod tests { let child_comment_like = CommentLikeForm { comment_id: inserted_child_comment.id, person_id: another_inserted_person.id, - post_id: inserted_post.id, score: 1, }; From fde92c612ae78f7cf6ba082a36ed59bf792ca9de Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 10:38:04 -0700 Subject: [PATCH 128/241] Update comment_view.rs --- crates/db_views/src/comment_view.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 4a67ef8a2a..babc3592eb 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -525,7 +525,6 @@ mod tests { let comment_like_form = CommentLikeForm { comment_id: inserted_comment_0.id, - post_id: inserted_post.id, person_id: inserted_timmy_person.id, score: 1, }; @@ -625,7 +624,6 @@ mod tests { // Like a new comment let comment_like_form = CommentLikeForm { comment_id: data.inserted_comment_1.id, - post_id: data.inserted_post.id, person_id: data.timmy_local_user_view.person.id, score: 1, }; From 0794ff93f21e3d4a02b8a7bb9c9e320c1e18600e Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 10:39:09 -0700 Subject: [PATCH 129/241] Update vote_view.rs --- crates/db_views/src/vote_view.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index d8e5cce4ba..647f152a4a 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -183,7 +183,6 @@ mod tests { // Timothy votes down his own comment let timmy_comment_vote_form = CommentLikeForm { - post_id: inserted_post.id, comment_id: inserted_comment.id, person_id: inserted_timmy.id, score: -1, @@ -194,7 +193,6 @@ mod tests { // Sara upvotes timmy's comment let sara_comment_vote_form = CommentLikeForm { - post_id: inserted_post.id, comment_id: inserted_comment.id, person_id: inserted_sara.id, score: 1, From 4918e728ae863b39e506426c40f3df29ee974687 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 11:02:58 -0700 Subject: [PATCH 130/241] Update mod.rs --- crates/apub/src/activities/voting/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/apub/src/activities/voting/mod.rs b/crates/apub/src/activities/voting/mod.rs index 3e59cb7d0c..7c39b2246d 100644 --- a/crates/apub/src/activities/voting/mod.rs +++ b/crates/apub/src/activities/voting/mod.rs @@ -62,7 +62,6 @@ async fn vote_comment( let comment_id = comment.id; let like_form = CommentLikeForm { comment_id, - post_id: comment.post_id, person_id: actor.id, score: vote_type.into(), }; From 421e05670e166e411416a3164204f12cfb6f4735 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 11:03:57 -0700 Subject: [PATCH 131/241] Update create.rs --- crates/api_crud/src/comment/create.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index 6493d68034..285b68303e 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -149,7 +149,6 @@ pub async fn create_comment( // You like your own comment by default let like_form = CommentLikeForm { comment_id: inserted_comment.id, - post_id: post.id, person_id: local_user_view.person.id, score: 1, }; From 1a08f27fae5978a2bbe64acf917e2503e5ab8ab8 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 11:04:41 -0700 Subject: [PATCH 132/241] Update comment.rs --- crates/apub/src/activities/create_or_update/comment.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/apub/src/activities/create_or_update/comment.rs b/crates/apub/src/activities/create_or_update/comment.rs index 2406d2eb34..e068b56213 100644 --- a/crates/apub/src/activities/create_or_update/comment.rs +++ b/crates/apub/src/activities/create_or_update/comment.rs @@ -160,7 +160,6 @@ impl ActivityHandler for CreateOrUpdateNote { // author likes their own comment by default let like_form = CommentLikeForm { comment_id: comment.id, - post_id: comment.post_id, person_id: comment.creator_id, score: 1, }; From 437200f26aa97afc07837e8b830aa867f4e1e529 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 12:48:49 -0700 Subject: [PATCH 133/241] Update community.rs --- crates/db_schema/src/impls/community.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 26a4a333e8..0cca082fc6 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -107,6 +107,12 @@ impl Joinable for CommunityModerator { ); insert_into(community_actions::table) .values(community_moderator_form) + .on_conflict(( + community_actions::person_id, + community_actions::community_id, + )) + .do_update() + .set(community_moderator_form), .returning(Self::as_select_unwrap()) .get_result::(conn) .await From f62fcc4d0644e85935ead58b4e2f82659c6dba36 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 12:56:46 -0700 Subject: [PATCH 134/241] Update community.rs --- crates/db_schema/src/impls/community.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 0cca082fc6..f438477f5a 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -112,7 +112,7 @@ impl Joinable for CommunityModerator { community_actions::community_id, )) .do_update() - .set(community_moderator_form), + .set(community_moderator_form) .returning(Self::as_select_unwrap()) .get_result::(conn) .await From 488797b3cf003226a6a94ee939d8b3d04484cc5c Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 12:59:02 -0700 Subject: [PATCH 135/241] Update up.sql --- migrations/2024-03-17-134311_smoosh-tables-together/up.sql | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index 8a36b209d2..90111b9c11 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -354,10 +354,7 @@ WHERE hidden IS NOT NULL; -- This index is currently redundant because instance_actions only has 1 action type, but inconsistency --- with other tables would make it harder to do everything correctly when adding another action type. If --- omitting this index is significantly beneficial, then it should be done when indexes are declared outside --- of migrations in `lemmy_db_schema::schema_setup` and adding this index doesn't require changing code that --- isn't right next to the code that needs to be changed to add another action type to instance_actions. +-- with other tables would make it harder to do everything correctly when adding another action type CREATE INDEX idx_instance_actions_blocked_not_null ON instance_actions (person_id, instance_id) WHERE blocked IS NOT NULL; From 90b10b799495b52d0c34a19ba47c9bf6933fd7b1 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 13:59:00 -0700 Subject: [PATCH 136/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 39 ++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 2ae8c3fe5d..97bf080ade 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -2,7 +2,7 @@ use diesel::{ dsl, expression::{AsExpression, TypedExpressionType}, pg::Pg, - query_builder::{AstPass, QueryFragment, UpdateStatement}, + query_builder::{AstPass, Query, QueryFragment, UpdateStatement}, result::Error, sql_types, QueryId, @@ -16,6 +16,7 @@ pub trait UpleteTable: Table + Default { pub trait OrDelete { type Output; + /// Change an update query so rows that equal `UpleteTable::EmptyRow::default()` are deleted fn or_delete(self) -> Self::Output; } @@ -52,6 +53,10 @@ pub struct SetOrDeleteQuery { empty_row: E, } +impl Query for SetOrDeleteQuery { + type SqlType = (sql_types::BigInt, sql_types::BigInt); +} + impl< T: QueryFragment, PK: QueryFragment, @@ -61,14 +66,14 @@ impl< > QueryFragment for SetOrDeleteQuery { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { - // `update_result` CTE with new rows + // `update_result` CTE with new rows (concurrent writers to these rows are blocked until this query ends) out.push_sql("WITH update_result AS ("); self.update_statement.walk_ast(out.reborrow())?; out.push_sql(" RETURNING "); self.all_columns.walk_ast(out.reborrow())?; - // Delete - out.push_sql(") DELETE FROM "); + // Beginning of `delete_result` CTE with 1 row per deleted row + out.push_sql("), delete_result AS (DELETE FROM "); self.table.walk_ast(out.reborrow())?; out.push_sql(" WHERE ("); self.primary_key.walk_ast(out.reborrow())?; @@ -84,8 +89,32 @@ impl< self.all_columns.walk_ast(out.reborrow())?; out.push_sql(") IS NOT DISTINCT FROM "); self.empty_row.walk_ast(out.reborrow())?; - out.push_sql(")"); + + // Select count from each CTE + out.push_sql(") RETURNING 1) SELECT (SELECT count(*) from update_result), (SELECT count(*) FROM delete_result)"); Ok(()) } } + +#[derive(Queryable, PartialEq, Eq, Debug)] +pub struct UpleteCount { + pub all: i64, + pub deleted: i64, +} + +impl UpleteCount { + pub fn only_updated(n: i64) -> Self { + UpleteCount { + all: n, + deleted: 0, + } + } + + pub fn only_deleted(n: i64) -> Self { + UpleteCount { + all: n, + deleted: n, + } + } +} From d502b68a6e48ab3e9d44af08a02f86792d34513b Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 14:43:30 -0700 Subject: [PATCH 137/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 55 ++++++++++++++++++---------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 97bf080ade..1f7830c03a 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -1,4 +1,5 @@ use diesel::{ + deserialize::Queryable, dsl, expression::{AsExpression, TypedExpressionType}, pg::Pg, @@ -53,8 +54,11 @@ pub struct SetOrDeleteQuery { empty_row: E, } -impl Query for SetOrDeleteQuery { - type SqlType = (sql_types::BigInt, sql_types::BigInt); +impl Query for SetOrDeleteQuery +where + T::SqlType: 'static, +{ + type SqlType = (sql_types::Array>, sql_types::BigInt); } impl< @@ -66,7 +70,8 @@ impl< > QueryFragment for SetOrDeleteQuery { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { - // `update_result` CTE with new rows (concurrent writers to these rows are blocked until this query ends) + // `update_result` CTE with new rows (concurrent writers to these rows are blocked until this + // query ends) out.push_sql("WITH update_result AS ("); self.update_statement.walk_ast(out.reborrow())?; out.push_sql(" RETURNING "); @@ -90,31 +95,41 @@ impl< out.push_sql(") IS NOT DISTINCT FROM "); self.empty_row.walk_ast(out.reborrow())?; - // Select count from each CTE - out.push_sql(") RETURNING 1) SELECT (SELECT count(*) from update_result), (SELECT count(*) FROM delete_result)"); + out.push_sql(") RETURNING "); + self.all_columns.walk_ast(out.reborrow())?; + + // Select values to be received + out.push_sql( + ") SELECT (SELECT array_agg(update_result.*) from update_result WHERE (update_result.*) != ALL (SELECT * FROM delete_result)), (SELECT count(*) FROM delete_result)" + ); Ok(()) } } -#[derive(Queryable, PartialEq, Eq, Debug)] -pub struct UpleteCount { - pub all: i64, - pub deleted: i64, +#[derive(PartialEq, Eq, Debug)] +pub struct UpletedRows { + pub updated_rows: Vec, + pub deleted_rows_count: i64, } -impl UpleteCount { - pub fn only_updated(n: i64) -> Self { - UpleteCount { - all: n, - deleted: 0, - } +impl UpletedRows { + pub fn only_updated(updated_rows: Vec) -> Self { + UpleteCount { updated_rows, deleted_rows_count: 0 } } - pub fn only_deleted(n: i64) -> Self { - UpleteCount { - all: n, - deleted: n, - } + pub fn only_deleted(deleted_rows_count: i64) -> Self { + UpleteCount { updated_rows: Vec::new(), deleted_rows_count } + } +} + +impl> Queryable<(sql_types::Array>, sql_types::BigInt), Pg> for UpletedRows { + type Row = (Vec, i64); + + fn build((updated_rows, deleted_rows_count): Self::Row) -> diesel::deserialize::Result { + Ok(UpletedRows { + updated_rows: updated_rows.into_iter().map(T::build).collect::, _>>()?, + deleted_rows_count, + }) } } From 29f4fa7dc19dfdbfcd949e527904fabc3269477d Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 14:56:19 -0700 Subject: [PATCH 138/241] revert to UpleteCount --- crates/db_schema/src/utils/uplete.rs | 46 ++++++++-------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 1f7830c03a..685dd8c866 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -1,5 +1,4 @@ use diesel::{ - deserialize::Queryable, dsl, expression::{AsExpression, TypedExpressionType}, pg::Pg, @@ -54,11 +53,8 @@ pub struct SetOrDeleteQuery { empty_row: E, } -impl Query for SetOrDeleteQuery -where - T::SqlType: 'static, -{ - type SqlType = (sql_types::Array>, sql_types::BigInt); +impl Query for SetOrDeleteQuery { + type SqlType = (sql_types::BigInt, sql_types::BigInt); } impl< @@ -95,41 +91,25 @@ impl< out.push_sql(") IS NOT DISTINCT FROM "); self.empty_row.walk_ast(out.reborrow())?; - out.push_sql(") RETURNING "); - self.all_columns.walk_ast(out.reborrow())?; - - // Select values to be received - out.push_sql( - ") SELECT (SELECT array_agg(update_result.*) from update_result WHERE (update_result.*) != ALL (SELECT * FROM delete_result)), (SELECT count(*) FROM delete_result)" - ); + // Select count from each CTE + out.push_sql(") RETURNING 1) SELECT (SELECT count(*) from update_result), (SELECT count(*) FROM delete_result)"); Ok(()) } } -#[derive(PartialEq, Eq, Debug)] -pub struct UpletedRows { - pub updated_rows: Vec, - pub deleted_rows_count: i64, +#[derive(Queryable, PartialEq, Eq, Debug)] +pub struct UpleteCount { + pub all: i64, + pub deleted: i64, } -impl UpletedRows { - pub fn only_updated(updated_rows: Vec) -> Self { - UpleteCount { updated_rows, deleted_rows_count: 0 } +impl UpleteCount { + pub fn only_updated(n: i64) -> Self { + UpleteCount { all: n, deleted: 0 } } - pub fn only_deleted(deleted_rows_count: i64) -> Self { - UpleteCount { updated_rows: Vec::new(), deleted_rows_count } - } -} - -impl> Queryable<(sql_types::Array>, sql_types::BigInt), Pg> for UpletedRows { - type Row = (Vec, i64); - - fn build((updated_rows, deleted_rows_count): Self::Row) -> diesel::deserialize::Result { - Ok(UpletedRows { - updated_rows: updated_rows.into_iter().map(T::build).collect::, _>>()?, - deleted_rows_count, - }) + pub fn only_deleted(n: i64) -> Self { + UpleteCount { all: n, deleted: n } } } From 730f4cbaa63b7dc4f000025a10836ee1c0eb5a44 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 15:00:34 -0700 Subject: [PATCH 139/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index edfc3e3003..ff49bd7e88 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -17,7 +17,7 @@ use crate::{ get_conn, naive_now, now, - uplete::{OrDelete, UpleteTable}, + uplete::{OrDelete, UpleteCount, UpleteTable}, DbPool, DELETED_REPLACEMENT_TEXT, }, @@ -198,7 +198,7 @@ impl Likeable for CommentLike { pool: &mut DbPool<'_>, person_id: PersonId, comment_id: CommentId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(comment_actions::table.find((person_id, comment_id))) .set(( @@ -249,7 +249,7 @@ impl Saveable for CommentSaved { async fn unsave( pool: &mut DbPool<'_>, comment_saved_form: &CommentSavedForm, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update( comment_actions::table.find((comment_saved_form.person_id, comment_saved_form.comment_id)), @@ -429,8 +429,8 @@ mod tests { format!("0.{}.{}", expected_comment.id, inserted_child_comment.id), inserted_child_comment.path.0, ); - assert_eq!(1, like_removed); - assert_eq!(1, saved_removed); + assert_eq!(UpleteCount::only_updated(1), like_removed); + assert_eq!(UpleteCount::only_deleted(1), saved_removed); assert_eq!(1, num_deleted); } } From cadab90c6060e759b1864ca0b9979d9fa09a02f7 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sat, 25 May 2024 15:17:08 -0700 Subject: [PATCH 140/241] Update traits.rs --- crates/db_schema/src/traits.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/db_schema/src/traits.rs b/crates/db_schema/src/traits.rs index 2b0da6c7fd..4779c0a83a 100644 --- a/crates/db_schema/src/traits.rs +++ b/crates/db_schema/src/traits.rs @@ -1,7 +1,7 @@ use crate::{ diesel::OptionalExtension, newtypes::{CommunityId, DbUrl, PersonId}, - utils::{get_conn, DbPool}, + utils::{get_conn, uplete::UpleteCount, DbPool}, }; use diesel::{ associations::HasTable, @@ -77,7 +77,7 @@ pub trait Followable { ) -> Result where Self: Sized; - async fn unfollow(pool: &mut DbPool<'_>, form: &Self::Form) -> Result + async fn unfollow(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; } @@ -88,7 +88,7 @@ pub trait Joinable { async fn join(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; - async fn leave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result + async fn leave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; } @@ -104,7 +104,7 @@ pub trait Likeable { pool: &mut DbPool<'_>, person_id: PersonId, item_id: Self::IdType, - ) -> Result + ) -> Result where Self: Sized; } @@ -115,7 +115,7 @@ pub trait Bannable { async fn ban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; - async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result + async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; } @@ -126,7 +126,7 @@ pub trait Saveable { async fn save(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; - async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result + async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; } @@ -137,7 +137,7 @@ pub trait Blockable { async fn block(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; - async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> Result + async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; } From f613f986b2ed533fcc90bc049719dcc455b8365a Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 07:40:12 -0700 Subject: [PATCH 141/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index ff49bd7e88..ea1526ce5d 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -206,7 +206,7 @@ impl Likeable for CommentLike { comment_actions::liked.eq(None::>), )) .or_delete() - .execute(conn) + .get_result(conn) .await } } @@ -256,7 +256,7 @@ impl Saveable for CommentSaved { ) .set(comment_actions::saved.eq(None::>)) .or_delete() - .execute(conn) + .get_result(conn) .await } } From 8c42d95e17a114ae61756f9c3f8cc219244c1892 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 07:59:59 -0700 Subject: [PATCH 142/241] Update community.rs --- crates/db_schema/src/impls/community.rs | 45 ++++++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index f438477f5a..bb32a944a7 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -24,6 +24,7 @@ use crate::{ functions::{coalesce, lower}, get_conn, now, + uplete::{OrDelete, UpleteCount, UpleteTable}, DbPool, }, SubscribedType, @@ -46,6 +47,19 @@ use diesel::{ }; use diesel_async::RunQueryDsl; +impl UpleteTable for community_actions::table { + type EmptyRow = ( + community_actions::community_id, + community_actions::person_id, + Option>, + Option, + Option>, + Option>, + Option>, + Option>, + ); +} + #[async_trait] impl Crud for Community { type InsertForm = CommunityInsertForm; @@ -121,14 +135,15 @@ impl Joinable for CommunityModerator { async fn leave( pool: &mut DbPool<'_>, community_moderator_form: &CommunityModeratorForm, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(community_actions::table.find(( community_moderator_form.person_id, community_moderator_form.community_id, ))) .set(community_actions::became_moderator.eq(None::>)) - .execute(conn) + .or_delete() + .get_result(conn) .await } } @@ -226,25 +241,27 @@ impl CommunityModerator { pub async fn delete_for_community( pool: &mut DbPool<'_>, for_community_id: CommunityId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update( community_actions::table.filter(community_actions::community_id.eq(for_community_id)), ) .set(community_actions::became_moderator.eq(None::>)) - .execute(conn) + .or_delete() + .get_result(conn) .await } pub async fn leave_all_communities( pool: &mut DbPool<'_>, for_person_id: PersonId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(community_actions::table.filter(community_actions::person_id.eq(for_person_id))) .set(community_actions::became_moderator.eq(None::>)) - .execute(conn) + .or_delete() + .get_result(conn) .await } @@ -305,7 +322,7 @@ impl Bannable for CommunityPersonBan { async fn unban( pool: &mut DbPool<'_>, community_person_ban_form: &CommunityPersonBanForm, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(community_actions::table.find(( community_person_ban_form.person_id, @@ -315,7 +332,8 @@ impl Bannable for CommunityPersonBan { community_actions::received_ban.eq(None::>), community_actions::ban_expires.eq(None::>), )) - .execute(conn) + .or_delete() + .get_result(conn) .await } } @@ -413,14 +431,15 @@ impl Followable for CommunityFollower { .get_result::(conn) .await } - async fn unfollow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result { + async fn unfollow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(community_actions::table.find((form.person_id, form.community_id))) .set(( community_actions::followed.eq(None::>), community_actions::follow_pending.eq(None::), )) - .execute(conn) + .or_delete() + .get_result(conn) .await } } @@ -640,9 +659,9 @@ mod tests { assert_eq!(expected_community_follower, inserted_community_follower); assert_eq!(expected_community_moderator, inserted_community_moderator); assert_eq!(expected_community_person_ban, inserted_community_person_ban); - assert_eq!(1, ignored_community); - assert_eq!(1, left_community); - assert_eq!(1, unban); + assert_eq!(UpleteCount::only_updated(1), ignored_community); + assert_eq!(UpleteCount::only_updated(1), left_community); + assert_eq!(UpleteCount::only_deleted(1), unban); // assert_eq!(2, loaded_count); assert_eq!(1, num_deleted); } From dabb47987288a66e2688ddd80d8998391bee7d04 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 08:01:43 -0700 Subject: [PATCH 143/241] Update community_block.rs --- crates/db_schema/src/impls/community_block.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index 4b5b05605e..b53d981d22 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -3,7 +3,13 @@ use crate::{ schema::community_actions, source::community_block::{CommunityBlock, CommunityBlockForm}, traits::Blockable, - utils::{find_action, get_conn, now, DbPool}, + utils::{ + find_action, + get_conn, + now, + uplete::{OrDelete, UpleteCount, UpleteTable}, + DbPool, + }, }; use chrono::{DateTime, Utc}; use diesel::{ @@ -68,14 +74,15 @@ impl Blockable for CommunityBlock { async fn unblock( pool: &mut DbPool<'_>, community_block_form: &Self::Form, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(community_actions::table.find(( community_block_form.person_id, community_block_form.community_id, ))) .set(community_actions::blocked.eq(None::>)) - .execute(conn) + .or_delete() + .get_result(conn) .await } } From 4c3af86c00cc84b97c4f8ace91f9d7ce6323c832 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 08:02:41 -0700 Subject: [PATCH 144/241] Update community.rs --- crates/db_schema/src/impls/community.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index bb32a944a7..244c6cb180 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -431,7 +431,10 @@ impl Followable for CommunityFollower { .get_result::(conn) .await } - async fn unfollow(pool: &mut DbPool<'_>, form: &CommunityFollowerForm) -> Result { + async fn unfollow( + pool: &mut DbPool<'_>, + form: &CommunityFollowerForm, + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(community_actions::table.find((form.person_id, form.community_id))) .set(( From c3e4835ece24b5b655bf9599b390f00f0b9cc790 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 08:07:26 -0700 Subject: [PATCH 145/241] Update instance_block.rs --- crates/db_schema/src/impls/instance_block.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index 6076cca47a..d526e4d6b3 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -3,7 +3,13 @@ use crate::{ schema::instance_actions, source::instance_block::{InstanceBlock, InstanceBlockForm}, traits::Blockable, - utils::{find_action, get_conn, now, DbPool}, + utils::{ + find_action, + get_conn, + now, + uplete::{OrDelete, UpleteCount}, + DbPool, + }, }; use chrono::{DateTime, Utc}; use diesel::{ @@ -65,7 +71,7 @@ impl Blockable for InstanceBlock { async fn unblock( pool: &mut DbPool<'_>, instance_block_form: &Self::Form, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(instance_actions::table.find(( instance_block_form.person_id, From 11a90f178b015db6052a51d3565cfd66a79e7da5 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 08:08:10 -0700 Subject: [PATCH 146/241] Update instance.rs --- crates/db_schema/src/impls/instance.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/db_schema/src/impls/instance.rs b/crates/db_schema/src/impls/instance.rs index 8d2e4b75aa..ae1ead279d 100644 --- a/crates/db_schema/src/impls/instance.rs +++ b/crates/db_schema/src/impls/instance.rs @@ -6,6 +6,7 @@ use crate::{ federation_blocklist, federation_queue_state, instance, + instance_actions, local_site, site, }, @@ -18,6 +19,7 @@ use crate::{ get_conn, naive_now, now, + uplete::UpleteTable, DbPool, }, }; @@ -32,6 +34,14 @@ use diesel::{ }; use diesel_async::RunQueryDsl; +impl UpleteTable for instance_actions::table { + type EmptyRow = ( + instance_actions::person_id, + instance_actions::instance_id, + Option>, + ); +} + impl Instance { /// Attempt to read Instance column for the given domain. If it doesn't exist, insert a new one. /// There is no need for update as the domain of an existing instance cant change. From 7b56d5ba4d710f7dce7a46a81fe5ab128702bb4d Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 08:08:28 -0700 Subject: [PATCH 147/241] Update community_block.rs --- crates/db_schema/src/impls/community_block.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index b53d981d22..da9ba33151 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -7,7 +7,7 @@ use crate::{ find_action, get_conn, now, - uplete::{OrDelete, UpleteCount, UpleteTable}, + uplete::{OrDelete, UpleteCount}, DbPool, }, }; From d264f0445f6f9737834afecce9d44cbb13931804 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 08:14:47 -0700 Subject: [PATCH 148/241] Update person.rs --- crates/db_schema/src/impls/person.rs | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index fd95515e5e..085e96a6d3 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -10,7 +10,15 @@ use crate::{ PersonUpdateForm, }, traits::{ApubActor, Crud, Followable}, - utils::{action_query, functions::lower, get_conn, naive_now, now, DbPool}, + utils::{ + action_query, + functions::lower, + get_conn, + naive_now, + now, + upsert::{OrTable, UpleteCount, UpleteTable}, + DbPool, + }, }; use chrono::{DateTime, Utc}; use diesel::{ @@ -24,6 +32,16 @@ use diesel::{ }; use diesel_async::RunQueryDsl; +impl UpleteTable for person_actions::table { + type EmptyRow = ( + person_actions::target_id, + person_actions::person_id, + Option>, + Option, + Option>, + ); +} + #[async_trait] impl Crud for Person { type InsertForm = PersonInsertForm; @@ -216,14 +234,15 @@ impl Followable for PersonFollower { async fn follow_accepted(_: &mut DbPool<'_>, _: CommunityId, _: PersonId) -> Result { unimplemented!() } - async fn unfollow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result { + async fn unfollow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(person_actions::table.find((form.follower_id, form.person_id))) .set(( person_actions::followed.eq(None::>), person_actions::follow_pending.eq(None::), )) - .execute(conn) + .or_delete() + .get_result(conn) .await } } @@ -361,6 +380,6 @@ mod tests { assert_eq!(vec![person_2], followers); let unfollow = PersonFollower::unfollow(pool, &follow_form).await.unwrap(); - assert_eq!(1, unfollow); + assert_eq!(UpleteCount::only_deleted(1), unfollow); } } From 436dc11be015c63baaf2bfff11f09554f135c584 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 08:16:08 -0700 Subject: [PATCH 149/241] Update person_block.rs --- crates/db_schema/src/impls/person_block.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index cabfcaf21f..ff6bf23482 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -3,7 +3,13 @@ use crate::{ schema::person_actions, source::person_block::{PersonBlock, PersonBlockForm}, traits::Blockable, - utils::{find_action, get_conn, now, DbPool}, + utils::{ + find_action, + get_conn, + now, + uplete::{OrDelete, UpleteCount}, + DbPool, + }, }; use chrono::{DateTime, Utc}; use diesel::{ @@ -66,13 +72,14 @@ impl Blockable for PersonBlock { .get_result::(conn) .await } - async fn unblock(pool: &mut DbPool<'_>, person_block_form: &Self::Form) -> Result { + async fn unblock(pool: &mut DbPool<'_>, person_block_form: &Self::Form) -> Result { let conn = &mut get_conn(pool).await?; diesel::update( person_actions::table.find((person_block_form.person_id, person_block_form.target_id)), ) .set(person_actions::blocked.eq(None::>)) - .execute(conn) + .or_delete() + .get_result(conn) .await } } From 86a174032e8304c2459100529826b11f2168603e Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 08:17:30 -0700 Subject: [PATCH 150/241] Update person.rs --- crates/db_schema/src/impls/person.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 085e96a6d3..33e7fc435e 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -234,7 +234,10 @@ impl Followable for PersonFollower { async fn follow_accepted(_: &mut DbPool<'_>, _: CommunityId, _: PersonId) -> Result { unimplemented!() } - async fn unfollow(pool: &mut DbPool<'_>, form: &PersonFollowerForm) -> Result { + async fn unfollow( + pool: &mut DbPool<'_>, + form: &PersonFollowerForm, + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(person_actions::table.find((form.follower_id, form.person_id))) .set(( From 6090afe3e525de95c3f5a037cbeaabda55cce84d Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 08:18:16 -0700 Subject: [PATCH 151/241] Update person_block.rs --- crates/db_schema/src/impls/person_block.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index ff6bf23482..e540190188 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -72,7 +72,10 @@ impl Blockable for PersonBlock { .get_result::(conn) .await } - async fn unblock(pool: &mut DbPool<'_>, person_block_form: &Self::Form) -> Result { + async fn unblock( + pool: &mut DbPool<'_>, + person_block_form: &Self::Form, + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update( person_actions::table.find((person_block_form.person_id, person_block_form.target_id)), From 09dea6bcf6b0ea3fb56ff3daf024c7ef91e6d16b Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 09:12:41 -0700 Subject: [PATCH 152/241] Update person.rs --- crates/db_schema/src/impls/person.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 33e7fc435e..61df18e1b2 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -16,7 +16,7 @@ use crate::{ get_conn, naive_now, now, - upsert::{OrTable, UpleteCount, UpleteTable}, + uplete::{OrTable, UpleteCount, UpleteTable}, DbPool, }, }; From 7c6ffb426478901c1be6d0ebaefecff2d6d27dff Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 09:13:34 -0700 Subject: [PATCH 153/241] Update instance.rs --- crates/db_schema/src/impls/instance.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/db_schema/src/impls/instance.rs b/crates/db_schema/src/impls/instance.rs index ae1ead279d..10fee8ff87 100644 --- a/crates/db_schema/src/impls/instance.rs +++ b/crates/db_schema/src/impls/instance.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use crate::{ diesel::dsl::IntervalDsl, newtypes::InstanceId, From abb1a7405f0c15eeb70da9ec2f283f5aa9dea2a0 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 09:14:01 -0700 Subject: [PATCH 154/241] Update instance_block.rs --- crates/db_schema/src/impls/instance_block.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index d526e4d6b3..13c55d7ad5 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -78,7 +78,8 @@ impl Blockable for InstanceBlock { instance_block_form.instance_id, ))) .set(instance_actions::blocked.eq(None::>)) - .execute(conn) + .or_delete() + .get_result(conn) .await } } From 322aa73aa0e58c0fe3841241f62936dc997a7dc7 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 09:21:32 -0700 Subject: [PATCH 155/241] Update instance.rs --- crates/db_schema/src/impls/instance.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/instance.rs b/crates/db_schema/src/impls/instance.rs index 10fee8ff87..4df7cd0c60 100644 --- a/crates/db_schema/src/impls/instance.rs +++ b/crates/db_schema/src/impls/instance.rs @@ -1,4 +1,3 @@ -use chrono::{DateTime, Utc}; use crate::{ diesel::dsl::IntervalDsl, newtypes::InstanceId, @@ -24,6 +23,7 @@ use crate::{ DbPool, }, }; +use chrono::{DateTime, Utc}; use diesel::{ dsl::{count_star, insert_into}, result::Error, From dba0e37b0a63e0badec2160993c506038ba6929e Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 09:32:36 -0700 Subject: [PATCH 156/241] Update person.rs --- crates/db_schema/src/impls/person.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 61df18e1b2..38586561c3 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -16,7 +16,7 @@ use crate::{ get_conn, naive_now, now, - uplete::{OrTable, UpleteCount, UpleteTable}, + uplete::{OrDelete, UpleteCount, UpleteTable}, DbPool, }, }; From b70c9fe36b96951520be796f3660344aa00c5d1f Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 09:43:49 -0700 Subject: [PATCH 157/241] Update post.rs --- crates/db_schema/src/impls/post.rs | 46 ++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index a3b389a51a..df09253bd5 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -21,6 +21,7 @@ use crate::{ get_conn, naive_now, now, + uplete::{OrDelete, UpleteCount, UpleteTable}, DbPool, DELETED_REPLACEMENT_TEXT, FETCH_LIMIT_MAX, @@ -42,6 +43,20 @@ use diesel::{ use diesel_async::RunQueryDsl; use std::collections::HashSet; +impl UpleteTable for post_actions::table { + type EmptyRow = ( + post_actions::post_id, + post_actions::person_id, + Option>, + Option>, + Option, + Option>, + Option>, + Option, + Option>, + ); +} + #[async_trait] impl Crud for Post { type InsertForm = PostInsertForm; @@ -284,14 +299,15 @@ impl Likeable for PostLike { pool: &mut DbPool<'_>, person_id: PersonId, post_id: PostId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(post_actions::table.find((person_id, post_id))) .set(( post_actions::like_score.eq(None::), post_actions::liked.eq(None::>), )) - .execute(conn) + .or_delete() + .get_result(conn) .await } } @@ -325,11 +341,15 @@ impl Saveable for PostSaved { .get_result::(conn) .await } - async fn unsave(pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm) -> Result { + async fn unsave( + pool: &mut DbPool<'_>, + post_saved_form: &PostSavedForm, + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(post_actions::table.find((post_saved_form.person_id, post_saved_form.post_id))) .set(post_actions::saved.eq(None::>)) - .execute(conn) + .or_delete() + .get_result(conn) .await } } @@ -364,7 +384,7 @@ impl PostRead { pool: &mut DbPool<'_>, post_id_: HashSet, person_id_: PersonId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update( @@ -373,7 +393,8 @@ impl PostRead { .filter(post_actions::person_id.eq(person_id_)), ) .set(post_actions::read.eq(None::>)) - .execute(conn) + .or_delete() + .get_result(conn) .await } } @@ -408,7 +429,7 @@ impl PostHide { pool: &mut DbPool<'_>, post_id_: HashSet, person_id_: PersonId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update( @@ -417,7 +438,8 @@ impl PostHide { .filter(post_actions::person_id.eq(person_id_)), ) .set(post_actions::hidden.eq(None::>)) - .execute(conn) + .or_delete() + .get_result(conn) .await } } @@ -444,7 +466,7 @@ mod tests { }, }, traits::{Crud, Likeable, Saveable}, - utils::build_db_pool_for_tests, + utils::{build_db_pool_for_tests, uplete::UpleteCount}, }; use pretty_assertions::assert_eq; use serial_test::serial; @@ -571,9 +593,9 @@ mod tests { let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id) .await .unwrap(); - assert_eq!(1, like_removed); + assert_eq!(UpleteCount::only_updated(1), like_removed); let saved_removed = PostSaved::unsave(pool, &post_saved_form).await.unwrap(); - assert_eq!(1, saved_removed); + assert_eq!(UpleteCount::only_updated(1), saved_removed); let read_removed = PostRead::mark_as_unread( pool, HashSet::from([inserted_post.id, inserted_post2.id]), @@ -581,7 +603,7 @@ mod tests { ) .await .unwrap(); - assert_eq!(2, read_removed); + assert_eq!(UpleteCount::only_deleted(2), read_removed); let num_deleted = Post::delete(pool, inserted_post.id).await.unwrap() + Post::delete(pool, inserted_post2.id).await.unwrap(); From d8946522d3cc565530ecccea98deecf838fe3817 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 09:45:34 -0700 Subject: [PATCH 158/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index ea1526ce5d..4f32c163d5 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -284,7 +284,7 @@ mod tests { post::{Post, PostInsertForm}, }, traits::{Crud, Likeable, Saveable}, - utils::build_db_pool_for_tests, + utils::{build_db_pool_for_tests, uplete::UpleteCount}, }; use diesel_ltree::Ltree; use pretty_assertions::assert_eq; From 4b3d9ce15f69bb4aa79a34e2637cb104e714d1f4 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 09:47:02 -0700 Subject: [PATCH 159/241] Update community.rs --- crates/db_schema/src/impls/community.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 244c6cb180..242c7532e8 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -518,7 +518,7 @@ mod tests { person::{Person, PersonInsertForm}, }, traits::{Bannable, Crud, Followable, Joinable}, - utils::build_db_pool_for_tests, + utils::{build_db_pool_for_tests, uplete::UpleteCount}, CommunityVisibility, }; use pretty_assertions::assert_eq; From e454d81098f201b97eca12ac286c27284d2c5b94 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 09:48:18 -0700 Subject: [PATCH 160/241] Update person.rs --- crates/db_schema/src/impls/person.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 38586561c3..6c2295adab 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -276,7 +276,7 @@ mod tests { person::{Person, PersonFollower, PersonFollowerForm, PersonInsertForm, PersonUpdateForm}, }, traits::{Crud, Followable}, - utils::build_db_pool_for_tests, + utils::{build_db_pool_for_tests, uplete::UpleteCount}, }; use pretty_assertions::assert_eq; use serial_test::serial; From c82c8e0dde1f823f57541eef68c5328b02e8298d Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 10:18:41 -0700 Subject: [PATCH 161/241] Update post_view.rs --- crates/db_views/src/post_view.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 1cf1aad1da..f622aede8f 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -602,7 +602,7 @@ mod tests { site::Site, }, traits::{Bannable, Blockable, Crud, Joinable, Likeable}, - utils::{build_db_pool, build_db_pool_for_tests, DbPool, RANK_DEFAULT}, + utils::{build_db_pool, build_db_pool_for_tests, uplete::UpleteCount, DbPool, RANK_DEFAULT}, CommunityVisibility, SortType, SubscribedType, @@ -942,7 +942,7 @@ mod tests { let like_removed = PostLike::remove(pool, data.local_user_view.person.id, data.inserted_post.id).await?; - assert_eq!(1, like_removed); + assert_eq!(UpleteCount::only_deleted(1), like_removed); cleanup(data, pool).await } From 422df50b74ad584d02e5492d2f4e2fd4e6f0c989 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 26 May 2024 19:13:18 -0700 Subject: [PATCH 162/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 4f32c163d5..260047f66e 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -17,7 +17,7 @@ use crate::{ get_conn, naive_now, now, - uplete::{OrDelete, UpleteCount, UpleteTable}, + uplete::{uplete, UpleteCount}, DbPool, DELETED_REPLACEMENT_TEXT, }, @@ -34,16 +34,6 @@ use diesel_async::RunQueryDsl; use diesel_ltree::Ltree; use url::Url; -impl UpleteTable for comment_actions::table { - type EmptyRow = ( - comment_actions::person_id, - comment_actions::comment_id, - Option, - Option>, - Option>, - ); -} - impl Comment { pub async fn permadelete_for_creator( pool: &mut DbPool<'_>, @@ -200,12 +190,9 @@ impl Likeable for CommentLike { comment_id: CommentId, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(comment_actions::table.find((person_id, comment_id))) - .set(( - comment_actions::like_score.eq(None::), - comment_actions::liked.eq(None::>), - )) - .or_delete() + uplete(comment_actions::table.find((person_id, comment_id))) + .set_null(comment_actions::like_score) + .set_null(comment_actions::liked) .get_result(conn) .await } @@ -251,11 +238,10 @@ impl Saveable for CommentSaved { comment_saved_form: &CommentSavedForm, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update( + uplete( comment_actions::table.find((comment_saved_form.person_id, comment_saved_form.comment_id)), ) - .set(comment_actions::saved.eq(None::>)) - .or_delete() + .set_null(comment_actions::saved) .get_result(conn) .await } From 0d210d9dbbac2763cd995af63b387bd280f6ea5e Mon Sep 17 00:00:00 2001 From: dullbananas Date: Mon, 27 May 2024 09:43:28 -0700 Subject: [PATCH 163/241] reduce diff --- crates/api_common/src/utils.rs | 1 - .../db_schema/replaceable_schema/triggers.sql | 6 +-- .../src/aggregates/person_post_aggregates.rs | 19 ++----- crates/db_schema/src/aggregates/structs.rs | 11 +++- crates/db_schema/src/impls/comment.rs | 35 ++---------- crates/db_schema/src/impls/community.rs | 53 ++----------------- crates/db_schema/src/impls/community_block.rs | 15 +----- crates/db_schema/src/impls/instance_block.rs | 15 +----- crates/db_schema/src/impls/person.rs | 19 +------ crates/db_schema/src/impls/person_block.rs | 15 +----- crates/db_schema/src/impls/post.rs | 37 ++----------- crates/db_schema/src/source/comment.rs | 18 ++++++- crates/db_schema/src/source/community.rs | 27 ++++++++-- .../db_schema/src/source/community_block.rs | 9 +++- crates/db_schema/src/source/instance_block.rs | 9 +++- crates/db_schema/src/source/person.rs | 11 +++- crates/db_schema/src/source/person_block.rs | 9 +++- crates/db_schema/src/source/post.rs | 32 +++++++++-- 18 files changed, 137 insertions(+), 204 deletions(-) diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index bad3b8180c..995d9dbbd4 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -165,7 +165,6 @@ pub async fn update_read_comments( person_id, post_id, read_comments, - ..PersonPostAggregatesForm::default() }; PersonPostAggregates::upsert(pool, &person_post_agg_form) diff --git a/crates/db_schema/replaceable_schema/triggers.sql b/crates/db_schema/replaceable_schema/triggers.sql index ada6f124cb..9ec240cc98 100644 --- a/crates/db_schema/replaceable_schema/triggers.sql +++ b/crates/db_schema/replaceable_schema/triggers.sql @@ -533,7 +533,7 @@ CREATE TRIGGER delete_comments FOR EACH ROW EXECUTE FUNCTION r.delete_comments_before_post (); -CREATE FUNCTION r.delete_community_actions_before_person () +CREATE FUNCTION r.delete_follow_before_person () RETURNS TRIGGER LANGUAGE plpgsql AS $$ @@ -544,10 +544,10 @@ BEGIN END; $$; -CREATE TRIGGER delete_community_actions +CREATE TRIGGER delete_follow BEFORE DELETE ON person FOR EACH ROW - EXECUTE FUNCTION r.delete_community_actions_before_person (); + EXECUTE FUNCTION r.delete_follow_before_person (); -- Triggers that change values before insert or update CREATE FUNCTION r.comment_change_values () diff --git a/crates/db_schema/src/aggregates/person_post_aggregates.rs b/crates/db_schema/src/aggregates/person_post_aggregates.rs index 85c9692e53..6140a7706f 100644 --- a/crates/db_schema/src/aggregates/person_post_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_post_aggregates.rs @@ -7,6 +7,7 @@ use crate::{ }; use diesel::{ dsl, + expression::SelectableHelper, insert_into, result::Error, ExpressionMethods, @@ -16,20 +17,6 @@ use diesel::{ use diesel_async::RunQueryDsl; impl PersonPostAggregates { - fn as_select_unwrap() -> ( - post_actions::person_id, - post_actions::post_id, - dsl::AssumeNotNull, - dsl::AssumeNotNull, - ) { - ( - post_actions::person_id, - post_actions::post_id, - post_actions::read_comments_amount.assume_not_null(), - post_actions::read_comments.assume_not_null(), - ) - } - pub async fn upsert( pool: &mut DbPool<'_>, form: &PersonPostAggregatesForm, @@ -41,7 +28,7 @@ impl PersonPostAggregates { .on_conflict((post_actions::person_id, post_actions::post_id)) .do_update() .set(form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } @@ -52,7 +39,7 @@ impl PersonPostAggregates { ) -> Result, Error> { let conn = &mut get_conn(pool).await?; find_action(post_actions::read_comments, (person_id_, post_id_)) - .select(Self::as_select_unwrap()) + .select(Self::as_select()) .first(conn) .await .optional() diff --git a/crates/db_schema/src/aggregates/structs.rs b/crates/db_schema/src/aggregates/structs.rs index 40895d6a87..dcd9278688 100644 --- a/crates/db_schema/src/aggregates/structs.rs +++ b/crates/db_schema/src/aggregates/structs.rs @@ -10,6 +10,8 @@ use crate::schema::{ }; use chrono::{DateTime, Utc}; #[cfg(feature = "full")] +use diesel::{dsl, expression_methods::NullableExpressionMethods}; +#[cfg(feature = "full")] use i_love_jesus::CursorKeysModule; use serde::{Deserialize, Serialize}; #[cfg(feature = "full")] @@ -147,7 +149,10 @@ pub struct PostAggregates { } #[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))] +#[cfg_attr( + feature = "full", + derive(Queryable, Selectable, Associations, Identifiable) +)] #[cfg_attr(feature = "full", diesel(table_name = post_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))] @@ -159,7 +164,11 @@ pub struct PersonPostAggregates { /// The number of comments they've read on that post. /// /// This is updated to the current post comment count every time they view a post. + #[diesel(select_expression = post_actions::read_comments_amount.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub read_comments: i64, + #[diesel(select_expression = post_actions::read_comments.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, } diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 260047f66e..67d9ecedf7 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -13,6 +13,7 @@ use crate::{ }, traits::{Crud, Likeable, Saveable}, utils::{ + expression::SelectableHelper, functions::coalesce, get_conn, naive_now, @@ -149,22 +150,6 @@ impl Crud for Comment { } } -impl CommentLike { - fn as_select_unwrap() -> ( - comment_actions::person_id, - comment_actions::comment_id, - dsl::AssumeNotNull, - dsl::AssumeNotNull, - ) { - ( - comment_actions::person_id, - comment_actions::comment_id, - comment_actions::like_score.assume_not_null(), - comment_actions::liked.assume_not_null(), - ) - } -} - #[async_trait] impl Likeable for CommentLike { type Form = CommentLikeForm; @@ -180,7 +165,7 @@ impl Likeable for CommentLike { .on_conflict((comment_actions::comment_id, comment_actions::person_id)) .do_update() .set(comment_like_form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } @@ -198,20 +183,6 @@ impl Likeable for CommentLike { } } -impl CommentSaved { - fn as_select_unwrap() -> ( - comment_actions::comment_id, - comment_actions::person_id, - dsl::AssumeNotNull, - ) { - ( - comment_actions::comment_id, - comment_actions::person_id, - comment_actions::saved.assume_not_null(), - ) - } -} - #[async_trait] impl Saveable for CommentSaved { type Form = CommentSavedForm; @@ -229,7 +200,7 @@ impl Saveable for CommentSaved { .on_conflict((comment_actions::comment_id, comment_actions::person_id)) .do_update() .set(comment_saved_form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 242c7532e8..a270f393d6 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -20,6 +20,7 @@ use crate::{ traits::{ApubActor, Bannable, Crud, Followable, Joinable}, utils::{ action_query, + expression::SelectableHelper, find_action, functions::{coalesce, lower}, get_conn, @@ -93,20 +94,6 @@ impl Crud for Community { } } -impl CommunityModerator { - fn as_select_unwrap() -> ( - community_actions::community_id, - community_actions::person_id, - dsl::AssumeNotNull, - ) { - ( - community_actions::community_id, - community_actions::person_id, - community_actions::became_moderator.assume_not_null(), - ) - } -} - #[async_trait] impl Joinable for CommunityModerator { type Form = CommunityModeratorForm; @@ -127,7 +114,7 @@ impl Joinable for CommunityModerator { )) .do_update() .set(community_moderator_form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } @@ -278,22 +265,6 @@ impl CommunityModerator { } } -impl CommunityPersonBan { - fn as_select_unwrap() -> ( - community_actions::community_id, - community_actions::person_id, - dsl::AssumeNotNull, - community_actions::ban_expires, - ) { - ( - community_actions::community_id, - community_actions::person_id, - community_actions::received_ban.assume_not_null(), - community_actions::ban_expires, - ) - } -} - #[async_trait] impl Bannable for CommunityPersonBan { type Form = CommunityPersonBanForm; @@ -314,7 +285,7 @@ impl Bannable for CommunityPersonBan { )) .do_update() .set(community_person_ban_form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } @@ -339,20 +310,6 @@ impl Bannable for CommunityPersonBan { } impl CommunityFollower { - fn as_select_unwrap() -> ( - community_actions::community_id, - community_actions::person_id, - dsl::AssumeNotNull, - dsl::AssumeNotNull, - ) { - ( - community_actions::community_id, - community_actions::person_id, - community_actions::followed.assume_not_null(), - community_actions::follow_pending.assume_not_null(), - ) - } - pub fn to_subscribed_type(follower: &Option) -> SubscribedType { match follower { Some(f) => { @@ -412,7 +369,7 @@ impl Followable for CommunityFollower { )) .do_update() .set(form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } @@ -427,7 +384,7 @@ impl Followable for CommunityFollower { (person_id, community_id), )) .set(community_actions::follow_pending.eq(Some(false))) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index da9ba33151..72eacb85db 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -14,6 +14,7 @@ use crate::{ use chrono::{DateTime, Utc}; use diesel::{ dsl::{self, exists, insert_into}, + expression::SelectableHelper, result::Error, select, ExpressionMethods, @@ -23,18 +24,6 @@ use diesel::{ use diesel_async::RunQueryDsl; impl CommunityBlock { - fn as_select_unwrap() -> ( - community_actions::person_id, - community_actions::community_id, - dsl::AssumeNotNull, - ) { - ( - community_actions::person_id, - community_actions::community_id, - community_actions::blocked.assume_not_null(), - ) - } - pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, @@ -67,7 +56,7 @@ impl Blockable for CommunityBlock { )) .do_update() .set(community_block_form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index 13c55d7ad5..f82452f1f7 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -14,6 +14,7 @@ use crate::{ use chrono::{DateTime, Utc}; use diesel::{ dsl::{self, exists, insert_into}, + expression::SelectableHelper, result::Error, select, ExpressionMethods, @@ -23,18 +24,6 @@ use diesel::{ use diesel_async::RunQueryDsl; impl InstanceBlock { - fn as_select_unwrap() -> ( - instance_actions::person_id, - instance_actions::instance_id, - dsl::AssumeNotNull, - ) { - ( - instance_actions::person_id, - instance_actions::instance_id, - instance_actions::blocked.assume_not_null(), - ) - } - pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, @@ -64,7 +53,7 @@ impl Blockable for InstanceBlock { .on_conflict((instance_actions::person_id, instance_actions::instance_id)) .do_update() .set(instance_block_form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 6c2295adab..1c7dbc7e58 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -23,6 +23,7 @@ use crate::{ use chrono::{DateTime, Utc}; use diesel::{ dsl::{self, insert_into}, + expression::SelectableHelper, result::Error, CombineDsl, ExpressionMethods, @@ -200,22 +201,6 @@ impl ApubActor for Person { } } -impl PersonFollower { - fn as_select_unwrap() -> ( - person_actions::target_id, - person_actions::person_id, - dsl::AssumeNotNull, - dsl::AssumeNotNull, - ) { - ( - person_actions::target_id, - person_actions::person_id, - person_actions::followed.assume_not_null(), - person_actions::follow_pending.assume_not_null(), - ) - } -} - #[async_trait] impl Followable for PersonFollower { type Form = PersonFollowerForm; @@ -227,7 +212,7 @@ impl Followable for PersonFollower { .on_conflict((person_actions::person_id, person_actions::target_id)) .do_update() .set(form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index e540190188..b1c17cfbc8 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -15,6 +15,7 @@ use chrono::{DateTime, Utc}; use diesel::{ dsl, dsl::{exists, insert_into}, + expression::SelectableHelper, result::Error, select, ExpressionMethods, @@ -24,18 +25,6 @@ use diesel::{ use diesel_async::RunQueryDsl; impl PersonBlock { - fn as_select_unwrap() -> ( - person_actions::person_id, - person_actions::target_id, - dsl::AssumeNotNull, - ) { - ( - person_actions::person_id, - person_actions::target_id, - person_actions::blocked.assume_not_null(), - ) - } - pub async fn read( pool: &mut DbPool<'_>, for_person_id: PersonId, @@ -68,7 +57,7 @@ impl Blockable for PersonBlock { .on_conflict((person_actions::person_id, person_actions::target_id)) .do_update() .set(person_block_form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index df09253bd5..06a979f83c 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -33,6 +33,7 @@ use ::url::Url; use chrono::{DateTime, Utc}; use diesel::{ dsl::{self, insert_into}, + expression::SelectableHelper, result::Error, DecoratableTarget, ExpressionMethods, @@ -261,24 +262,6 @@ impl Post { } } -// TODO: replace `as_select_unwrap` definitions with `Default::default()` after next Diesel release -// which should implement `Default` for `AssumeNotNull` -impl PostLike { - fn as_select_unwrap() -> ( - post_actions::post_id, - post_actions::person_id, - dsl::AssumeNotNull, - dsl::AssumeNotNull, - ) { - ( - post_actions::post_id, - post_actions::person_id, - post_actions::like_score.assume_not_null(), - post_actions::liked.assume_not_null(), - ) - } -} - #[async_trait] impl Likeable for PostLike { type Form = PostLikeForm; @@ -291,7 +274,7 @@ impl Likeable for PostLike { .on_conflict((post_actions::post_id, post_actions::person_id)) .do_update() .set(post_like_form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } @@ -312,20 +295,6 @@ impl Likeable for PostLike { } } -impl PostSaved { - fn as_select_unwrap() -> ( - post_actions::post_id, - post_actions::person_id, - dsl::AssumeNotNull, - ) { - ( - post_actions::post_id, - post_actions::person_id, - post_actions::saved.assume_not_null(), - ) - } -} - #[async_trait] impl Saveable for PostSaved { type Form = PostSavedForm; @@ -337,7 +306,7 @@ impl Saveable for PostSaved { .on_conflict((post_actions::post_id, post_actions::person_id)) .do_update() .set(post_saved_form) - .returning(Self::as_select_unwrap()) + .returning(Self::as_select()) .get_result::(conn) .await } diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index 2f235932ee..883719ba28 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -5,6 +5,8 @@ use crate::newtypes::{CommentId, DbUrl, LanguageId, PersonId, PostId}; use crate::schema::{comment, comment_actions}; use chrono::{DateTime, Utc}; #[cfg(feature = "full")] +use diesel::{dsl, expression_methods::NullableExpressionMethods} +#[cfg(feature = "full")] use diesel_ltree::Ltree; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; @@ -88,7 +90,10 @@ pub struct CommentUpdateForm { } #[derive(PartialEq, Eq, Debug, Clone)] -#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] +#[cfg_attr( + feature = "full", + derive(Identifiable, Queryable, Selectable, Associations) +)] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))] #[cfg_attr(feature = "full", diesel(table_name = comment_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, comment_id)))] @@ -96,7 +101,11 @@ pub struct CommentUpdateForm { pub struct CommentLike { pub person_id: PersonId, pub comment_id: CommentId, + #[diesel(select_expression = comment_actions::like_score.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub score: i16, + #[diesel(select_expression = comment_actions::liked.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, } @@ -111,7 +120,10 @@ pub struct CommentLikeForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] +#[cfg_attr( + feature = "full", + derive(Identifiable, Queryable, Selectable, Associations) +)] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))] #[cfg_attr(feature = "full", diesel(table_name = comment_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, comment_id)))] @@ -119,6 +131,8 @@ pub struct CommentLikeForm { pub struct CommentSaved { pub comment_id: CommentId, pub person_id: PersonId, + #[diesel(select_expression = comment_actions::saved.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, } diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index 1be33d8b9f..0b5ce60da2 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -7,6 +7,8 @@ use crate::{ CommunityVisibility, }; use chrono::{DateTime, Utc}; +#[cfg(feature = "full")] +use diesel::{dsl, expression_methods::NullableExplessionMethods}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -134,7 +136,10 @@ pub struct CommunityUpdateForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] +#[cfg_attr( + feature = "full", + derive(Identifiable, Queryable, Selectable, Associations) +)] #[cfg_attr( feature = "full", diesel(belongs_to(crate::source::community::Community)) @@ -145,6 +150,8 @@ pub struct CommunityUpdateForm { pub struct CommunityModerator { pub community_id: CommunityId, pub person_id: PersonId, + #[diesel(select_expression = community_actions::became_moderator.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, } @@ -157,7 +164,10 @@ pub struct CommunityModeratorForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] +#[cfg_attr( + feature = "full", + derive(Identifiable, Queryable, Selectable, Associations) +)] #[cfg_attr( feature = "full", diesel(belongs_to(crate::source::community::Community)) @@ -168,7 +178,11 @@ pub struct CommunityModeratorForm { pub struct CommunityPersonBan { pub community_id: CommunityId, pub person_id: PersonId, + #[diesel(select_expression = community_actions::received_ban.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, + #[diesel(select_expression = community_actions::ban_expires.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub expires: Option>, } @@ -183,7 +197,10 @@ pub struct CommunityPersonBanForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] +#[cfg_attr( + feature = "full", + derive(Identifiable, Queryable, Selectable, Associations) +)] #[cfg_attr( feature = "full", diesel(belongs_to(crate::source::community::Community)) @@ -194,7 +211,11 @@ pub struct CommunityPersonBanForm { pub struct CommunityFollower { pub community_id: CommunityId, pub person_id: PersonId, + #[diesel(select_expression = community_actions::followed.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, + #[diesel(select_expression = community_actions::follow_pending.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub pending: bool, } diff --git a/crates/db_schema/src/source/community_block.rs b/crates/db_schema/src/source/community_block.rs index d46ade3e82..845238c7e3 100644 --- a/crates/db_schema/src/source/community_block.rs +++ b/crates/db_schema/src/source/community_block.rs @@ -2,10 +2,15 @@ use crate::newtypes::{CommunityId, PersonId}; #[cfg(feature = "full")] use crate::schema::community_actions; use chrono::{DateTime, Utc}; +#[cfg(feature = "full")] +use diesel::{dsl, expression_methods::NullableExpressionMethods}; use serde::{Deserialize, Serialize}; #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))] +#[cfg_attr( + feature = "full", + derive(Queryable, Selectable, Associations, Identifiable) +)] #[cfg_attr( feature = "full", diesel(belongs_to(crate::source::community::Community)) @@ -16,6 +21,8 @@ use serde::{Deserialize, Serialize}; pub struct CommunityBlock { pub person_id: PersonId, pub community_id: CommunityId, + #[diesel(select_expression = community_actions::blocked.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, } diff --git a/crates/db_schema/src/source/instance_block.rs b/crates/db_schema/src/source/instance_block.rs index 237b21bc9f..530a9dd8dd 100644 --- a/crates/db_schema/src/source/instance_block.rs +++ b/crates/db_schema/src/source/instance_block.rs @@ -2,10 +2,15 @@ use crate::newtypes::{InstanceId, PersonId}; #[cfg(feature = "full")] use crate::schema::instance_actions; use chrono::{DateTime, Utc}; +#[cfg(feature = "full")] +use diesel::{dsl, expression_methods::NullableExpressionMethods}; use serde::{Deserialize, Serialize}; #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))] +#[cfg_attr( + feature = "full", + derive(Queryable, Selectable, Associations, Identifiable) +)] #[cfg_attr( feature = "full", diesel(belongs_to(crate::source::instance::Instance)) @@ -16,6 +21,8 @@ use serde::{Deserialize, Serialize}; pub struct InstanceBlock { pub person_id: PersonId, pub instance_id: InstanceId, + #[diesel(select_expression = instance_actions::blocked.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, } diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index 4e52b23a74..5f261f99d9 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -6,6 +6,8 @@ use crate::{ source::placeholder_apub_url, }; use chrono::{DateTime, Utc}; +#[cfg(feature = "full")] +use diesel::{dsl, expression_methods::ExpressionMethods}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -114,7 +116,10 @@ pub struct PersonUpdateForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] +#[cfg_attr( + feature = "full", + derive(Identifiable, Queryable, Selectable, Associations) +)] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))] #[cfg_attr(feature = "full", diesel(table_name = person_actions))] #[cfg_attr(feature = "full", diesel(primary_key(follower_id, person_id)))] @@ -122,7 +127,11 @@ pub struct PersonUpdateForm { pub struct PersonFollower { pub person_id: PersonId, pub follower_id: PersonId, + #[diesel(select_expression = person_actions::followed.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, + #[diesel(select_expression = person_actions::follow_pending.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub pending: bool, } diff --git a/crates/db_schema/src/source/person_block.rs b/crates/db_schema/src/source/person_block.rs index 0d3f8118c3..82c82b22fd 100644 --- a/crates/db_schema/src/source/person_block.rs +++ b/crates/db_schema/src/source/person_block.rs @@ -2,10 +2,15 @@ use crate::newtypes::PersonId; #[cfg(feature = "full")] use crate::schema::person_actions; use chrono::{DateTime, Utc}; +#[cfg(feature = "full")] +use diesel::{dsl, expression_methods::NullableExpressionMethods}; use serde::{Deserialize, Serialize}; #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] -#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))] +#[cfg_attr( + feature = "full", + derive(Queryable, Selectable, Associations, Identifiable) +)] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))] #[cfg_attr(feature = "full", diesel(table_name = person_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, target_id)))] @@ -13,6 +18,8 @@ use serde::{Deserialize, Serialize}; pub struct PersonBlock { pub person_id: PersonId, pub target_id: PersonId, + #[diesel(select_expression = person_actions::blocked.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, } diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 1e980ef348..1dd61bae56 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -2,6 +2,8 @@ use crate::newtypes::{CommunityId, DbUrl, LanguageId, PersonId, PostId}; #[cfg(feature = "full")] use crate::schema::{post, post_actions}; use chrono::{DateTime, Utc}; +#[cfg(feature = "full")] +use diesel::{dsl, expression_methods::NullableExpressionMethods}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -119,7 +121,10 @@ pub struct PostUpdateForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] +#[cfg_attr( + feature = "full", + derive(Identifiable, Queryable, Selectable, Associations) +)] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))] @@ -127,7 +132,11 @@ pub struct PostUpdateForm { pub struct PostLike { pub post_id: PostId, pub person_id: PersonId, + #[diesel(select_expression = post_actions::like_score.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub score: i16, + #[diesel(select_expression = post_actions::liked.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, } @@ -142,7 +151,10 @@ pub struct PostLikeForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] +#[cfg_attr( + feature = "full", + derive(Identifiable, Queryable, Selectable, Associations) +)] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))] @@ -150,6 +162,8 @@ pub struct PostLikeForm { pub struct PostSaved { pub post_id: PostId, pub person_id: PersonId, + #[diesel(select_expression = post_actions::saved.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, } @@ -161,7 +175,10 @@ pub struct PostSavedForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] +#[cfg_attr( + feature = "full", + derive(Identifiable, Queryable, Selectable, Associations) +)] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))] @@ -169,6 +186,8 @@ pub struct PostSavedForm { pub struct PostRead { pub post_id: PostId, pub person_id: PersonId, + #[diesel(select_expression = post_actions::read.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, } @@ -180,7 +199,10 @@ pub(crate) struct PostReadForm { } #[derive(PartialEq, Eq, Debug)] -#[cfg_attr(feature = "full", derive(Identifiable, Queryable, Associations))] +#[cfg_attr( + feature = "full", + derive(Identifiable, Queryable, Selectable, Associations) +)] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))] #[cfg_attr(feature = "full", diesel(table_name = post_actions))] #[cfg_attr(feature = "full", diesel(primary_key(person_id, post_id)))] @@ -188,6 +210,8 @@ pub(crate) struct PostReadForm { pub struct PostHide { pub post_id: PostId, pub person_id: PersonId, + #[diesel(select_expression = post_actions::hidden.assume_not_null())] + #[diesel(select_expression_type = dsl::AssumeNotNull)] pub published: DateTime, } From b23429cfe99587f3df838a78d301f9c7506e1b23 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 11:13:19 -0700 Subject: [PATCH 164/241] revert some changes in views --- crates/db_views/src/comment_report_view.rs | 36 +++++++++------- crates/db_views/src/comment_view.rs | 12 +++++- crates/db_views/src/post_report_view.rs | 42 +++++++++++-------- crates/db_views/src/post_view.rs | 17 ++++++-- .../db_views_actor/src/comment_reply_view.rs | 30 +++++++++---- .../db_views_actor/src/person_mention_view.rs | 30 +++++++++---- 6 files changed, 114 insertions(+), 53 deletions(-) diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index 806eb47bf4..94e65f96ee 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -46,12 +46,11 @@ fn queries<'a>() -> Queries< > { let all_joins = |query: comment_report::BoxedQuery<'a, Pg>, my_person_id: PersonId| { query + .inner_join(comment::table) + .inner_join(post::table.on(comment::post_id.eq(post::id))) + .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(person::table.on(comment_report::creator_id.eq(person::id))) - .inner_join( - comment::table - .inner_join(post::table.inner_join(community::table)) - .inner_join(aliases::person1.left_join(local_user::table)), - ) + .inner_join(aliases::person1.on(comment::creator_id.eq(aliases::person1.field(person::id)))) .inner_join( comment_aggregates::table.on(comment_report::comment_id.eq(comment_aggregates::comment_id)), ) @@ -60,6 +59,22 @@ fn queries<'a>() -> Queries< Some(my_person_id), comment_report::comment_id, )) + .left_join( + aliases::person2 + .on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), + ) + .left_join(actions_alias( + creator_community_actions, + comment::creator_id, + post::community_id, + )) + .left_join( + local_user::table.on( + comment::creator_id + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ), + ) .left_join(actions( person_actions::table, Some(my_person_id), @@ -70,15 +85,6 @@ fn queries<'a>() -> Queries< Some(my_person_id), post::community_id, )) - .left_join( - aliases::person2 - .on(comment_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), - ) - .left_join(actions_alias( - creator_community_actions, - comment::creator_id, - post::community_id, - )) .select(( comment_report::all_columns, comment::all_columns, @@ -104,7 +110,7 @@ fn queries<'a>() -> Queries< .field(community_actions::became_moderator) .nullable() .is_not_null(), - coalesce(local_user::admin.nullable(), false), + local_user::admin.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(), CommunityFollower::select_subscribed_type(), comment_actions::saved.nullable().is_not_null(), diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index babc3592eb..6ea2374bf5 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -50,9 +50,17 @@ fn queries<'a>() -> Queries< impl ReadFn<'a, CommentView, (CommentId, Option)>, impl ListFn<'a, CommentView, CommentQuery<'a>>, > { + let creator_is_admin = exists( + local_user::table.filter( + comment::creator_id + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ), + ); + let all_joins = move |query: comment::BoxedQuery<'a, Pg>, my_person_id: Option| { query - .inner_join(person::table.left_join(local_user::table)) + .inner_join(person::table) .inner_join(post::table) .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(comment_aggregates::table) @@ -96,7 +104,7 @@ fn queries<'a>() -> Queries< .field(community_actions::became_moderator) .nullable() .is_not_null(), - coalesce(local_user::admin.nullable(), false), + creator_is_admin, CommunityFollower::select_subscribed_type(), comment_actions::saved.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(), diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/post_report_view.rs index ab8bf4fee0..b7827dc790 100644 --- a/crates/db_views/src/post_report_view.rs +++ b/crates/db_views/src/post_report_view.rs @@ -44,33 +44,38 @@ fn queries<'a>() -> Queries< > { let all_joins = |query: post_report::BoxedQuery<'a, Pg>, my_person_id: PersonId| { query - .inner_join( - post::table - .inner_join(community::table) - .inner_join(aliases::person1.left_join(local_user::table)), - ) + .inner_join(post::table) + .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(person::table.on(post_report::creator_id.eq(person::id))) - .inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id))) - .left_join( - aliases::person2 - .on(post_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), - ) - .left_join(actions(post_actions::table, Some(my_person_id), post::id)) + .inner_join(aliases::person1.on(post::creator_id.eq(aliases::person1.field(person::id)))) .left_join(actions_alias( creator_community_actions, post::creator_id, post::community_id, )) .left_join(actions( - person_actions::table, + community_actions::table, Some(my_person_id), - post::creator_id, + post::community_id, )) + .left_join( + local_user::table.on( + post::creator_id + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ), + ) + .left_join(actions(post_actions::table, Some(my_person_id), post::id)) .left_join(actions( - community_actions::table, + person_actions::table, Some(my_person_id), - post::community_id, + post::creator_id, )) + .inner_join(post_aggregates::table.on(post_report::post_id.eq(post_aggregates::post_id))) + .left_join( + aliases::person2 + .on(post_report::resolver_id.eq(aliases::person2.field(person::id).nullable())), + ) .select(( post_report::all_columns, post::all_columns, @@ -85,14 +90,17 @@ fn queries<'a>() -> Queries< .field(community_actions::became_moderator) .nullable() .is_not_null(), - coalesce(local_user::admin.nullable(), false), + local_user::admin.nullable().is_not_null(), CommunityFollower::select_subscribed_type(), post_actions::saved.nullable().is_not_null(), post_actions::read.nullable().is_not_null(), post_actions::hidden.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(), post_actions::like_score.nullable(), - post_aggregates::comments - coalesce(post_actions::read_comments_amount.nullable(), 0), + coalesce( + post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(), + post_aggregates::comments, + ), post_aggregates::all_columns, aliases::person2.fields(person::all_columns.nullable()), )) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index f0ffc4d9c3..aa901db8da 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -59,10 +59,18 @@ fn queries<'a>() -> Queries< impl ReadFn<'a, PostView, (PostId, Option, bool)>, impl ListFn<'a, PostView, (PostQuery<'a>, &'a Site)>, > { + let creator_is_admin = exists( + local_user::table.filter( + comment::creator_id + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ), + ); + let all_joins = move |query: post_aggregates::BoxedQuery<'a, Pg>, my_person_id: Option| { query - .inner_join(person::table.left_join(local_user::table)) + .inner_join(person::table) .inner_join(community::table) .inner_join(post::table) .left_join(actions( @@ -103,7 +111,7 @@ fn queries<'a>() -> Queries< .field(community_actions::became_moderator) .nullable() .is_not_null(), - coalesce(local_user::admin.nullable(), false), + creator_is_admin, post_aggregates::all_columns, CommunityFollower::select_subscribed_type(), post_actions::saved.nullable().is_not_null(), @@ -111,7 +119,10 @@ fn queries<'a>() -> Queries< post_actions::hidden.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(), post_actions::like_score.nullable(), - post_aggregates::comments - coalesce(post_actions::read_comments_amount.nullable(), 0), + coalesce( + post_aggregates::comments.nullable() - post_actions::read_comments_amount.nullable(), + post_aggregates::comments, + ), )) }; diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index 7c9bf14681..956f449b3c 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -1,5 +1,12 @@ use crate::structs::CommentReplyView; -use diesel::{pg::Pg, result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl}; +use diesel::{ + dsl::exists, + pg::Pg, + result::Error, + ExpressionMethods, + NullableExpressionMethods, + QueryDsl, +}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, @@ -36,16 +43,23 @@ fn queries<'a>() -> Queries< impl ReadFn<'a, CommentReplyView, (CommentReplyId, Option)>, impl ListFn<'a, CommentReplyView, CommentReplyQuery>, > { + let creator_is_admin = exists( + local_user::table.filter( + comment::creator_id + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ), + ); + let all_joins = move |query: comment_reply::BoxedQuery<'a, Pg>, my_person_id: Option| { query - .inner_join( - comment::table - .inner_join(person::table.left_join(local_user::table)) - .inner_join(post::table.inner_join(community::table)) - .inner_join(comment_aggregates::table), - ) + .inner_join(comment::table) + .inner_join(person::table.on(comment::creator_id.eq(person::id))) + .inner_join(post::table.on(comment::post_id.eq(post::id))) + .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(aliases::person1) + .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id))) .left_join(actions(comment_actions::table, my_person_id, comment::id)) .left_join(actions( community_actions::table, @@ -79,7 +93,7 @@ fn queries<'a>() -> Queries< .field(community_actions::became_moderator) .nullable() .is_not_null(), - coalesce(local_user::admin.nullable(), false), + creator_is_admin, CommunityFollower::select_subscribed_type(), comment_actions::saved.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(), diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index 3a39943ae3..a048b7d6d0 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -1,5 +1,12 @@ use crate::structs::PersonMentionView; -use diesel::{pg::Pg, result::Error, ExpressionMethods, NullableExpressionMethods, QueryDsl}; +use diesel::{ + dsl::exists, + pg::Pg, + result::Error, + ExpressionMethods, + NullableExpressionMethods, + QueryDsl, +}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::{self, creator_community_actions}, @@ -36,16 +43,23 @@ fn queries<'a>() -> Queries< impl ReadFn<'a, PersonMentionView, (PersonMentionId, Option)>, impl ListFn<'a, PersonMentionView, PersonMentionQuery>, > { + let creator_is_admin = exists( + local_user::table.filter( + comment::creator_id + .eq(local_user::person_id) + .and(local_user::admin.eq(true)), + ), + ); + let all_joins = move |query: person_mention::BoxedQuery<'a, Pg>, my_person_id: Option| { query - .inner_join( - comment::table - .inner_join(person::table.left_join(local_user::table)) - .inner_join(post::table.inner_join(community::table)) - .inner_join(comment_aggregates::table), - ) + .inner_join(comment::table) + .inner_join(person::table.on(comment::creator_id.eq(person::id))) + .inner_join(post::table.on(comment::post_id.eq(post::id))) + .inner_join(community::table.on(post::community_id.eq(community::id))) .inner_join(aliases::person1) + .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id))) .left_join(actions( community_actions::table, my_person_id, @@ -79,7 +93,7 @@ fn queries<'a>() -> Queries< .field(community_actions::became_moderator) .nullable() .is_not_null(), - coalesce(local_user::admin.nullable(), false), + creator_is_admin, CommunityFollower::select_subscribed_type(), comment_actions::saved.nullable().is_not_null(), person_actions::blocked.nullable().is_not_null(), From 602db61ec7df1efd33b401eb82451ab4d794a5d0 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 11:20:59 -0700 Subject: [PATCH 165/241] Update post_view.rs --- crates/db_views/src/post_view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index aa901db8da..ec3bfd01c9 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -60,7 +60,7 @@ fn queries<'a>() -> Queries< impl ListFn<'a, PostView, (PostQuery<'a>, &'a Site)>, > { let creator_is_admin = exists( - local_user::table.filter( + post::table.filter( comment::creator_id .eq(local_user::person_id) .and(local_user::admin.eq(true)), From 56ba24be14132c5c659e136c90f8383d33a08d22 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 11:29:54 -0700 Subject: [PATCH 166/241] Update comment.rs --- crates/db_schema/src/source/comment.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index 883719ba28..44759fbffc 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -5,7 +5,7 @@ use crate::newtypes::{CommentId, DbUrl, LanguageId, PersonId, PostId}; use crate::schema::{comment, comment_actions}; use chrono::{DateTime, Utc}; #[cfg(feature = "full")] -use diesel::{dsl, expression_methods::NullableExpressionMethods} +use diesel::{dsl, expression_methods::NullableExpressionMethods}; #[cfg(feature = "full")] use diesel_ltree::Ltree; use serde::{Deserialize, Serialize}; From 1c1249ab9eca98ca925e3e82681bc1795044093a Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 12:39:22 -0700 Subject: [PATCH 167/241] Update post.rs --- crates/db_schema/src/source/post.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 1dd61bae56..da253253d7 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -132,11 +132,11 @@ pub struct PostUpdateForm { pub struct PostLike { pub post_id: PostId, pub person_id: PersonId, - #[diesel(select_expression = post_actions::like_score.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = post_actions::like_score.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub score: i16, - #[diesel(select_expression = post_actions::liked.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = post_actions::liked.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, } @@ -162,8 +162,8 @@ pub struct PostLikeForm { pub struct PostSaved { pub post_id: PostId, pub person_id: PersonId, - #[diesel(select_expression = post_actions::saved.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = post_actions::saved.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, } @@ -186,8 +186,8 @@ pub struct PostSavedForm { pub struct PostRead { pub post_id: PostId, pub person_id: PersonId, - #[diesel(select_expression = post_actions::read.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = post_actions::read.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, } @@ -210,8 +210,8 @@ pub(crate) struct PostReadForm { pub struct PostHide { pub post_id: PostId, pub person_id: PersonId, - #[diesel(select_expression = post_actions::hidden.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = post_actions::hidden.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, } From 89a7f6a02871b7c73a287039ac12dc6a7f8166f4 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 12:57:22 -0700 Subject: [PATCH 168/241] fix missing cfg_attr --- crates/db_schema/src/aggregates/structs.rs | 8 ++++---- crates/db_schema/src/source/comment.rs | 12 +++++------ crates/db_schema/src/source/community.rs | 20 +++++++++---------- .../db_schema/src/source/community_block.rs | 4 ++-- crates/db_schema/src/source/instance_block.rs | 4 ++-- crates/db_schema/src/source/person.rs | 8 ++++---- crates/db_schema/src/source/person_block.rs | 4 ++-- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/crates/db_schema/src/aggregates/structs.rs b/crates/db_schema/src/aggregates/structs.rs index dcd9278688..c2e54ae5cb 100644 --- a/crates/db_schema/src/aggregates/structs.rs +++ b/crates/db_schema/src/aggregates/structs.rs @@ -164,11 +164,11 @@ pub struct PersonPostAggregates { /// The number of comments they've read on that post. /// /// This is updated to the current post comment count every time they view a post. - #[diesel(select_expression = post_actions::read_comments_amount.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = post_actions::read_comments_amount.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub read_comments: i64, - #[diesel(select_expression = post_actions::read_comments.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = post_actions::read_comments.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, } diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index 44759fbffc..14b2bd20bd 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -101,11 +101,11 @@ pub struct CommentUpdateForm { pub struct CommentLike { pub person_id: PersonId, pub comment_id: CommentId, - #[diesel(select_expression = comment_actions::like_score.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = comment_actions::like_score.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub score: i16, - #[diesel(select_expression = comment_actions::liked.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = comment_actions::liked.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, } @@ -131,8 +131,8 @@ pub struct CommentLikeForm { pub struct CommentSaved { pub comment_id: CommentId, pub person_id: PersonId, - #[diesel(select_expression = comment_actions::saved.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = comment_actions::saved.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, } diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index 0b5ce60da2..0f0d078f0b 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -150,8 +150,8 @@ pub struct CommunityUpdateForm { pub struct CommunityModerator { pub community_id: CommunityId, pub person_id: PersonId, - #[diesel(select_expression = community_actions::became_moderator.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = community_actions::became_moderator.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, } @@ -178,11 +178,11 @@ pub struct CommunityModeratorForm { pub struct CommunityPersonBan { pub community_id: CommunityId, pub person_id: PersonId, - #[diesel(select_expression = community_actions::received_ban.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = community_actions::received_ban.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, - #[diesel(select_expression = community_actions::ban_expires.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = community_actions::ban_expires.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub expires: Option>, } @@ -211,11 +211,11 @@ pub struct CommunityPersonBanForm { pub struct CommunityFollower { pub community_id: CommunityId, pub person_id: PersonId, - #[diesel(select_expression = community_actions::followed.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = community_actions::followed.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, - #[diesel(select_expression = community_actions::follow_pending.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = community_actions::follow_pending.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub pending: bool, } diff --git a/crates/db_schema/src/source/community_block.rs b/crates/db_schema/src/source/community_block.rs index 845238c7e3..a7c23419c9 100644 --- a/crates/db_schema/src/source/community_block.rs +++ b/crates/db_schema/src/source/community_block.rs @@ -21,8 +21,8 @@ use serde::{Deserialize, Serialize}; pub struct CommunityBlock { pub person_id: PersonId, pub community_id: CommunityId, - #[diesel(select_expression = community_actions::blocked.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = community_actions::blocked.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, } diff --git a/crates/db_schema/src/source/instance_block.rs b/crates/db_schema/src/source/instance_block.rs index 530a9dd8dd..e1963c8946 100644 --- a/crates/db_schema/src/source/instance_block.rs +++ b/crates/db_schema/src/source/instance_block.rs @@ -21,8 +21,8 @@ use serde::{Deserialize, Serialize}; pub struct InstanceBlock { pub person_id: PersonId, pub instance_id: InstanceId, - #[diesel(select_expression = instance_actions::blocked.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = instance_actions::blocked.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, } diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index 5f261f99d9..6dd792f1a6 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -127,11 +127,11 @@ pub struct PersonUpdateForm { pub struct PersonFollower { pub person_id: PersonId, pub follower_id: PersonId, - #[diesel(select_expression = person_actions::followed.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = person_actions::followed.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, - #[diesel(select_expression = person_actions::follow_pending.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = person_actions::follow_pending.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub pending: bool, } diff --git a/crates/db_schema/src/source/person_block.rs b/crates/db_schema/src/source/person_block.rs index 82c82b22fd..ec988a60f4 100644 --- a/crates/db_schema/src/source/person_block.rs +++ b/crates/db_schema/src/source/person_block.rs @@ -18,8 +18,8 @@ use serde::{Deserialize, Serialize}; pub struct PersonBlock { pub person_id: PersonId, pub target_id: PersonId, - #[diesel(select_expression = person_actions::blocked.assume_not_null())] - #[diesel(select_expression_type = dsl::AssumeNotNull)] + #[cfg_attr(feature = "full", diesel(select_expression = person_actions::blocked.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, } From aa157d473988a190daa60d9c6a4b1acfa788a2b4 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 14:13:33 -0700 Subject: [PATCH 169/241] rewrite uplete --- crates/db_schema/src/utils/uplete.rs | 194 ++++++++++++++++++--------- 1 file changed, 129 insertions(+), 65 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 685dd8c866..d958eab0f2 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -1,115 +1,179 @@ use diesel::{ + associations::HasTable, dsl, expression::{AsExpression, TypedExpressionType}, pg::Pg, - query_builder::{AstPass, Query, QueryFragment, UpdateStatement}, + query_builder::{ + methods::{SelectDsl, FilterDsl}, + AstPass, + AsQuery, + Query, + QueryFragment, + UpdateStatement, + }, result::Error, sql_types, + Column, QueryId, Table, }; +use tuplex::{IntoArray, Len}; -pub trait UpleteTable: Table + Default { - type EmptyRow; +pub type CountSqlType = (sql_types::BigInt, sql_types::BigInt); + +/// Set columns to null and delete the row if all columns not in the primary key are null +pub fn uplete(query: Q) -> UpleteBuilder { + UpleteBuilder { + query, + set_null_columns: Vec::new(), + } } -pub trait OrDelete { - type Output; +pub struct UpleteBuilder { + query: Q, + set_null_columns: Vec, +} - /// Change an update query so rows that equal `UpleteTable::EmptyRow::default()` are deleted - fn or_delete(self) -> Self::Output; +impl UpleteBuilder { + pub fn set_null + Into>(mut self, column: C) -> Self { + self.set_null_columns.push(column.into()); + self + } } -impl OrDelete for UpdateStatement +impl AsQuery for UpleteBuilder where - T::SqlType: sql_types::SqlType + TypedExpressionType, - T::EmptyRow: Default + AsExpression>, + Q::Table: Default + Table, + Q::Table::AllColumns: IntoArray, + Q::Query: SelectDsl<(K0, K1)>, + dsl::Select: Clone + FilterDsl + FilterDsl>, { - type Output = SetOrDeleteQuery< - T, - T::PrimaryKey, - T::AllColumns, - Self, - dsl::AsExprOf>, - >; - - fn or_delete(self) -> Self::Output { - SetOrDeleteQuery { - table: T::default(), - primary_key: T::default().primary_key(), - all_columns: T::all_columns(), - update_statement: self, - empty_row: T::EmptyRow::default().as_expression(), + type Query = UpleteQuery; + + type SqlType = CountSqlType; + + fn as_query(self) -> Self::Query { + let primary_key = Q::Table::default().primary_key(); + let primary_key_type_ids = [primary_key.0.type_id(), primary_key.1.type_id()]; + let other_columns = Q::Table::all_columns().into_array().into_iter().filter(|c: DynColumn| { + primary_key_type_ids + .iter() + .chain(self.set_null_columns.iter().map(|c| c.type_id())) + .all(|other| other != c.type_id()); + }).collect::>(); + let subquery = self.query.select(primary_key.clone()); + UpleteQuery { + // Updated rows and deleted rows must not overlap, so updating all rows and using the returned new rows to determine which ones to delete is not an option. + // + // https://www.postgresql.org/docs/16/queries-with.html#QUERIES-WITH-MODIFYING + // + // "Trying to update the same row twice in a single statement is not supported. Only one of the modifications takes place, but it is not easy (and sometimes not possible) to reliably predict which one. This also applies to deleting a row that was already updated in the same statement: only the update is performed." + update_subquery: Box::new(subquery.clone().filter(not(AllNull(other_columns.clone())))), + delete_subquery: Box::new(subquery.filter(AllNull(other_columns))), + table: Box::new(Q::Table::default()), + primary_key: Box::new(primary_key), + set_null_columns: self.set_null_columns, } } } -#[derive(QueryId)] -pub struct SetOrDeleteQuery { - table: T, - primary_key: PK, - all_columns: C, - update_statement: U, - empty_row: E, +pub struct UpleteQuery { + update_subquery: Box>, + delete_subquery: Box>, + table: Box>, + primary_key: Box>, + set_null_columns: Vec, } -impl Query for SetOrDeleteQuery { - type SqlType = (sql_types::BigInt, sql_types::BigInt); +impl QueryId for UpleteQuery { + type QueryId = (); + + const HAS_STATIC_QUERY_ID: bool = false; } -impl< - T: QueryFragment, - PK: QueryFragment, - C: QueryFragment, - U: QueryFragment, - E: QueryFragment, - > QueryFragment for SetOrDeleteQuery -{ +impl QueryFragment for UpleteQuery { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { - // `update_result` CTE with new rows (concurrent writers to these rows are blocked until this - // query ends) - out.push_sql("WITH update_result AS ("); - self.update_statement.walk_ast(out.reborrow())?; - out.push_sql(" RETURNING "); - self.all_columns.walk_ast(out.reborrow())?; - - // Beginning of `delete_result` CTE with 1 row per deleted row - out.push_sql("), delete_result AS (DELETE FROM "); + assert_ne!(self.set_null_columns.len(), 0, "`set_null` was not called"); + + // Which rows to update + out.push_sql("WITH update_keys AS ("); + self.update_subquery.walk_ast(out.reborrow())?; + out.push_sql(" FOR UPDATE)"); + + // Which rows to delete + out.push_sql(", delete_keys AS ("); + self.delete_subquery.walk_ast(out.reborrow())?; + out.push_sql(" FOR UPDATE)"); + + // Update rows + out.push_sql(", update_result AS (UPDATE "); self.table.walk_ast(out.reborrow())?; + let mut item_prefix = " SET "; + for column in &self.set_null_columns { + out.push_sql(item_prefix); + column.walk_ast(out.reborrow())?; + out.push_sql(" = NULL"); + item_prefix = ","; + } out.push_sql(" WHERE ("); self.primary_key.walk_ast(out.reborrow())?; + out.push_sql(") = ANY (SELECT * FROM update_keys) RETURNING 1)"); - // Select from `update_result` with an alias that matches the original table's name - out.push_sql(") = ANY (SELECT "); - self.primary_key.walk_ast(out.reborrow())?; - out.push_sql(" FROM update_result AS "); + // Delete rows + out.push_sql(", delete_result AS (DELETE FROM "); self.table.walk_ast(out.reborrow())?; - - // Filter the select statement out.push_sql(" WHERE ("); - self.all_columns.walk_ast(out.reborrow())?; - out.push_sql(") IS NOT DISTINCT FROM "); - self.empty_row.walk_ast(out.reborrow())?; + self.primary_key.walk_ast(out.reborrow())?; + out.push_sql(") = ANY (SELECT * FROM update_keys) RETURNING 1)"); - // Select count from each CTE - out.push_sql(") RETURNING 1) SELECT (SELECT count(*) from update_result), (SELECT count(*) FROM delete_result)"); + // Count updated rows and deleted rows (`RETURNING 1` makes this possible) + out.push_sql(" SELECT (SELECT count(*) FROM update_result), (SELECT count(*) FROM delete_result)"); Ok(()) } } +struct AllNull(Vec); + +impl Expression for AllNull { + type SqlType = sql_types::Bool; +} + +impl QueryFragment for AllNull { + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { + let mut item_prefix = "("; + for column in &self.0 { + out.push_sql(item_prefix); + column.walk_ast(out.reborrow())?; + out.push_sql(" IS NOT NULL"); + item_prefix = " AND "; + } + out.push_sql(")"); + + Ok(()) + } +} + +struct DynColumn(Box>); + +impl> From for DynColumn { + fn from(value: T) -> Self { + DynColumn(Box::new(value)) + } +} + #[derive(Queryable, PartialEq, Eq, Debug)] pub struct UpleteCount { - pub all: i64, + pub updated: i64, pub deleted: i64, } impl UpleteCount { pub fn only_updated(n: i64) -> Self { - UpleteCount { all: n, deleted: 0 } + UpleteCount { updated: n, deleted: 0 } } pub fn only_deleted(n: i64) -> Self { - UpleteCount { all: n, deleted: n } + UpleteCount { updated: 0, deleted: n } } } From 216b6d2c8838e3f0ccafa199127311dd28babe9b Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 14:14:49 -0700 Subject: [PATCH 170/241] Update Cargo.toml --- crates/db_schema/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index 5adc04dc1f..98c035d6ea 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -39,6 +39,7 @@ full = [ "rustls", "i-love-jesus", "diesel-bind-if-some", + "tuplex", ] [dependencies] @@ -83,6 +84,7 @@ i-love-jesus = { workspace = true, optional = true } anyhow = { workspace = true } diesel-bind-if-some = { workspace = true, optional = true } moka.workspace = true +tuplex = { workspace = true } [dev-dependencies] serial_test = { workspace = true } From db20b69de25cbbb8f294bd0c0aa9e2ccbf82d816 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 14:15:59 -0700 Subject: [PATCH 171/241] Update Cargo.toml --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 83d12d8130..e92c6d35a2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -168,6 +168,7 @@ i-love-jesus = { version = "0.1.0" } clap = { version = "4.5.4", features = ["derive", "env"] } pretty_assertions = "1.4.0" diesel-bind-if-some = "0.1.0" +tuplex = "0.1.2" [dependencies] lemmy_api = { workspace = true } From 7f8a689e81575a61bffc9a40c387aaeea23a7560 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 14:21:15 -0700 Subject: [PATCH 172/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 41 +++++++++++++++++++--------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index d958eab0f2..db9bee21f1 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -4,9 +4,9 @@ use diesel::{ expression::{AsExpression, TypedExpressionType}, pg::Pg, query_builder::{ - methods::{SelectDsl, FilterDsl}, - AstPass, + methods::{FilterDsl, SelectDsl}, AsQuery, + AstPass, Query, QueryFragment, UpdateStatement, @@ -55,19 +55,27 @@ where fn as_query(self) -> Self::Query { let primary_key = Q::Table::default().primary_key(); let primary_key_type_ids = [primary_key.0.type_id(), primary_key.1.type_id()]; - let other_columns = Q::Table::all_columns().into_array().into_iter().filter(|c: DynColumn| { - primary_key_type_ids - .iter() - .chain(self.set_null_columns.iter().map(|c| c.type_id())) - .all(|other| other != c.type_id()); - }).collect::>(); + let other_columns = Q::Table::all_columns() + .into_array() + .into_iter() + .filter(|c: DynColumn| { + primary_key_type_ids + .iter() + .chain(self.set_null_columns.iter().map(|c| c.type_id())) + .all(|other| other != c.type_id()) + }) + .collect::>(); let subquery = self.query.select(primary_key.clone()); UpleteQuery { - // Updated rows and deleted rows must not overlap, so updating all rows and using the returned new rows to determine which ones to delete is not an option. + // Updated rows and deleted rows must not overlap, so updating all rows and using the returned + // new rows to determine which ones to delete is not an option. // // https://www.postgresql.org/docs/16/queries-with.html#QUERIES-WITH-MODIFYING // - // "Trying to update the same row twice in a single statement is not supported. Only one of the modifications takes place, but it is not easy (and sometimes not possible) to reliably predict which one. This also applies to deleting a row that was already updated in the same statement: only the update is performed." + // "Trying to update the same row twice in a single statement is not supported. Only one of + // the modifications takes place, but it is not easy (and sometimes not possible) to reliably + // predict which one. This also applies to deleting a row that was already updated in the same + // statement: only the update is performed." update_subquery: Box::new(subquery.clone().filter(not(AllNull(other_columns.clone())))), delete_subquery: Box::new(subquery.filter(AllNull(other_columns))), table: Box::new(Q::Table::default()), @@ -127,7 +135,8 @@ impl QueryFragment for UpleteQuery { out.push_sql(") = ANY (SELECT * FROM update_keys) RETURNING 1)"); // Count updated rows and deleted rows (`RETURNING 1` makes this possible) - out.push_sql(" SELECT (SELECT count(*) FROM update_result), (SELECT count(*) FROM delete_result)"); + out.push_sql(" SELECT (SELECT count(*) FROM update_result)"); + out.push_sql(", (SELECT count(*) FROM delete_result)"); Ok(()) } @@ -170,10 +179,16 @@ pub struct UpleteCount { impl UpleteCount { pub fn only_updated(n: i64) -> Self { - UpleteCount { updated: n, deleted: 0 } + UpleteCount { + updated: n, + deleted: 0, + } } pub fn only_deleted(n: i64) -> Self { - UpleteCount { updated: 0, deleted: n } + UpleteCount { + updated: 0, + deleted: n, + } } } From e6a7a5a0fdd70a7d037b2783c9c111349e59626c Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 14:22:13 -0700 Subject: [PATCH 173/241] add `pub` to structs that appear in trait bounds --- crates/db_schema/src/utils/uplete.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index db9bee21f1..9453f6f9fe 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -142,7 +142,7 @@ impl QueryFragment for UpleteQuery { } } -struct AllNull(Vec); +pub struct AllNull(Vec); impl Expression for AllNull { type SqlType = sql_types::Bool; @@ -163,7 +163,7 @@ impl QueryFragment for AllNull { } } -struct DynColumn(Box>); +pub struct DynColumn(Box>); impl> From for DynColumn { fn from(value: T) -> Self { From c1435b68426bc82c514f291f4e1fde5238692482 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 14:23:07 -0700 Subject: [PATCH 174/241] optional = true --- crates/db_schema/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index 98c035d6ea..79e15dba3e 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -84,7 +84,7 @@ i-love-jesus = { workspace = true, optional = true } anyhow = { workspace = true } diesel-bind-if-some = { workspace = true, optional = true } moka.workspace = true -tuplex = { workspace = true } +tuplex = { workspace = true, optional = true } [dev-dependencies] serial_test = { workspace = true } From cd508cc7596dacf22fd4a30e87819572d86e5b4e Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 14:42:14 -0700 Subject: [PATCH 175/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 9453f6f9fe..e9d5d4e4e2 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -1,26 +1,24 @@ use diesel::{ associations::HasTable, dsl, - expression::{AsExpression, TypedExpressionType}, pg::Pg, query_builder::{ - methods::{FilterDsl, SelectDsl}, AsQuery, AstPass, Query, QueryFragment, - UpdateStatement, + QueryId, }, + query_dsl::methods::{FilterDsl, SelectDsl}, result::Error, sql_types, Column, - QueryId, + Expression, Table, }; +use std::any::Any; use tuplex::{IntoArray, Len}; -pub type CountSqlType = (sql_types::BigInt, sql_types::BigInt); - /// Set columns to null and delete the row if all columns not in the primary key are null pub fn uplete(query: Q) -> UpleteBuilder { UpleteBuilder { @@ -76,7 +74,7 @@ where // the modifications takes place, but it is not easy (and sometimes not possible) to reliably // predict which one. This also applies to deleting a row that was already updated in the same // statement: only the update is performed." - update_subquery: Box::new(subquery.clone().filter(not(AllNull(other_columns.clone())))), + update_subquery: Box::new(subquery.clone().filter(dsl::not(AllNull(other_columns.clone())))), delete_subquery: Box::new(subquery.filter(AllNull(other_columns))), table: Box::new(Q::Table::default()), primary_key: Box::new(primary_key), @@ -99,6 +97,10 @@ impl QueryId for UpleteQuery { const HAS_STATIC_QUERY_ID: bool = false; } +impl Query for UpleteQuery { + type SqlType = (sql_types::BigInt, sql_types::BigInt); +} + impl QueryFragment for UpleteQuery { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { assert_ne!(self.set_null_columns.len(), 0, "`set_null` was not called"); From 287947ac71827ce816f137c2a2c0db8ef110e722 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 14:43:40 -0700 Subject: [PATCH 176/241] Update community.rs --- crates/db_schema/src/source/community.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index 0f0d078f0b..e79e8c5b19 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -8,7 +8,7 @@ use crate::{ }; use chrono::{DateTime, Utc}; #[cfg(feature = "full")] -use diesel::{dsl, expression_methods::NullableExplessionMethods}; +use diesel::{dsl, expression_methods::NullableExpressionMethods}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] From 4e89913cebed0d9b7400037797820f15fb9bd237 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 14:45:36 -0700 Subject: [PATCH 177/241] Update comment.rs --- crates/db_schema/src/impls/comment.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 67d9ecedf7..9690e6e4b7 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -13,7 +13,6 @@ use crate::{ }, traits::{Crud, Likeable, Saveable}, utils::{ - expression::SelectableHelper, functions::coalesce, get_conn, naive_now, @@ -25,7 +24,8 @@ use crate::{ }; use chrono::{DateTime, Utc}; use diesel::{ - dsl::{self, insert_into}, + dsl::insert_into, + expression::SelectableHelper, result::Error, ExpressionMethods, NullableExpressionMethods, From 7ed70806ae287ce4d6eae1283912b6d914ba8c48 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 14:47:56 -0700 Subject: [PATCH 178/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index e9d5d4e4e2..a262d5ec8b 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -2,13 +2,7 @@ use diesel::{ associations::HasTable, dsl, pg::Pg, - query_builder::{ - AsQuery, - AstPass, - Query, - QueryFragment, - QueryId, - }, + query_builder::{AsQuery, AstPass, Query, QueryFragment, QueryId}, query_dsl::methods::{FilterDsl, SelectDsl}, result::Error, sql_types, @@ -74,7 +68,11 @@ where // the modifications takes place, but it is not easy (and sometimes not possible) to reliably // predict which one. This also applies to deleting a row that was already updated in the same // statement: only the update is performed." - update_subquery: Box::new(subquery.clone().filter(dsl::not(AllNull(other_columns.clone())))), + update_subquery: Box::new( + subquery + .clone() + .filter(dsl::not(AllNull(other_columns.clone()))), + ), delete_subquery: Box::new(subquery.filter(AllNull(other_columns))), table: Box::new(Q::Table::default()), primary_key: Box::new(primary_key), From bc941a2981e992c39bf1c27fc0364a2a40735617 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 15:44:15 -0700 Subject: [PATCH 179/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index a262d5ec8b..5db84bc58b 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -36,13 +36,14 @@ impl UpleteBuilder { impl AsQuery for UpleteBuilder where Q::Table: Default + Table, - Q::Table::AllColumns: IntoArray, + Q::Table::AllColumns: IntoArray, + : IntoIterator, Q::Query: SelectDsl<(K0, K1)>, dsl::Select: Clone + FilterDsl + FilterDsl>, { type Query = UpleteQuery; - type SqlType = CountSqlType; + type SqlType = (sql_types::BigInt, sql_types::BigInt); fn as_query(self) -> Self::Query { let primary_key = Q::Table::default().primary_key(); @@ -163,9 +164,9 @@ impl QueryFragment for AllNull { } } -pub struct DynColumn(Box>); +pub struct DynColumn(Box + 'static>); -impl> From for DynColumn { +impl + 'static> From for DynColumn { fn from(value: T) -> Self { DynColumn(Box::new(value)) } From 19c4ba165dc3aa9c9342a59dee885b9279744ef1 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 18:40:11 -0700 Subject: [PATCH 180/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 5db84bc58b..859f35b91f 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -37,7 +37,7 @@ impl AsQuery for UpleteBuilder< where Q::Table: Default + Table, Q::Table::AllColumns: IntoArray, - : IntoIterator, + ::Output IntoIterator, Q::Query: SelectDsl<(K0, K1)>, dsl::Select: Clone + FilterDsl + FilterDsl>, { From 60af6eddc82f6b15574ac5ab7b4f4e0e39ab0bf1 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 18:43:08 -0700 Subject: [PATCH 181/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 859f35b91f..e8a021278e 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -37,7 +37,7 @@ impl AsQuery for UpleteBuilder< where Q::Table: Default + Table, Q::Table::AllColumns: IntoArray, - ::Output IntoIterator, + ::Output: IntoIterator, Q::Query: SelectDsl<(K0, K1)>, dsl::Select: Clone + FilterDsl + FilterDsl>, { From 0e441c89ab2b51d0279412921f0e878fd14a7d10 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 18:44:50 -0700 Subject: [PATCH 182/241] something is wrong with me --- crates/db_schema/src/utils/uplete.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index e8a021278e..38d3502739 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -37,7 +37,7 @@ impl AsQuery for UpleteBuilder< where Q::Table: Default + Table, Q::Table::AllColumns: IntoArray, - ::Output: IntoIterator, + >::Output: IntoIterator, Q::Query: SelectDsl<(K0, K1)>, dsl::Select: Clone + FilterDsl + FilterDsl>, { From 4751d28b7b37bbccb9e3fd3e053297104f041c88 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 28 May 2024 19:22:58 -0700 Subject: [PATCH 183/241] use new uplete function everywhere --- .../src/aggregates/person_post_aggregates.rs | 1 - crates/db_schema/src/impls/community.rs | 50 ++++++------------- crates/db_schema/src/impls/community_block.rs | 9 ++-- crates/db_schema/src/impls/instance.rs | 9 ---- crates/db_schema/src/impls/instance_block.rs | 9 ++-- crates/db_schema/src/impls/person.rs | 23 ++------- crates/db_schema/src/impls/person_block.rs | 8 ++- crates/db_schema/src/impls/post.rs | 42 ++++------------ crates/db_schema/src/source/person.rs | 2 + 9 files changed, 43 insertions(+), 110 deletions(-) diff --git a/crates/db_schema/src/aggregates/person_post_aggregates.rs b/crates/db_schema/src/aggregates/person_post_aggregates.rs index 6140a7706f..63a50af9c8 100644 --- a/crates/db_schema/src/aggregates/person_post_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_post_aggregates.rs @@ -6,7 +6,6 @@ use crate::{ utils::{find_action, get_conn, now, DbPool}, }; use diesel::{ - dsl, expression::SelectableHelper, insert_into, result::Error, diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index a270f393d6..c346df5bb1 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -20,12 +20,11 @@ use crate::{ traits::{ApubActor, Bannable, Crud, Followable, Joinable}, utils::{ action_query, - expression::SelectableHelper, find_action, functions::{coalesce, lower}, get_conn, now, - uplete::{OrDelete, UpleteCount, UpleteTable}, + uplete::{uplete, UpleteCount}, DbPool, }, SubscribedType, @@ -35,6 +34,7 @@ use diesel::{ deserialize, dsl, dsl::{exists, insert_into}, + expression::SelectableHelper, pg::Pg, result::Error, select, @@ -48,19 +48,6 @@ use diesel::{ }; use diesel_async::RunQueryDsl; -impl UpleteTable for community_actions::table { - type EmptyRow = ( - community_actions::community_id, - community_actions::person_id, - Option>, - Option, - Option>, - Option>, - Option>, - Option>, - ); -} - #[async_trait] impl Crud for Community { type InsertForm = CommunityInsertForm; @@ -124,12 +111,11 @@ impl Joinable for CommunityModerator { community_moderator_form: &CommunityModeratorForm, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(community_actions::table.find(( + uplete(community_actions::table.find(( community_moderator_form.person_id, community_moderator_form.community_id, ))) - .set(community_actions::became_moderator.eq(None::>)) - .or_delete() + .set_null(community_actions::became_moderator) .get_result(conn) .await } @@ -231,11 +217,10 @@ impl CommunityModerator { ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update( + uplete( community_actions::table.filter(community_actions::community_id.eq(for_community_id)), ) - .set(community_actions::became_moderator.eq(None::>)) - .or_delete() + .set_null(community_actions::became_moderator) .get_result(conn) .await } @@ -245,9 +230,8 @@ impl CommunityModerator { for_person_id: PersonId, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(community_actions::table.filter(community_actions::person_id.eq(for_person_id))) - .set(community_actions::became_moderator.eq(None::>)) - .or_delete() + uplete(community_actions::table.filter(community_actions::person_id.eq(for_person_id))) + .set_null(community_actions::became_moderator) .get_result(conn) .await } @@ -295,15 +279,12 @@ impl Bannable for CommunityPersonBan { community_person_ban_form: &CommunityPersonBanForm, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(community_actions::table.find(( + uplete(community_actions::table.find(( community_person_ban_form.person_id, community_person_ban_form.community_id, ))) - .set(( - community_actions::received_ban.eq(None::>), - community_actions::ban_expires.eq(None::>), - )) - .or_delete() + .set_null(community_actions::received_ban) + .set_null(community_actions::ban_expires) .get_result(conn) .await } @@ -393,12 +374,9 @@ impl Followable for CommunityFollower { form: &CommunityFollowerForm, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(community_actions::table.find((form.person_id, form.community_id))) - .set(( - community_actions::followed.eq(None::>), - community_actions::follow_pending.eq(None::), - )) - .or_delete() + uplete(community_actions::table.find((form.person_id, form.community_id))) + .set_null(community_actions::followed) + .set_null(community_actions::follow_pending) .get_result(conn) .await } diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index 72eacb85db..5e9382644c 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -7,13 +7,13 @@ use crate::{ find_action, get_conn, now, - uplete::{OrDelete, UpleteCount}, + uplete::{uplete, UpleteCount}, DbPool, }, }; use chrono::{DateTime, Utc}; use diesel::{ - dsl::{self, exists, insert_into}, + dsl::{exists, insert_into}, expression::SelectableHelper, result::Error, select, @@ -65,12 +65,11 @@ impl Blockable for CommunityBlock { community_block_form: &Self::Form, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(community_actions::table.find(( + uplete(community_actions::table.find(( community_block_form.person_id, community_block_form.community_id, ))) - .set(community_actions::blocked.eq(None::>)) - .or_delete() + .set_null(community_actions::blocked) .get_result(conn) .await } diff --git a/crates/db_schema/src/impls/instance.rs b/crates/db_schema/src/impls/instance.rs index 5b21a5515f..1a8e829e26 100644 --- a/crates/db_schema/src/impls/instance.rs +++ b/crates/db_schema/src/impls/instance.rs @@ -19,7 +19,6 @@ use crate::{ get_conn, naive_now, now, - uplete::UpleteTable, DbPool, }, }; @@ -35,14 +34,6 @@ use diesel::{ }; use diesel_async::RunQueryDsl; -impl UpleteTable for instance_actions::table { - type EmptyRow = ( - instance_actions::person_id, - instance_actions::instance_id, - Option>, - ); -} - impl Instance { /// Attempt to read Instance column for the given domain. If it doesn't exist, insert a new one. /// There is no need for update as the domain of an existing instance cant change. diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index f82452f1f7..54e938f570 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -7,13 +7,13 @@ use crate::{ find_action, get_conn, now, - uplete::{OrDelete, UpleteCount}, + uplete::{uplete, UpleteCount}, DbPool, }, }; use chrono::{DateTime, Utc}; use diesel::{ - dsl::{self, exists, insert_into}, + dsl::{exists, insert_into}, expression::SelectableHelper, result::Error, select, @@ -62,12 +62,11 @@ impl Blockable for InstanceBlock { instance_block_form: &Self::Form, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(instance_actions::table.find(( + uplete(instance_actions::table.find(( instance_block_form.person_id, instance_block_form.instance_id, ))) - .set(instance_actions::blocked.eq(None::>)) - .or_delete() + .set_null(instance_actions::blocked) .get_result(conn) .await } diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 1c7dbc7e58..4b4212d911 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -16,13 +16,13 @@ use crate::{ get_conn, naive_now, now, - uplete::{OrDelete, UpleteCount, UpleteTable}, + uplete::{uplete, UpleteCount}, DbPool, }, }; use chrono::{DateTime, Utc}; use diesel::{ - dsl::{self, insert_into}, + dsl::insert_into, expression::SelectableHelper, result::Error, CombineDsl, @@ -33,16 +33,6 @@ use diesel::{ }; use diesel_async::RunQueryDsl; -impl UpleteTable for person_actions::table { - type EmptyRow = ( - person_actions::target_id, - person_actions::person_id, - Option>, - Option, - Option>, - ); -} - #[async_trait] impl Crud for Person { type InsertForm = PersonInsertForm; @@ -224,12 +214,9 @@ impl Followable for PersonFollower { form: &PersonFollowerForm, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(person_actions::table.find((form.follower_id, form.person_id))) - .set(( - person_actions::followed.eq(None::>), - person_actions::follow_pending.eq(None::), - )) - .or_delete() + uplete(person_actions::table.find((form.follower_id, form.person_id))) + .set_null(person_actions::followed) + .set_null(person_actions::follow_pending) .get_result(conn) .await } diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index b1c17cfbc8..7a2b5a845a 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -7,13 +7,12 @@ use crate::{ find_action, get_conn, now, - uplete::{OrDelete, UpleteCount}, + uplete::{uplete, UpleteCount}, DbPool, }, }; use chrono::{DateTime, Utc}; use diesel::{ - dsl, dsl::{exists, insert_into}, expression::SelectableHelper, result::Error, @@ -66,11 +65,10 @@ impl Blockable for PersonBlock { person_block_form: &Self::Form, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update( + uplete( person_actions::table.find((person_block_form.person_id, person_block_form.target_id)), ) - .set(person_actions::blocked.eq(None::>)) - .or_delete() + .set_null(person_actions::blocked) .get_result(conn) .await } diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 06a979f83c..20a46b9f47 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -21,7 +21,7 @@ use crate::{ get_conn, naive_now, now, - uplete::{OrDelete, UpleteCount, UpleteTable}, + uplete::{uplete, UpleteCount}, DbPool, DELETED_REPLACEMENT_TEXT, FETCH_LIMIT_MAX, @@ -32,7 +32,7 @@ use crate::{ use ::url::Url; use chrono::{DateTime, Utc}; use diesel::{ - dsl::{self, insert_into}, + dsl::insert_into, expression::SelectableHelper, result::Error, DecoratableTarget, @@ -44,20 +44,6 @@ use diesel::{ use diesel_async::RunQueryDsl; use std::collections::HashSet; -impl UpleteTable for post_actions::table { - type EmptyRow = ( - post_actions::post_id, - post_actions::person_id, - Option>, - Option>, - Option, - Option>, - Option>, - Option, - Option>, - ); -} - #[async_trait] impl Crud for Post { type InsertForm = PostInsertForm; @@ -284,12 +270,9 @@ impl Likeable for PostLike { post_id: PostId, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(post_actions::table.find((person_id, post_id))) - .set(( - post_actions::like_score.eq(None::), - post_actions::liked.eq(None::>), - )) - .or_delete() + uplete(post_actions::table.find((person_id, post_id))) + .set_null(post_actions::like_score) + .set_null(post_actions::liked) .get_result(conn) .await } @@ -315,9 +298,8 @@ impl Saveable for PostSaved { post_saved_form: &PostSavedForm, ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update(post_actions::table.find((post_saved_form.person_id, post_saved_form.post_id))) - .set(post_actions::saved.eq(None::>)) - .or_delete() + uplete(post_actions::table.find((post_saved_form.person_id, post_saved_form.post_id))) + .set_null(post_actions::saved) .get_result(conn) .await } @@ -356,13 +338,12 @@ impl PostRead { ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update( + uplete( post_actions::table .filter(post_actions::post_id.eq_any(post_id_)) .filter(post_actions::person_id.eq(person_id_)), ) - .set(post_actions::read.eq(None::>)) - .or_delete() + .set_null(post_actions::read) .get_result(conn) .await } @@ -401,13 +382,12 @@ impl PostHide { ) -> Result { let conn = &mut get_conn(pool).await?; - diesel::update( + uplete( post_actions::table .filter(post_actions::post_id.eq_any(post_id_)) .filter(post_actions::person_id.eq(person_id_)), ) - .set(post_actions::hidden.eq(None::>)) - .or_delete() + .set_null(post_actions::hidden) .get_result(conn) .await } diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index 6dd792f1a6..2c7008b056 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -125,7 +125,9 @@ pub struct PersonUpdateForm { #[cfg_attr(feature = "full", diesel(primary_key(follower_id, person_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct PersonFollower { + #[cfg_attr(feature = "full", diesel(column_name = target_id))] pub person_id: PersonId, + #[cfg_attr(feature = "full", diesel(column_name = person_id))] pub follower_id: PersonId, #[cfg_attr(feature = "full", diesel(select_expression = person_actions::followed.assume_not_null()))] #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] From 591895ee384d0046127c02bea6552adbd203debf Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 09:41:46 -0700 Subject: [PATCH 184/241] fmt --- crates/db_schema/src/impls/community.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index c346df5bb1..1832e817c2 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -217,12 +217,10 @@ impl CommunityModerator { ) -> Result { let conn = &mut get_conn(pool).await?; - uplete( - community_actions::table.filter(community_actions::community_id.eq(for_community_id)), - ) - .set_null(community_actions::became_moderator) - .get_result(conn) - .await + uplete(community_actions::table.filter(community_actions::community_id.eq(for_community_id))) + .set_null(community_actions::became_moderator) + .get_result(conn) + .await } pub async fn leave_all_communities( From eb720a9d88f78888c557af79a32b9ba6bfbd5b5e Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 09:43:41 -0700 Subject: [PATCH 185/241] fmt --- crates/db_schema/src/impls/person_block.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index 7a2b5a845a..944b839776 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -65,11 +65,9 @@ impl Blockable for PersonBlock { person_block_form: &Self::Form, ) -> Result { let conn = &mut get_conn(pool).await?; - uplete( - person_actions::table.find((person_block_form.person_id, person_block_form.target_id)), - ) - .set_null(person_actions::blocked) - .get_result(conn) - .await + uplete(person_actions::table.find((person_block_form.person_id, person_block_form.target_id))) + .set_null(person_actions::blocked) + .get_result(conn) + .await } } From 6ce8f67ebffdbfade592572a1b760e4eacec1272 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 11:10:40 -0700 Subject: [PATCH 186/241] Keep post_id when comment_actions::liked is not null --- crates/api/src/comment/like.rs | 1 + crates/api_crud/src/comment/create.rs | 1 + .../src/activities/create_or_update/comment.rs | 1 + crates/apub/src/activities/voting/mod.rs | 1 + .../src/aggregates/comment_aggregates.rs | 2 ++ .../src/aggregates/person_aggregates.rs | 2 ++ crates/db_schema/src/impls/comment.rs | 6 ++++++ crates/db_schema/src/source/comment.rs | 4 ++++ crates/db_views/src/comment_view.rs | 2 ++ crates/db_views/src/vote_view.rs | 6 ++++-- .../down.sql | 18 ++---------------- .../up.sql | 6 ++++-- 12 files changed, 30 insertions(+), 20 deletions(-) diff --git a/crates/api/src/comment/like.rs b/crates/api/src/comment/like.rs index aa76b91e68..d0aa4a6c26 100644 --- a/crates/api/src/comment/like.rs +++ b/crates/api/src/comment/like.rs @@ -59,6 +59,7 @@ pub async fn like_comment( let like_form = CommentLikeForm { comment_id: data.comment_id, + post_id: orig_comment.post.id, person_id: local_user_view.person.id, score: data.score, }; diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index 48d3787c79..2efd46964f 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -160,6 +160,7 @@ pub async fn create_comment( // You like your own comment by default let like_form = CommentLikeForm { comment_id: inserted_comment.id, + post_id: post.id, person_id: local_user_view.person.id, score: 1, }; diff --git a/crates/apub/src/activities/create_or_update/comment.rs b/crates/apub/src/activities/create_or_update/comment.rs index e068b56213..2406d2eb34 100644 --- a/crates/apub/src/activities/create_or_update/comment.rs +++ b/crates/apub/src/activities/create_or_update/comment.rs @@ -160,6 +160,7 @@ impl ActivityHandler for CreateOrUpdateNote { // author likes their own comment by default let like_form = CommentLikeForm { comment_id: comment.id, + post_id: comment.post_id, person_id: comment.creator_id, score: 1, }; diff --git a/crates/apub/src/activities/voting/mod.rs b/crates/apub/src/activities/voting/mod.rs index 7c39b2246d..3e59cb7d0c 100644 --- a/crates/apub/src/activities/voting/mod.rs +++ b/crates/apub/src/activities/voting/mod.rs @@ -62,6 +62,7 @@ async fn vote_comment( let comment_id = comment.id; let like_form = CommentLikeForm { comment_id, + post_id: comment.post_id, person_id: actor.id, score: vote_type.into(), }; diff --git a/crates/db_schema/src/aggregates/comment_aggregates.rs b/crates/db_schema/src/aggregates/comment_aggregates.rs index 1baa488817..915d17b1d0 100644 --- a/crates/db_schema/src/aggregates/comment_aggregates.rs +++ b/crates/db_schema/src/aggregates/comment_aggregates.rs @@ -118,6 +118,7 @@ mod tests { let comment_like = CommentLikeForm { comment_id: inserted_comment.id, + post_id: inserted_post.id, person_id: inserted_person.id, score: 1, }; @@ -136,6 +137,7 @@ mod tests { // Add a post dislike from the other person let comment_dislike = CommentLikeForm { comment_id: inserted_comment.id, + post_id: inserted_post.id, person_id: another_inserted_person.id, score: -1, }; diff --git a/crates/db_schema/src/aggregates/person_aggregates.rs b/crates/db_schema/src/aggregates/person_aggregates.rs index 8315b5afc1..e96795e8c2 100644 --- a/crates/db_schema/src/aggregates/person_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_aggregates.rs @@ -100,6 +100,7 @@ mod tests { let mut comment_like = CommentLikeForm { comment_id: inserted_comment.id, + post_id: inserted_post.id, person_id: inserted_person.id, score: 1, }; @@ -119,6 +120,7 @@ mod tests { let child_comment_like = CommentLikeForm { comment_id: inserted_child_comment.id, + post_id: inserted_post.id, person_id: another_inserted_person.id, score: 1, }; diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 9690e6e4b7..ce24662f85 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -178,6 +178,9 @@ impl Likeable for CommentLike { uplete(comment_actions::table.find((person_id, comment_id))) .set_null(comment_actions::like_score) .set_null(comment_actions::liked) + // Deleting empty `comment_actions` rows would not work without setting `post_id` to + // null, because it's not part of the primary key + .set_null(comment_actions::post_id) .get_result(conn) .await } @@ -320,6 +323,7 @@ mod tests { // Comment Like let comment_like_form = CommentLikeForm { comment_id: inserted_comment.id, + post_id: inserted_post.id, person_id: inserted_person.id, score: 1, }; @@ -328,6 +332,7 @@ mod tests { let expected_comment_like = CommentLike { comment_id: inserted_comment.id, + post_id: inserted_post.id, person_id: inserted_person.id, published: inserted_comment_like.published, score: 1, @@ -343,6 +348,7 @@ mod tests { let expected_comment_saved = CommentSaved { comment_id: inserted_comment.id, + post_id: inserted_post.id, person_id: inserted_person.id, published: inserted_comment_saved.published, }; diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index 14b2bd20bd..57155f83e1 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -101,6 +101,9 @@ pub struct CommentUpdateForm { pub struct CommentLike { pub person_id: PersonId, pub comment_id: CommentId, + #[cfg_attr(feature = "full", diesel(select_expression = comment_actions::post_id.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] + pub post_id: PostId, // TODO this is redundant #[cfg_attr(feature = "full", diesel(select_expression = comment_actions::like_score.assume_not_null()))] #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub score: i16, @@ -115,6 +118,7 @@ pub struct CommentLike { pub struct CommentLikeForm { pub person_id: PersonId, pub comment_id: CommentId, + pub post_id: PostId, // TODO this is redundant #[cfg_attr(feature = "full", diesel(column_name = like_score))] pub score: i16, } diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 6ea2374bf5..c2b1717789 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -533,6 +533,7 @@ mod tests { let comment_like_form = CommentLikeForm { comment_id: inserted_comment_0.id, + post_id: inserted_post.id, person_id: inserted_timmy_person.id, score: 1, }; @@ -632,6 +633,7 @@ mod tests { // Like a new comment let comment_like_form = CommentLikeForm { comment_id: data.inserted_comment_1.id, + post_id: data.inserted_post.id, person_id: data.timmy_local_user_view.person.id, score: 1, }; diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index 647f152a4a..6cb4c8c87f 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -4,7 +4,7 @@ use diesel_async::RunQueryDsl; use lemmy_db_schema::{ aliases::creator_community_actions, newtypes::{CommentId, PostId}, - schema::{comment, comment_actions, community_actions, person, post, post_actions}, + schema::{comment_actions, community_actions, person, post, post_actions}, utils::{action_query, actions_alias, get_conn, limit_and_offset, DbPool}, }; @@ -53,7 +53,7 @@ impl VoteView { action_query(comment_actions::like_score) .inner_join(person::table) - .inner_join(comment::table.inner_join(post::table)) + .inner_join(post::table) .left_join(actions_alias( creator_community_actions, comment_actions::person_id, @@ -184,6 +184,7 @@ mod tests { // Timothy votes down his own comment let timmy_comment_vote_form = CommentLikeForm { comment_id: inserted_comment.id, + post_id: inserted_post.id, person_id: inserted_timmy.id, score: -1, }; @@ -194,6 +195,7 @@ mod tests { // Sara upvotes timmy's comment let sara_comment_vote_form = CommentLikeForm { comment_id: inserted_comment.id, + post_id: inserted_post.id, person_id: inserted_sara.id, score: 1, }; diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql index 68b9d9070e..4a1d4d9e4a 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql @@ -35,22 +35,8 @@ ALTER TABLE comment_like ALTER COLUMN published SET NOT NULL, ALTER COLUMN published SET DEFAULT now(), ALTER COLUMN score SET NOT NULL, - DROP COLUMN saved, - ADD COLUMN post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE; - -UPDATE - comment_like -SET - post_id = ( - SELECT - post_id - FROM - comment - WHERE - comment.id = comment_like.comment_id); - -ALTER TABLE comment_like - ALTER COLUMN post_id SET NOT NULL; + ALTER COLUMN post_id SET NOT NULL, + DROP COLUMN saved; -- Create community_follower from community_actions CREATE TABLE community_block ( diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index 90111b9c11..829d864707 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -11,12 +11,14 @@ ALTER TABLE comment_actions RENAME COLUMN published TO liked; ALTER TABLE comment_actions RENAME COLUMN score TO like_score; ALTER TABLE comment_actions - DROP COLUMN post_id, + ALTER COLUMN post_id DROP NOT NULL, ALTER COLUMN liked DROP NOT NULL, ALTER COLUMN liked DROP DEFAULT, ALTER COLUMN like_score DROP NOT NULL, ADD COLUMN saved timestamptz, - ADD CONSTRAINT comment_actions_check_liked CHECK ((liked IS NULL) = (like_score IS NULL)); + -- `post_id` was only in the `comment_liked` table, and removing it entirely or making it not null + -- for the `saved` action would make this PR too complicated + ADD CONSTRAINT comment_actions_check_liked CHECK ((liked IS NULL) = (like_score IS NULL) = (post_id IS NULL)); WITH old_comment_saved AS ( DELETE FROM comment_saved From 3c56d1b038c9bc421fd84fba743e51630260e186 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 11:28:33 -0700 Subject: [PATCH 187/241] Update up.sql --- migrations/2024-03-17-134311_smoosh-tables-together/up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index 829d864707..eede02f620 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -18,7 +18,7 @@ ALTER TABLE comment_actions ADD COLUMN saved timestamptz, -- `post_id` was only in the `comment_liked` table, and removing it entirely or making it not null -- for the `saved` action would make this PR too complicated - ADD CONSTRAINT comment_actions_check_liked CHECK ((liked IS NULL) = (like_score IS NULL) = (post_id IS NULL)); + ADD CONSTRAINT comment_actions_check_liked CHECK ((liked IS NULL) = ALL (ARRAY[like_score IS NULL, post_id IS NULL]); WITH old_comment_saved AS ( DELETE FROM comment_saved From 13a63e6d24bfe5edf3ab50fc8e5b192beeda01fa Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 11:39:30 -0700 Subject: [PATCH 188/241] Update up.sql --- migrations/2024-03-17-134311_smoosh-tables-together/up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index eede02f620..a194b3664f 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -18,7 +18,7 @@ ALTER TABLE comment_actions ADD COLUMN saved timestamptz, -- `post_id` was only in the `comment_liked` table, and removing it entirely or making it not null -- for the `saved` action would make this PR too complicated - ADD CONSTRAINT comment_actions_check_liked CHECK ((liked IS NULL) = ALL (ARRAY[like_score IS NULL, post_id IS NULL]); + ADD CONSTRAINT comment_actions_check_liked CHECK ((liked IS NULL) = ALL (ARRAY[like_score IS NULL, post_id IS NULL])); WITH old_comment_saved AS ( DELETE FROM comment_saved From d2f8710c7b558f144dc434d5c774fdd91200e6dd Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 12:04:28 -0700 Subject: [PATCH 189/241] clean up up.sql --- .../up.sql | 342 ++++++++---------- 1 file changed, 150 insertions(+), 192 deletions(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index a194b3664f..daeabaa28e 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -1,15 +1,36 @@ --- For each new actions table: --- * Transform the table previously used for the most common action type into the new actions table, --- which should only change the table's metadata instead of rewriting the rows --- * Add actions from other old tables to the new table --- --- Create comment_actions from comment_like +-- For each new actions table, transform the table previously used for the most common action type +-- into the new actions table, which should only change the table's metadata instead of rewriting the +-- rows ALTER TABLE comment_like RENAME TO comment_actions; +ALTER TABLE community_follower RENAME TO community_actions; + +ALTER TABLE instance_block RENAME TO instance_actions; + +ALTER TABLE person_follower RENAME TO person_actions; + +ALTER TABLE post_read RENAME TO post_actions; + ALTER TABLE comment_actions RENAME COLUMN published TO liked; ALTER TABLE comment_actions RENAME COLUMN score TO like_score; +ALTER TABLE community_actions RENAME COLUMN published TO followed; + +ALTER TABLE community_actions RENAME COLUMN pending TO follow_pending; + +ALTER TABLE instance_actions RENAME COLUMN published TO blocked; + +ALTER TABLE person_actions RENAME COLUMN person_id TO target_id; + +ALTER TABLE person_actions RENAME COLUMN follower_id TO person_id; + +ALTER TABLE person_actions RENAME COLUMN published TO followed; + +ALTER TABLE person_actions RENAME COLUMN pending TO follow_pending; + +ALTER TABLE post_actions RENAME COLUMN published TO read; + ALTER TABLE comment_actions ALTER COLUMN post_id DROP NOT NULL, ALTER COLUMN liked DROP NOT NULL, @@ -20,29 +41,6 @@ ALTER TABLE comment_actions -- for the `saved` action would make this PR too complicated ADD CONSTRAINT comment_actions_check_liked CHECK ((liked IS NULL) = ALL (ARRAY[like_score IS NULL, post_id IS NULL])); -WITH old_comment_saved AS ( - DELETE FROM comment_saved -RETURNING - *) - INSERT INTO comment_actions (person_id, comment_id, saved) - SELECT - person_id, - comment_id, - published - FROM - old_comment_saved - ON CONFLICT (person_id, - comment_id) - DO UPDATE SET - saved = excluded.saved; - --- Create community_actions from community_follower -ALTER TABLE community_follower RENAME TO community_actions; - -ALTER TABLE community_actions RENAME COLUMN published TO followed; - -ALTER TABLE community_actions RENAME pending TO follow_pending; - ALTER TABLE community_actions ALTER COLUMN followed DROP NOT NULL, ALTER COLUMN followed DROP DEFAULT, @@ -57,82 +55,10 @@ ALTER TABLE community_actions ADD CONSTRAINT community_actions_check_followed CHECK ((followed IS NULL) = (follow_pending IS NULL)), ADD CONSTRAINT community_actions_check_received_ban CHECK ((received_ban IS NULL, ban_expires IS NULL) != (FALSE, TRUE)); -WITH old_community_block AS ( - DELETE FROM community_block -RETURNING - *) - INSERT INTO community_actions (person_id, community_id, blocked) - SELECT - person_id, - community_id, - published - FROM - old_community_block - ON CONFLICT (person_id, - community_id) - DO UPDATE SET - person_id = excluded.person_id, - community_id = excluded.community_id, - blocked = excluded.blocked; - -WITH old_community_moderator AS ( - DELETE FROM community_moderator -RETURNING - *) - INSERT INTO community_actions (person_id, community_id, became_moderator) - SELECT - person_id, - community_id, - published - FROM - old_community_moderator - ON CONFLICT (person_id, - community_id) - DO UPDATE SET - person_id = excluded.person_id, - community_id = excluded.community_id, - became_moderator = excluded.became_moderator; - -WITH old_community_person_ban AS ( - DELETE FROM community_person_ban -RETURNING - *) - INSERT INTO community_actions (person_id, community_id, received_ban, ban_expires) - SELECT - person_id, - community_id, - published, - expires - FROM - old_community_person_ban - ON CONFLICT (person_id, - community_id) - DO UPDATE SET - person_id = excluded.person_id, - community_id = excluded.community_id, - received_ban = excluded.received_ban, - ban_expires = excluded.ban_expires; - --- Create instance_actions from instance_block -ALTER TABLE instance_block RENAME TO instance_actions; - -ALTER TABLE instance_actions RENAME COLUMN published TO blocked; - ALTER TABLE instance_actions ALTER COLUMN blocked DROP NOT NULL, ALTER COLUMN blocked DROP DEFAULT; --- Create person_actions from person_follower -ALTER TABLE person_follower RENAME TO person_actions; - -ALTER TABLE person_actions RENAME COLUMN person_id TO target_id; - -ALTER TABLE person_actions RENAME COLUMN follower_id TO person_id; - -ALTER TABLE person_actions RENAME COLUMN published TO followed; - -ALTER TABLE person_actions RENAME COLUMN pending TO follow_pending; - ALTER TABLE person_actions ALTER COLUMN followed DROP NOT NULL, ALTER COLUMN followed DROP DEFAULT, @@ -140,29 +66,6 @@ ALTER TABLE person_actions ADD COLUMN blocked timestamptz, ADD CONSTRAINT person_actions_check_followed CHECK ((followed IS NULL) = (follow_pending IS NULL)); -WITH old_person_block AS ( - DELETE FROM person_block -RETURNING - *) - INSERT INTO person_actions (person_id, target_id, blocked) - SELECT - person_id, - target_id, - published - FROM - old_person_block - ON CONFLICT (person_id, - target_id) - DO UPDATE SET - person_id = excluded.person_id, - target_id = excluded.target_id, - blocked = excluded.blocked; - --- Create post_actions from post_read -ALTER TABLE post_read RENAME TO post_actions; - -ALTER TABLE post_actions RENAME COLUMN published TO read; - ALTER TABLE post_actions ALTER COLUMN read DROP NOT NULL, ALTER COLUMN read DROP DEFAULT, @@ -175,73 +78,128 @@ ALTER TABLE post_actions ADD CONSTRAINT post_actions_check_read_comments CHECK ((read_comments IS NULL) = (read_comments_amount IS NULL)), ADD CONSTRAINT post_actions_check_liked CHECK ((liked IS NULL) = (like_score IS NULL)); -WITH old_person_post_aggregates AS ( - DELETE FROM person_post_aggregates -RETURNING - *) - INSERT INTO post_actions (person_id, post_id, read_comments, read_comments_amount) - SELECT - person_id, - post_id, - published, - read_comments - FROM - old_person_post_aggregates - ON CONFLICT (person_id, - post_id) - DO UPDATE SET - read_comments = excluded.read_comments, - read_comments_amount = excluded.read_comments_amount; - -WITH old_post_hide AS ( - DELETE FROM post_hide -RETURNING - *) - INSERT INTO post_actions (person_id, post_id, hidden) - SELECT - person_id, - post_id, - published - FROM - old_post_hide - ON CONFLICT (person_id, - post_id) - DO UPDATE SET - hidden = excluded.hidden; - -WITH old_post_like AS ( - DELETE FROM post_like -RETURNING - *) - INSERT INTO post_actions (person_id, post_id, liked, like_score) - SELECT - person_id, - post_id, - published, - score - FROM - old_post_like - ON CONFLICT (person_id, - post_id) - DO UPDATE SET - liked = excluded.liked, - like_score = excluded.like_score; - -WITH old_post_saved AS ( - DELETE FROM post_saved -RETURNING - *) - INSERT INTO post_actions (person_id, post_id, saved) - SELECT - person_id, - post_id, - published - FROM - old_post_saved - ON CONFLICT (person_id, - post_id) - DO UPDATE SET - saved = excluded.saved; +-- Add actions from other old tables to the new tables +INSERT INTO comment_actions (person_id, comment_id, saved) +SELECT + person_id, + comment_id, + published +FROM + comment_saved +ON CONFLICT (person_id, + comment_id) + DO UPDATE SET + saved = excluded.saved; + +INSERT INTO community_actions (person_id, community_id, blocked) +SELECT + person_id, + community_id, + published +FROM + community_block +ON CONFLICT (person_id, + community_id) + DO UPDATE SET + person_id = excluded.person_id, + community_id = excluded.community_id, + blocked = excluded.blocked; + +INSERT INTO community_actions (person_id, community_id, became_moderator) +SELECT + person_id, + community_id, + published +FROM + community_moderator +ON CONFLICT (person_id, + community_id) + DO UPDATE SET + person_id = excluded.person_id, + community_id = excluded.community_id, + became_moderator = excluded.became_moderator; + +INSERT INTO community_actions (person_id, community_id, received_ban, ban_expires) +SELECT + person_id, + community_id, + published, + expires +FROM + community_person_ban +ON CONFLICT (person_id, + community_id) + DO UPDATE SET + person_id = excluded.person_id, + community_id = excluded.community_id, + received_ban = excluded.received_ban, + ban_expires = excluded.ban_expires; + +INSERT INTO person_actions (person_id, target_id, blocked) +SELECT + person_id, + target_id, + published +FROM + person_block +ON CONFLICT (person_id, + target_id) + DO UPDATE SET + person_id = excluded.person_id, + target_id = excluded.target_id, + blocked = excluded.blocked; + +INSERT INTO post_actions (person_id, post_id, read_comments, read_comments_amount) +SELECT + person_id, + post_id, + published, + read_comments +FROM + person_post_aggregates +ON CONFLICT (person_id, + post_id) + DO UPDATE SET + read_comments = excluded.read_comments, + read_comments_amount = excluded.read_comments_amount; + +INSERT INTO post_actions (person_id, post_id, hidden) +SELECT + person_id, + post_id, + published +FROM + post_hide +ON CONFLICT (person_id, + post_id) + DO UPDATE SET + hidden = excluded.hidden; + +INSERT INTO post_actions (person_id, post_id, liked, like_score) +SELECT + person_id, + post_id, + published, + score +FROM + post_like +ON CONFLICT (person_id, + post_id) + DO UPDATE SET + liked = excluded.liked, + like_score = excluded.like_score; + +INSERT INTO post_actions (person_id, post_id, saved) +SELECT + person_id, + post_id, + published +FROM + post_saved +ON CONFLICT (person_id, + post_id) + DO UPDATE SET + saved = excluded.saved; -- Drop old tables DROP TABLE comment_saved, community_block, community_moderator, community_person_ban, person_block, person_post_aggregates, post_hide, post_like, post_saved; @@ -377,6 +335,6 @@ FROM person_actions; CREATE statistics post_actions_read_comments_stat ON (read_comments IS NULL), (read_comments_amount IS NULL) FROM post_actions; -CREATE statistics post_actions_liked_stat ON (liked IS NULL), (like_score IS NULL) +CREATE statistics post_actions_liked_stat ON (liked IS NULL), (like_score IS NULL), (post_id IS NULL) FROM post_actions; From fdb7bceac00109b3a5ad5da8317e6cc119cd1eff Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 12:21:07 -0700 Subject: [PATCH 190/241] clean up down.sql --- .../down.sql | 178 +++++++++--------- 1 file changed, 84 insertions(+), 94 deletions(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql index 4a1d4d9e4a..3524a9845f 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql @@ -1,9 +1,4 @@ --- For each new actions table: --- * Create tables that are dropped in up.sql, and insert into them --- * Do the opposite of the `ALTER TABLE` commands in up.sql, with `DELETE` being used to --- only keep rows where the preserved action is not null --- --- Create comment_like from comment_actions +-- For each new actions table, create tables that are dropped in up.sql, and insert into them CREATE TABLE comment_saved ( person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, comment_id int REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, @@ -21,24 +16,6 @@ FROM WHERE saved IS NOT NULL; -DELETE FROM comment_actions -WHERE liked IS NULL; - -ALTER TABLE comment_actions RENAME TO comment_like; - -ALTER TABLE comment_like RENAME COLUMN liked TO published; - -ALTER TABLE comment_like RENAME COLUMN like_score TO score; - -ALTER TABLE comment_like - DROP CONSTRAINT comment_actions_check_liked, - ALTER COLUMN published SET NOT NULL, - ALTER COLUMN published SET DEFAULT now(), - ALTER COLUMN score SET NOT NULL, - ALTER COLUMN post_id SET NOT NULL, - DROP COLUMN saved; - --- Create community_follower from community_actions CREATE TABLE community_block ( person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, @@ -56,78 +33,42 @@ FROM WHERE blocked IS NOT NULL; -CREATE TABLE community_moderator ( +CREATE TABLE community_person_ban ( community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, published timestamptz DEFAULT now() NOT NULL, + expires timestamptz, PRIMARY KEY (person_id, community_id) ); -INSERT INTO community_moderator (community_id, person_id, published) +INSERT INTO community_person_ban (community_id, person_id, published, expires) SELECT community_id, person_id, - became_moderator + received_ban, + ban_expires FROM community_actions WHERE - became_moderator IS NOT NULL; + received_ban IS NOT NULL; -CREATE TABLE community_person_ban ( +CREATE TABLE community_moderator ( community_id int REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, published timestamptz DEFAULT now() NOT NULL, - expires timestamptz, PRIMARY KEY (person_id, community_id) ); -INSERT INTO community_person_ban (community_id, person_id, published, expires) +INSERT INTO community_moderator (community_id, person_id, published) SELECT community_id, person_id, - received_ban, - ban_expires + became_moderator FROM community_actions WHERE - received_ban IS NOT NULL; - -DELETE FROM community_actions -WHERE followed IS NULL; - -ALTER TABLE community_actions RENAME TO community_follower; - -ALTER TABLE community_follower RENAME COLUMN followed TO published; - -ALTER TABLE community_follower RENAME follow_pending TO pending; - -ALTER TABLE community_follower - DROP CONSTRAINT community_actions_check_followed, - DROP CONSTRAINT community_actions_check_received_ban, - ALTER COLUMN published SET NOT NULL, - ALTER COLUMN published SET DEFAULT now(), - ALTER COLUMN pending SET NOT NULL, - -- This `SET DEFAULT` is done for community follow, but not person follow. It's not a mistake - -- in this migration. Believe it or not, `pending` only had a default value in community follow. - ALTER COLUMN pending SET DEFAULT FALSE, - DROP COLUMN blocked, - DROP COLUMN became_moderator, - DROP COLUMN received_ban, - DROP COLUMN ban_expires; - --- Create instance_block from instance_actions -DELETE FROM instance_actions -WHERE blocked IS NULL; - -ALTER TABLE instance_actions RENAME TO instance_block; - -ALTER TABLE instance_block RENAME COLUMN blocked TO published; - -ALTER TABLE instance_block - ALTER COLUMN published SET NOT NULL, - ALTER COLUMN published SET DEFAULT now(); + became_moderator IS NOT NULL; --- Create person_follower from person_actions CREATE TABLE person_block ( person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, target_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, @@ -145,27 +86,6 @@ FROM WHERE blocked IS NOT NULL; -DELETE FROM person_actions -WHERE followed IS NULL; - -ALTER TABLE person_actions RENAME TO person_follower; - -ALTER TABLE person_follower RENAME COLUMN person_id TO follower_id; - -ALTER TABLE person_follower RENAME COLUMN target_id TO person_id; - -ALTER TABLE person_follower RENAME COLUMN followed TO published; - -ALTER TABLE person_follower RENAME COLUMN follow_pending TO pending; - -ALTER TABLE person_follower - DROP CONSTRAINT person_actions_check_followed, - ALTER COLUMN published SET NOT NULL, - ALTER COLUMN published SET DEFAULT now(), - ALTER COLUMN pending SET NOT NULL, - DROP COLUMN blocked; - --- Create post_read from post_actions CREATE TABLE person_post_aggregates ( person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL, @@ -238,13 +158,86 @@ FROM WHERE saved IS NOT NULL; + +-- Do the opposite of the `ALTER TABLE` commands in up.sql +DELETE FROM comment_actions +WHERE liked IS NULL; + +DELETE FROM community_actions +WHERE followed IS NULL; + +DELETE FROM instance_actions +WHERE blocked IS NULL; + +DELETE FROM person_actions +WHERE followed IS NULL; + DELETE FROM post_actions WHERE read IS NULL; +ALTER TABLE comment_actions RENAME TO comment_like; + +ALTER TABLE community_actions RENAME TO community_follower; + +ALTER TABLE instance_actions RENAME TO instance_block; + +ALTER TABLE person_actions RENAME TO person_follower; + ALTER TABLE post_actions RENAME TO post_read; +ALTER TABLE comment_like RENAME COLUMN liked TO published; + +ALTER TABLE comment_like RENAME COLUMN like_score TO score; + +ALTER TABLE community_follower RENAME COLUMN followed TO published; + +ALTER TABLE community_follower RENAME COLUMN follow_pending TO pending; + +ALTER TABLE instance_block RENAME COLUMN blocked TO published; + +ALTER TABLE person_follower RENAME COLUMN person_id TO follower_id; + +ALTER TABLE person_follower RENAME COLUMN target_id TO person_id; + +ALTER TABLE person_follower RENAME COLUMN followed TO published; + +ALTER TABLE person_follower RENAME COLUMN follow_pending TO pending; + ALTER TABLE post_read RENAME COLUMN read TO published; +ALTER TABLE comment_like + DROP CONSTRAINT comment_actions_check_liked, + ALTER COLUMN published SET NOT NULL, + ALTER COLUMN published SET DEFAULT now(), + ALTER COLUMN score SET NOT NULL, + ALTER COLUMN post_id SET NOT NULL, + DROP COLUMN saved; + +ALTER TABLE community_follower + DROP CONSTRAINT community_actions_check_followed, + DROP CONSTRAINT community_actions_check_received_ban, + ALTER COLUMN published SET NOT NULL, + ALTER COLUMN published SET DEFAULT now(), + ALTER COLUMN pending SET NOT NULL, + -- This `SET DEFAULT` is done for community follow, but not person follow. It's not a mistake + -- in this migration. Believe it or not, `pending` only had a default value in community follow. + ALTER COLUMN pending SET DEFAULT FALSE, + DROP COLUMN blocked, + DROP COLUMN became_moderator, + DROP COLUMN received_ban, + DROP COLUMN ban_expires; + +ALTER TABLE instance_block + ALTER COLUMN published SET NOT NULL, + ALTER COLUMN published SET DEFAULT now(); + +ALTER TABLE person_follower + DROP CONSTRAINT person_actions_check_followed, + ALTER COLUMN published SET NOT NULL, + ALTER COLUMN published SET DEFAULT now(), + ALTER COLUMN pending SET NOT NULL, + DROP COLUMN blocked; + ALTER TABLE post_read DROP CONSTRAINT post_actions_check_read_comments, DROP CONSTRAINT post_actions_check_liked, @@ -332,6 +325,3 @@ DROP INDEX idx_comment_actions_liked_not_null, idx_community_actions_followed_no -- Drop statistics of columns that still exist DROP statistics comment_actions_liked_stat, community_actions_followed_stat, person_actions_followed_stat; -ALTER TABLE comment_actions - ALTER COLUMN post_id SET NOT NULL; - From 64eb9304c71b9f4f763fc741b2103fe42c819db0 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 12:42:20 -0700 Subject: [PATCH 191/241] fix --- crates/db_schema/src/impls/community_block.rs | 1 - crates/db_schema/src/impls/instance.rs | 2 -- crates/db_schema/src/impls/instance_block.rs | 1 - crates/db_schema/src/impls/person.rs | 1 - crates/db_schema/src/impls/person_block.rs | 1 - crates/db_schema/src/source/person.rs | 2 +- crates/db_schema/src/utils/uplete.rs | 6 +++--- 7 files changed, 4 insertions(+), 10 deletions(-) diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index 5e9382644c..d8e3618f78 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -11,7 +11,6 @@ use crate::{ DbPool, }, }; -use chrono::{DateTime, Utc}; use diesel::{ dsl::{exists, insert_into}, expression::SelectableHelper, diff --git a/crates/db_schema/src/impls/instance.rs b/crates/db_schema/src/impls/instance.rs index 1a8e829e26..94bf909a31 100644 --- a/crates/db_schema/src/impls/instance.rs +++ b/crates/db_schema/src/impls/instance.rs @@ -6,7 +6,6 @@ use crate::{ federation_blocklist, federation_queue_state, instance, - instance_actions, local_site, site, }, @@ -22,7 +21,6 @@ use crate::{ DbPool, }, }; -use chrono::{DateTime, Utc}; use diesel::{ dsl::{count_star, insert_into}, result::Error, diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index 54e938f570..68668d9534 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -11,7 +11,6 @@ use crate::{ DbPool, }, }; -use chrono::{DateTime, Utc}; use diesel::{ dsl::{exists, insert_into}, expression::SelectableHelper, diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 4b4212d911..3b4ed6956f 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -20,7 +20,6 @@ use crate::{ DbPool, }, }; -use chrono::{DateTime, Utc}; use diesel::{ dsl::insert_into, expression::SelectableHelper, diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index 944b839776..c38c49402e 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -11,7 +11,6 @@ use crate::{ DbPool, }, }; -use chrono::{DateTime, Utc}; use diesel::{ dsl::{exists, insert_into}, expression::SelectableHelper, diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index 2c7008b056..e42c980224 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -122,7 +122,7 @@ pub struct PersonUpdateForm { )] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))] #[cfg_attr(feature = "full", diesel(table_name = person_actions))] -#[cfg_attr(feature = "full", diesel(primary_key(follower_id, person_id)))] +#[cfg_attr(feature = "full", diesel(primary_key(person_id, target_id)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] pub struct PersonFollower { #[cfg_attr(feature = "full", diesel(column_name = target_id))] diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 38d3502739..6f336be4ae 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -36,8 +36,8 @@ impl UpleteBuilder { impl AsQuery for UpleteBuilder where Q::Table: Default + Table, - Q::Table::AllColumns: IntoArray, - >::Output: IntoIterator, + ::AllColumns: IntoArray, + <::AllColumns as IntoArray>::Output: IntoIterator, Q::Query: SelectDsl<(K0, K1)>, dsl::Select: Clone + FilterDsl + FilterDsl>, { @@ -149,7 +149,7 @@ impl Expression for AllNull { type SqlType = sql_types::Bool; } -impl QueryFragment for AllNull { +impl QueryFragment for AllNull { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { let mut item_prefix = "("; for column in &self.0 { From 4cd46ed6fc26710894a507ff76a34ecedc66c6e4 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 12:44:43 -0700 Subject: [PATCH 192/241] Update person_aggregates.rs --- crates/db_schema/src/aggregates/person_aggregates.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/aggregates/person_aggregates.rs b/crates/db_schema/src/aggregates/person_aggregates.rs index e96795e8c2..03295173f4 100644 --- a/crates/db_schema/src/aggregates/person_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_aggregates.rs @@ -100,8 +100,8 @@ mod tests { let mut comment_like = CommentLikeForm { comment_id: inserted_comment.id, - post_id: inserted_post.id, person_id: inserted_person.id, + post_id: inserted_post.id, score: 1, }; @@ -120,8 +120,8 @@ mod tests { let child_comment_like = CommentLikeForm { comment_id: inserted_child_comment.id, - post_id: inserted_post.id, person_id: another_inserted_person.id, + post_id: inserted_post.id, score: 1, }; From a0b0566485174490da4fe9daaefa4e734ff9ad0a Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 12:46:54 -0700 Subject: [PATCH 193/241] fmt --- migrations/2024-03-17-134311_smoosh-tables-together/down.sql | 1 - 1 file changed, 1 deletion(-) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql index 3524a9845f..38a273747c 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/down.sql @@ -158,7 +158,6 @@ FROM WHERE saved IS NOT NULL; - -- Do the opposite of the `ALTER TABLE` commands in up.sql DELETE FROM comment_actions WHERE liked IS NULL; From 5ba61c43b5c592c2a7c617e2826ff7bc0105ff17 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 13:21:50 -0700 Subject: [PATCH 194/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 48 +++++++++++++++++----------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 6f336be4ae..2ac67b0813 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -14,9 +14,14 @@ use std::any::Any; use tuplex::{IntoArray, Len}; /// Set columns to null and delete the row if all columns not in the primary key are null -pub fn uplete(query: Q) -> UpleteBuilder { +pub fn uplete(query: Q) -> UpleteBuilder +where + Q: AsQuery + Table, + Q::Table: Default, + Q::Query: SelectDsl<::PrimaryKey>, +{ UpleteBuilder { - query, + query: query.as_query().select(Q::Table::default().primary_key()), set_null_columns: Vec::new(), } } @@ -33,32 +38,37 @@ impl UpleteBuilder { } } -impl AsQuery for UpleteBuilder +impl AsQuery for UpleteBuilder where - Q::Table: Default + Table, + K0: 'static, + K1: 'static, + (K0, K1): Expression + QueryFragment + Send, + Q: HasTable, + Q::Table: Default + Table + QueryFragment + Send + 'static, ::AllColumns: IntoArray, <::AllColumns as IntoArray>::Output: IntoIterator, - Q::Query: SelectDsl<(K0, K1)>, - dsl::Select: Clone + FilterDsl + FilterDsl>, + Q: Clone + FilterDsl + FilterDsl>, + dsl::Filter: QueryFragment + Send + 'static, + dsl::Filter>: QueryFragment + Send + 'static, { type Query = UpleteQuery; type SqlType = (sql_types::BigInt, sql_types::BigInt); fn as_query(self) -> Self::Query { - let primary_key = Q::Table::default().primary_key(); - let primary_key_type_ids = [primary_key.0.type_id(), primary_key.1.type_id()]; + let table = Q::Table::default(); + let pk = table.primary_key(); + let pk_type_ids = [pk.0.type_id(), pk.1.type_id()]; let other_columns = Q::Table::all_columns() .into_array() .into_iter() .filter(|c: DynColumn| { - primary_key_type_ids + pk_type_ids .iter() .chain(self.set_null_columns.iter().map(|c| c.type_id())) .all(|other| other != c.type_id()) }) .collect::>(); - let subquery = self.query.select(primary_key.clone()); UpleteQuery { // Updated rows and deleted rows must not overlap, so updating all rows and using the returned // new rows to determine which ones to delete is not an option. @@ -70,23 +80,23 @@ where // predict which one. This also applies to deleting a row that was already updated in the same // statement: only the update is performed." update_subquery: Box::new( - subquery + self.query .clone() .filter(dsl::not(AllNull(other_columns.clone()))), ), - delete_subquery: Box::new(subquery.filter(AllNull(other_columns))), - table: Box::new(Q::Table::default()), - primary_key: Box::new(primary_key), + delete_subquery: Box::new(self.query.filter(AllNull(other_columns))), + table: Box::new(table), + primary_key: Box::new(pk), set_null_columns: self.set_null_columns, } } } pub struct UpleteQuery { - update_subquery: Box>, - delete_subquery: Box>, - table: Box>, - primary_key: Box>, + update_subquery: Box + Send + 'static>, + delete_subquery: Box + Send + 'static>, + table: Box + Send + 'static>, + primary_key: Box + Send + 'static>, set_null_columns: Vec, } @@ -164,7 +174,7 @@ impl QueryFragment for AllNull { } } -pub struct DynColumn(Box + 'static>); +pub struct DynColumn(Box + Send + 'static>); impl + 'static> From for DynColumn { fn from(value: T) -> Self { From 85053e51c216a42111e99abdcc28d5647e949db6 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 13:23:35 -0700 Subject: [PATCH 195/241] fmt --- crates/db_schema/src/utils/uplete.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 2ac67b0813..c39670589d 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -80,7 +80,8 @@ where // predict which one. This also applies to deleting a row that was already updated in the same // statement: only the update is performed." update_subquery: Box::new( - self.query + self + .query .clone() .filter(dsl::not(AllNull(other_columns.clone()))), ), From 83901d16386fc53895f91582ad1fe2cd007e447c Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 13:39:21 -0700 Subject: [PATCH 196/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index c39670589d..90f998d2d2 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -16,7 +16,7 @@ use tuplex::{IntoArray, Len}; /// Set columns to null and delete the row if all columns not in the primary key are null pub fn uplete(query: Q) -> UpleteBuilder where - Q: AsQuery + Table, + Q: AsQuery + HasTable, Q::Table: Default, Q::Query: SelectDsl<::PrimaryKey>, { From 00642277e74aacf71e9e6cfa6c6817a6e7f3dbc8 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 13:54:29 -0700 Subject: [PATCH 197/241] Update community.rs --- crates/db_schema/src/source/community.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index e79e8c5b19..46f0b7a80b 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -181,8 +181,6 @@ pub struct CommunityPersonBan { #[cfg_attr(feature = "full", diesel(select_expression = community_actions::received_ban.assume_not_null()))] #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, - #[cfg_attr(feature = "full", diesel(select_expression = community_actions::ban_expires.assume_not_null()))] - #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub expires: Option>, } From b9492135ac4cf61ea9a33a7f51e9cf60f9bc50f7 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 17:11:51 -0700 Subject: [PATCH 198/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 67 +++++++++++++++------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 90f998d2d2..8b65eeabcc 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -19,6 +19,9 @@ where Q: AsQuery + HasTable, Q::Table: Default, Q::Query: SelectDsl<::PrimaryKey>, + + // For better error messages + UpleteBuilder: AsQuery, { UpleteBuilder { query: query.as_query().select(Q::Table::default().primary_key()), @@ -38,14 +41,13 @@ impl UpleteBuilder { } } -impl AsQuery for UpleteBuilder +impl AsQuery for UpleteBuilder where - K0: 'static, - K1: 'static, - (K0, K1): Expression + QueryFragment + Send, Q: HasTable, - Q::Table: Default + Table + QueryFragment + Send + 'static, + Q::Table: Default + QueryFragment + Send + 'static, + ::PrimaryKey: IntoArray + QueryFragment + Send + 'static, ::AllColumns: IntoArray, + <::PrimaryKey as IntoArray>::Output: IntoIterator, <::AllColumns as IntoArray>::Output: IntoIterator, Q: Clone + FilterDsl + FilterDsl>, dsl::Filter: QueryFragment + Send + 'static, @@ -57,18 +59,19 @@ where fn as_query(self) -> Self::Query { let table = Q::Table::default(); - let pk = table.primary_key(); - let pk_type_ids = [pk.0.type_id(), pk.1.type_id()]; - let other_columns = Q::Table::all_columns() - .into_array() - .into_iter() - .filter(|c: DynColumn| { - pk_type_ids - .iter() - .chain(self.set_null_columns.iter().map(|c| c.type_id())) - .all(|other| other != c.type_id()) - }) - .collect::>(); + let primary_key_columns = table.primary_key().into_array(); + let deletion_condition = AllNull( + Q::Table::all_columns() + .into_array() + .into_iter() + .filter(|c: DynColumn| { + primary_key_columns + .iter() + .chain(&self.set_null_columns) + .all(|excluded_column| excluded_column.type_id() != c.type_id()) + }) + .collect::>() + ); UpleteQuery { // Updated rows and deleted rows must not overlap, so updating all rows and using the returned // new rows to determine which ones to delete is not an option. @@ -83,11 +86,11 @@ where self .query .clone() - .filter(dsl::not(AllNull(other_columns.clone()))), + .filter(dsl::not(deletion_condition.clone())), ), - delete_subquery: Box::new(self.query.filter(AllNull(other_columns))), + delete_subquery: Box::new(self.query.filter(deletion_condition)), table: Box::new(table), - primary_key: Box::new(pk), + primary_key: Box::new(table.primary_key()), set_null_columns: self.set_null_columns, } } @@ -115,17 +118,18 @@ impl QueryFragment for UpleteQuery { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { assert_ne!(self.set_null_columns.len(), 0, "`set_null` was not called"); - // Which rows to update - out.push_sql("WITH update_keys AS ("); - self.update_subquery.walk_ast(out.reborrow())?; - out.push_sql(" FOR UPDATE)"); - - // Which rows to delete - out.push_sql(", delete_keys AS ("); - self.delete_subquery.walk_ast(out.reborrow())?; - out.push_sql(" FOR UPDATE)"); + // Declare `update_keys` and `delete_keys` CTEs, which select primary keys + for (prefix, subquery) in [ + ("WITH update_keys", &self.update_subquery), + (", delete_keys", &self.delete_subquery), + ] { + out.push_sql(prefix); + out.push_sql(" AS ("); + subquery.walk_ast(out.reborrow())?; + out.push_sql(" FOR UPDATE)"); + } - // Update rows + // Update rows that are referenced in `update_keys` out.push_sql(", update_result AS (UPDATE "); self.table.walk_ast(out.reborrow())?; let mut item_prefix = " SET "; @@ -139,7 +143,7 @@ impl QueryFragment for UpleteQuery { self.primary_key.walk_ast(out.reborrow())?; out.push_sql(") = ANY (SELECT * FROM update_keys) RETURNING 1)"); - // Delete rows + // Delete rows that are referenced in `delete_keys` out.push_sql(", delete_result AS (DELETE FROM "); self.table.walk_ast(out.reborrow())?; out.push_sql(" WHERE ("); @@ -154,6 +158,7 @@ impl QueryFragment for UpleteQuery { } } +#[derive(Clone)] pub struct AllNull(Vec); impl Expression for AllNull { From 1761eb533540cfe8e002cb297102f86aa2fbf763 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 17:40:00 -0700 Subject: [PATCH 199/241] Update local_user.rs --- crates/db_schema/src/impls/local_user.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index cad0a37c0a..fef2cec784 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -163,21 +163,21 @@ impl LocalUser { let followed_communities = action_query(community_actions::followed) .filter(community_actions::person_id.eq(person_id_)) - .inner_join(community::table.on(community_actions::community_id.eq(community::id))) + .inner_join(community::table) .select(community::actor_id) .get_results(conn) .await?; let saved_posts = action_query(post_actions::saved) .filter(post_actions::person_id.eq(person_id_)) - .inner_join(post::table.on(post_actions::post_id.eq(post::id))) + .inner_join(post::table) .select(post::ap_id) .get_results(conn) .await?; let saved_comments = action_query(comment_actions::saved) .filter(comment_actions::person_id.eq(person_id_)) - .inner_join(comment::table.on(comment_actions::comment_id.eq(comment::id))) + .inner_join(comment::table) .select(comment::ap_id) .get_results(conn) .await?; @@ -191,7 +191,7 @@ impl LocalUser { let blocked_users = action_query(person_actions::blocked) .filter(person_actions::person_id.eq(person_id_)) - .inner_join(person::table.on(person_actions::target_id.eq(person::id))) + .inner_join(person::table) .select(person::actor_id) .get_results(conn) .await?; From 37ed6515d4e2acaf787ea9ecbb5b0da130a91cbf Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 17:44:06 -0700 Subject: [PATCH 200/241] fmt --- crates/db_schema/src/utils/uplete.rs | 2 +- crates/db_views_actor/src/comment_reply_view.rs | 5 +---- crates/db_views_actor/src/person_mention_view.rs | 5 +---- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 8b65eeabcc..f285bf6e0a 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -70,7 +70,7 @@ where .chain(&self.set_null_columns) .all(|excluded_column| excluded_column.type_id() != c.type_id()) }) - .collect::>() + .collect::>(), ); UpleteQuery { // Updated rows and deleted rows must not overlap, so updating all rows and using the returned diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index 15ba6c426c..1c8b576e99 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -23,10 +23,7 @@ use lemmy_db_schema::{ person_actions, post, }, - source::{ - community::CommunityFollower, - local_user::LocalUser, - }, + source::{community::CommunityFollower, local_user::LocalUser}, utils::{ actions, actions_alias, diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index ba46e3cef1..cbd513f730 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -23,10 +23,7 @@ use lemmy_db_schema::{ person_mention, post, }, - source::{ - community::CommunityFollower, - local_user::LocalUser, - }, + source::{community::CommunityFollower, local_user::LocalUser}, utils::{ actions, actions_alias, From c95f0b05e3e2bbc998c76533f44735036ed7b746 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 18:08:43 -0700 Subject: [PATCH 201/241] fix --- crates/db_schema/src/impls/local_user.rs | 2 +- crates/db_schema/src/source/community.rs | 1 + crates/db_schema/src/utils/uplete.rs | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index fef2cec784..cc96a0bdf2 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -191,7 +191,7 @@ impl LocalUser { let blocked_users = action_query(person_actions::blocked) .filter(person_actions::person_id.eq(person_id_)) - .inner_join(person::table) + .inner_join(person::table.on(person_actions::target_id.eq(person::id))) .select(person::actor_id) .get_results(conn) .await?; diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index 46f0b7a80b..0f96280ded 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -181,6 +181,7 @@ pub struct CommunityPersonBan { #[cfg_attr(feature = "full", diesel(select_expression = community_actions::received_ban.assume_not_null()))] #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, + #[cfg_attr(feature = "full", diesel(column_name = ban_expires))] pub expires: Option>, } diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index f285bf6e0a..ab1b09a2d6 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -1,6 +1,7 @@ use diesel::{ associations::HasTable, dsl, + expression::{is_aggregate, ValidGrouping}, pg::Pg, query_builder::{AsQuery, AstPass, Query, QueryFragment, QueryId}, query_dsl::methods::{FilterDsl, SelectDsl}, @@ -165,6 +166,10 @@ impl Expression for AllNull { type SqlType = sql_types::Bool; } +impl ValidGrouping<()> for AllNull { + type IsAggregate = is_aggregate::No; +} + impl QueryFragment for AllNull { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { let mut item_prefix = "("; From 561e0434686af3541f61b3c7d1a9219932029603 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 18:58:01 -0700 Subject: [PATCH 202/241] fix --- crates/db_schema/src/source/person.rs | 2 +- crates/db_schema/src/utils/uplete.rs | 50 +++++++++++++-------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index e42c980224..d8238340ee 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -7,7 +7,7 @@ use crate::{ }; use chrono::{DateTime, Utc}; #[cfg(feature = "full")] -use diesel::{dsl, expression_methods::ExpressionMethods}; +use diesel::{dsl, expression_methods::NullableExpressionMethods}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index ab1b09a2d6..17c53e5f5d 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -12,10 +12,10 @@ use diesel::{ Table, }; use std::any::Any; -use tuplex::{IntoArray, Len}; +use tuplex::IntoArray; /// Set columns to null and delete the row if all columns not in the primary key are null -pub fn uplete(query: Q) -> UpleteBuilder +pub fn uplete(query: Q) -> UpleteBuilder::PrimaryKey>> where Q: AsQuery + HasTable, Q::Table: Default, @@ -48,8 +48,8 @@ where Q::Table: Default + QueryFragment + Send + 'static, ::PrimaryKey: IntoArray + QueryFragment + Send + 'static, ::AllColumns: IntoArray, - <::PrimaryKey as IntoArray>::Output: IntoIterator, - <::AllColumns as IntoArray>::Output: IntoIterator, + <::PrimaryKey as IntoArray>::Output: AsRef<[DynColumn]>, + <::AllColumns as IntoArray>::Output: AsRef<[DynColumn]>, Q: Clone + FilterDsl + FilterDsl>, dsl::Filter: QueryFragment + Send + 'static, dsl::Filter>: QueryFragment + Send + 'static, @@ -61,18 +61,22 @@ where fn as_query(self) -> Self::Query { let table = Q::Table::default(); let primary_key_columns = table.primary_key().into_array(); - let deletion_condition = AllNull( - Q::Table::all_columns() - .into_array() - .into_iter() - .filter(|c: DynColumn| { - primary_key_columns - .iter() - .chain(&self.set_null_columns) - .all(|excluded_column| excluded_column.type_id() != c.type_id()) - }) - .collect::>(), - ); + let all_columns = Q::Table::all_columns().into_array(); + let deletion_condition = || { + AllNull( + all_columns + .as_ref() + .iter() + .filter(|c: DynColumn| { + primary_key_columns + .as_ref() + .iter() + .chain(&self.set_null_columns) + .all(|excluded_column| excluded_column.type_id() != c.type_id()) + }) + .collect::>(), + ) + }; UpleteQuery { // Updated rows and deleted rows must not overlap, so updating all rows and using the returned // new rows to determine which ones to delete is not an option. @@ -84,12 +88,9 @@ where // predict which one. This also applies to deleting a row that was already updated in the same // statement: only the update is performed." update_subquery: Box::new( - self - .query - .clone() - .filter(dsl::not(deletion_condition.clone())), + self.query.clone().filter(dsl::not(deletion_condition())), ), - delete_subquery: Box::new(self.query.filter(deletion_condition)), + delete_subquery: Box::new(self.query.filter(deletion_condition())), table: Box::new(table), primary_key: Box::new(table.primary_key()), set_null_columns: self.set_null_columns, @@ -136,7 +137,7 @@ impl QueryFragment for UpleteQuery { let mut item_prefix = " SET "; for column in &self.set_null_columns { out.push_sql(item_prefix); - column.walk_ast(out.reborrow())?; + column.0.walk_ast(out.reborrow())?; out.push_sql(" = NULL"); item_prefix = ","; } @@ -159,7 +160,6 @@ impl QueryFragment for UpleteQuery { } } -#[derive(Clone)] pub struct AllNull(Vec); impl Expression for AllNull { @@ -175,7 +175,7 @@ impl QueryFragment for AllNull { let mut item_prefix = "("; for column in &self.0 { out.push_sql(item_prefix); - column.walk_ast(out.reborrow())?; + column.0.walk_ast(out.reborrow())?; out.push_sql(" IS NOT NULL"); item_prefix = " AND "; } @@ -187,7 +187,7 @@ impl QueryFragment for AllNull { pub struct DynColumn(Box + Send + 'static>); -impl + 'static> From for DynColumn { +impl + Send + 'static> From for DynColumn { fn from(value: T) -> Self { DynColumn(Box::new(value)) } From d9890f198c3c9fc38a333a70c01312678835c53e Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 19:05:44 -0700 Subject: [PATCH 203/241] fmt --- crates/db_schema/src/utils/uplete.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 17c53e5f5d..ea6d88829e 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -87,9 +87,7 @@ where // the modifications takes place, but it is not easy (and sometimes not possible) to reliably // predict which one. This also applies to deleting a row that was already updated in the same // statement: only the update is performed." - update_subquery: Box::new( - self.query.clone().filter(dsl::not(deletion_condition())), - ), + update_subquery: Box::new(self.query.clone().filter(dsl::not(deletion_condition()))), delete_subquery: Box::new(self.query.filter(deletion_condition())), table: Box::new(table), primary_key: Box::new(table.primary_key()), From 266edf47f9cfe5f4d6328b592ef304636ae107f0 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 19:56:13 -0700 Subject: [PATCH 204/241] improve uplete api --- crates/db_schema/src/impls/comment.rs | 16 +++++----- crates/db_schema/src/impls/community.rs | 30 ++++++++--------- crates/db_schema/src/impls/community_block.rs | 6 ++-- crates/db_schema/src/impls/instance_block.rs | 6 ++-- crates/db_schema/src/impls/person.rs | 10 +++--- crates/db_schema/src/impls/person_block.rs | 6 ++-- crates/db_schema/src/impls/post.rs | 26 +++++++-------- crates/db_schema/src/traits.rs | 14 ++++---- crates/db_schema/src/utils/uplete.rs | 32 +++++++++---------- 9 files changed, 73 insertions(+), 73 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index ce24662f85..c9afaffc05 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -17,7 +17,7 @@ use crate::{ get_conn, naive_now, now, - uplete::{uplete, UpleteCount}, + uplete, DbPool, DELETED_REPLACEMENT_TEXT, }, @@ -173,9 +173,9 @@ impl Likeable for CommentLike { pool: &mut DbPool<'_>, person_id: PersonId, comment_id: CommentId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(comment_actions::table.find((person_id, comment_id))) + uplete::new(comment_actions::table.find((person_id, comment_id))) .set_null(comment_actions::like_score) .set_null(comment_actions::liked) // Deleting empty `comment_actions` rows would not work without setting `post_id` to @@ -210,9 +210,9 @@ impl Saveable for CommentSaved { async fn unsave( pool: &mut DbPool<'_>, comment_saved_form: &CommentSavedForm, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete( + uplete::new( comment_actions::table.find((comment_saved_form.person_id, comment_saved_form.comment_id)), ) .set_null(comment_actions::saved) @@ -244,7 +244,7 @@ mod tests { post::{Post, PostInsertForm}, }, traits::{Crud, Likeable, Saveable}, - utils::{build_db_pool_for_tests, uplete::UpleteCount}, + utils::{build_db_pool_for_tests, uplete::uplete::Count}, }; use diesel_ltree::Ltree; use pretty_assertions::assert_eq; @@ -392,8 +392,8 @@ mod tests { format!("0.{}.{}", expected_comment.id, inserted_child_comment.id), inserted_child_comment.path.0, ); - assert_eq!(UpleteCount::only_updated(1), like_removed); - assert_eq!(UpleteCount::only_deleted(1), saved_removed); + assert_eq!(uplete::Count::only_updated(1), like_removed); + assert_eq!(uplete::Count::only_deleted(1), saved_removed); assert_eq!(1, num_deleted); } } diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 1832e817c2..cc4865afe9 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -24,7 +24,7 @@ use crate::{ functions::{coalesce, lower}, get_conn, now, - uplete::{uplete, UpleteCount}, + uplete, DbPool, }, SubscribedType, @@ -109,9 +109,9 @@ impl Joinable for CommunityModerator { async fn leave( pool: &mut DbPool<'_>, community_moderator_form: &CommunityModeratorForm, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(community_actions::table.find(( + uplete::new(community_actions::table.find(( community_moderator_form.person_id, community_moderator_form.community_id, ))) @@ -214,10 +214,10 @@ impl CommunityModerator { pub async fn delete_for_community( pool: &mut DbPool<'_>, for_community_id: CommunityId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(community_actions::table.filter(community_actions::community_id.eq(for_community_id))) + uplete::new(community_actions::table.filter(community_actions::community_id.eq(for_community_id))) .set_null(community_actions::became_moderator) .get_result(conn) .await @@ -226,9 +226,9 @@ impl CommunityModerator { pub async fn leave_all_communities( pool: &mut DbPool<'_>, for_person_id: PersonId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(community_actions::table.filter(community_actions::person_id.eq(for_person_id))) + uplete::new(community_actions::table.filter(community_actions::person_id.eq(for_person_id))) .set_null(community_actions::became_moderator) .get_result(conn) .await @@ -275,9 +275,9 @@ impl Bannable for CommunityPersonBan { async fn unban( pool: &mut DbPool<'_>, community_person_ban_form: &CommunityPersonBanForm, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(community_actions::table.find(( + uplete::new(community_actions::table.find(( community_person_ban_form.person_id, community_person_ban_form.community_id, ))) @@ -370,9 +370,9 @@ impl Followable for CommunityFollower { async fn unfollow( pool: &mut DbPool<'_>, form: &CommunityFollowerForm, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(community_actions::table.find((form.person_id, form.community_id))) + uplete::new(community_actions::table.find((form.person_id, form.community_id))) .set_null(community_actions::followed) .set_null(community_actions::follow_pending) .get_result(conn) @@ -451,7 +451,7 @@ mod tests { person::{Person, PersonInsertForm}, }, traits::{Bannable, Crud, Followable, Joinable}, - utils::{build_db_pool_for_tests, uplete::UpleteCount}, + utils::{build_db_pool_for_tests, uplete::uplete::Count}, CommunityVisibility, }; use pretty_assertions::assert_eq; @@ -595,9 +595,9 @@ mod tests { assert_eq!(expected_community_follower, inserted_community_follower); assert_eq!(expected_community_moderator, inserted_community_moderator); assert_eq!(expected_community_person_ban, inserted_community_person_ban); - assert_eq!(UpleteCount::only_updated(1), ignored_community); - assert_eq!(UpleteCount::only_updated(1), left_community); - assert_eq!(UpleteCount::only_deleted(1), unban); + assert_eq!(uplete::Count::only_updated(1), ignored_community); + assert_eq!(uplete::Count::only_updated(1), left_community); + assert_eq!(uplete::Count::only_deleted(1), unban); // assert_eq!(2, loaded_count); assert_eq!(1, num_deleted); } diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index d8e3618f78..428df331ff 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -7,7 +7,7 @@ use crate::{ find_action, get_conn, now, - uplete::{uplete, UpleteCount}, + uplete, DbPool, }, }; @@ -62,9 +62,9 @@ impl Blockable for CommunityBlock { async fn unblock( pool: &mut DbPool<'_>, community_block_form: &Self::Form, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(community_actions::table.find(( + uplete::new(community_actions::table.find(( community_block_form.person_id, community_block_form.community_id, ))) diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index 68668d9534..735824008d 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -7,7 +7,7 @@ use crate::{ find_action, get_conn, now, - uplete::{uplete, UpleteCount}, + uplete, DbPool, }, }; @@ -59,9 +59,9 @@ impl Blockable for InstanceBlock { async fn unblock( pool: &mut DbPool<'_>, instance_block_form: &Self::Form, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(instance_actions::table.find(( + uplete::new(instance_actions::table.find(( instance_block_form.person_id, instance_block_form.instance_id, ))) diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 3b4ed6956f..446e75056e 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -16,7 +16,7 @@ use crate::{ get_conn, naive_now, now, - uplete::{uplete, UpleteCount}, + uplete, DbPool, }, }; @@ -211,9 +211,9 @@ impl Followable for PersonFollower { async fn unfollow( pool: &mut DbPool<'_>, form: &PersonFollowerForm, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(person_actions::table.find((form.follower_id, form.person_id))) + uplete::new(person_actions::table.find((form.follower_id, form.person_id))) .set_null(person_actions::followed) .set_null(person_actions::follow_pending) .get_result(conn) @@ -247,7 +247,7 @@ mod tests { person::{Person, PersonFollower, PersonFollowerForm, PersonInsertForm, PersonUpdateForm}, }, traits::{Crud, Followable}, - utils::{build_db_pool_for_tests, uplete::UpleteCount}, + utils::{build_db_pool_for_tests, uplete::uplete::Count}, }; use pretty_assertions::assert_eq; use serial_test::serial; @@ -354,6 +354,6 @@ mod tests { assert_eq!(vec![person_2], followers); let unfollow = PersonFollower::unfollow(pool, &follow_form).await.unwrap(); - assert_eq!(UpleteCount::only_deleted(1), unfollow); + assert_eq!(uplete::Count::only_deleted(1), unfollow); } } diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index c38c49402e..4ca0f682ee 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -7,7 +7,7 @@ use crate::{ find_action, get_conn, now, - uplete::{uplete, UpleteCount}, + uplete, DbPool, }, }; @@ -62,9 +62,9 @@ impl Blockable for PersonBlock { async fn unblock( pool: &mut DbPool<'_>, person_block_form: &Self::Form, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(person_actions::table.find((person_block_form.person_id, person_block_form.target_id))) + uplete::new(person_actions::table.find((person_block_form.person_id, person_block_form.target_id))) .set_null(person_actions::blocked) .get_result(conn) .await diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 20a46b9f47..46e2e870e7 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -21,7 +21,7 @@ use crate::{ get_conn, naive_now, now, - uplete::{uplete, UpleteCount}, + uplete, DbPool, DELETED_REPLACEMENT_TEXT, FETCH_LIMIT_MAX, @@ -268,9 +268,9 @@ impl Likeable for PostLike { pool: &mut DbPool<'_>, person_id: PersonId, post_id: PostId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(post_actions::table.find((person_id, post_id))) + uplete::new(post_actions::table.find((person_id, post_id))) .set_null(post_actions::like_score) .set_null(post_actions::liked) .get_result(conn) @@ -296,9 +296,9 @@ impl Saveable for PostSaved { async fn unsave( pool: &mut DbPool<'_>, post_saved_form: &PostSavedForm, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete(post_actions::table.find((post_saved_form.person_id, post_saved_form.post_id))) + uplete::new(post_actions::table.find((post_saved_form.person_id, post_saved_form.post_id))) .set_null(post_actions::saved) .get_result(conn) .await @@ -335,10 +335,10 @@ impl PostRead { pool: &mut DbPool<'_>, post_id_: HashSet, person_id_: PersonId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete( + uplete::new( post_actions::table .filter(post_actions::post_id.eq_any(post_id_)) .filter(post_actions::person_id.eq(person_id_)), @@ -379,10 +379,10 @@ impl PostHide { pool: &mut DbPool<'_>, post_id_: HashSet, person_id_: PersonId, - ) -> Result { + ) -> Result { let conn = &mut get_conn(pool).await?; - uplete( + uplete::new( post_actions::table .filter(post_actions::post_id.eq_any(post_id_)) .filter(post_actions::person_id.eq(person_id_)), @@ -415,7 +415,7 @@ mod tests { }, }, traits::{Crud, Likeable, Saveable}, - utils::{build_db_pool_for_tests, uplete::UpleteCount}, + utils::{build_db_pool_for_tests, uplete::uplete::Count}, }; use pretty_assertions::assert_eq; use serial_test::serial; @@ -542,9 +542,9 @@ mod tests { let like_removed = PostLike::remove(pool, inserted_person.id, inserted_post.id) .await .unwrap(); - assert_eq!(UpleteCount::only_updated(1), like_removed); + assert_eq!(uplete::Count::only_updated(1), like_removed); let saved_removed = PostSaved::unsave(pool, &post_saved_form).await.unwrap(); - assert_eq!(UpleteCount::only_updated(1), saved_removed); + assert_eq!(uplete::Count::only_updated(1), saved_removed); let read_removed = PostRead::mark_as_unread( pool, HashSet::from([inserted_post.id, inserted_post2.id]), @@ -552,7 +552,7 @@ mod tests { ) .await .unwrap(); - assert_eq!(UpleteCount::only_deleted(2), read_removed); + assert_eq!(uplete::Count::only_deleted(2), read_removed); let num_deleted = Post::delete(pool, inserted_post.id).await.unwrap() + Post::delete(pool, inserted_post2.id).await.unwrap(); diff --git a/crates/db_schema/src/traits.rs b/crates/db_schema/src/traits.rs index 4779c0a83a..d9b64114a6 100644 --- a/crates/db_schema/src/traits.rs +++ b/crates/db_schema/src/traits.rs @@ -1,7 +1,7 @@ use crate::{ diesel::OptionalExtension, newtypes::{CommunityId, DbUrl, PersonId}, - utils::{get_conn, uplete::UpleteCount, DbPool}, + utils::{get_conn, uplete::uplete::Count, DbPool}, }; use diesel::{ associations::HasTable, @@ -77,7 +77,7 @@ pub trait Followable { ) -> Result where Self: Sized; - async fn unfollow(pool: &mut DbPool<'_>, form: &Self::Form) -> Result + async fn unfollow(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; } @@ -88,7 +88,7 @@ pub trait Joinable { async fn join(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; - async fn leave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result + async fn leave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; } @@ -104,7 +104,7 @@ pub trait Likeable { pool: &mut DbPool<'_>, person_id: PersonId, item_id: Self::IdType, - ) -> Result + ) -> Result where Self: Sized; } @@ -115,7 +115,7 @@ pub trait Bannable { async fn ban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; - async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result + async fn unban(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; } @@ -126,7 +126,7 @@ pub trait Saveable { async fn save(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; - async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result + async fn unsave(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; } @@ -137,7 +137,7 @@ pub trait Blockable { async fn block(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; - async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> Result + async fn unblock(pool: &mut DbPool<'_>, form: &Self::Form) -> Result where Self: Sized; } diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index ea6d88829e..b716fe70d1 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -14,8 +14,9 @@ use diesel::{ use std::any::Any; use tuplex::IntoArray; -/// Set columns to null and delete the row if all columns not in the primary key are null -pub fn uplete(query: Q) -> UpleteBuilder::PrimaryKey>> +/// Set columns (each specified with `UpleteBuilder::set_null`) to null in the rows found by +/// `query`, and delete rows that have no remaining non-null values outside of the primary key +pub fn new(query: Q) -> UpleteBuilder::PrimaryKey>> where Q: AsQuery + HasTable, Q::Table: Default, @@ -48,8 +49,8 @@ where Q::Table: Default + QueryFragment + Send + 'static, ::PrimaryKey: IntoArray + QueryFragment + Send + 'static, ::AllColumns: IntoArray, - <::PrimaryKey as IntoArray>::Output: AsRef<[DynColumn]>, - <::AllColumns as IntoArray>::Output: AsRef<[DynColumn]>, + <::PrimaryKey as IntoArray>::Output: IntoIterator, + <::AllColumns as IntoArray>::Output: IntoIterator, Q: Clone + FilterDsl + FilterDsl>, dsl::Filter: QueryFragment + Send + 'static, dsl::Filter>: QueryFragment + Send + 'static, @@ -60,17 +61,16 @@ where fn as_query(self) -> Self::Query { let table = Q::Table::default(); - let primary_key_columns = table.primary_key().into_array(); - let all_columns = Q::Table::all_columns().into_array(); let deletion_condition = || { AllNull( - all_columns - .as_ref() - .iter() + Q::Table::all_columns() + .into_array() + .into_iter() .filter(|c: DynColumn| { - primary_key_columns - .as_ref() - .iter() + table + .primary_key() + .into_array(); + .into_iter() .chain(&self.set_null_columns) .all(|excluded_column| excluded_column.type_id() != c.type_id()) }) @@ -192,21 +192,21 @@ impl + Send + 'static> From for DynColumn { } #[derive(Queryable, PartialEq, Eq, Debug)] -pub struct UpleteCount { +pub struct Count { pub updated: i64, pub deleted: i64, } -impl UpleteCount { +impl Count { pub fn only_updated(n: i64) -> Self { - UpleteCount { + Count { updated: n, deleted: 0, } } pub fn only_deleted(n: i64) -> Self { - UpleteCount { + Count { updated: 0, deleted: n, } From 85b52c679804dda8daaa15dc39177c99a1ebc78d Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 19:58:16 -0700 Subject: [PATCH 205/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index b716fe70d1..f02bc59e07 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -69,7 +69,7 @@ where .filter(|c: DynColumn| { table .primary_key() - .into_array(); + .into_array() .into_iter() .chain(&self.set_null_columns) .all(|excluded_column| excluded_column.type_id() != c.type_id()) From ce7569720e9ea71cb4411e85de65094079c1527b Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 20:18:58 -0700 Subject: [PATCH 206/241] fix --- crates/db_schema/src/impls/comment.rs | 2 +- crates/db_schema/src/impls/community.rs | 12 +++++++----- crates/db_schema/src/impls/community_block.rs | 8 +------- crates/db_schema/src/impls/instance_block.rs | 8 +------- crates/db_schema/src/impls/person.rs | 12 ++---------- crates/db_schema/src/impls/person_block.rs | 18 +++++++----------- crates/db_schema/src/impls/post.rs | 2 +- crates/db_schema/src/traits.rs | 2 +- 8 files changed, 21 insertions(+), 43 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index c9afaffc05..d7a0aa10f9 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -244,7 +244,7 @@ mod tests { post::{Post, PostInsertForm}, }, traits::{Crud, Likeable, Saveable}, - utils::{build_db_pool_for_tests, uplete::uplete::Count}, + utils::{build_db_pool_for_tests, uplete::Count}, }; use diesel_ltree::Ltree; use pretty_assertions::assert_eq; diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index cc4865afe9..a6c1d76bbf 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -217,10 +217,12 @@ impl CommunityModerator { ) -> Result { let conn = &mut get_conn(pool).await?; - uplete::new(community_actions::table.filter(community_actions::community_id.eq(for_community_id))) - .set_null(community_actions::became_moderator) - .get_result(conn) - .await + uplete::new( + community_actions::table.filter(community_actions::community_id.eq(for_community_id)), + ) + .set_null(community_actions::became_moderator) + .get_result(conn) + .await } pub async fn leave_all_communities( @@ -451,7 +453,7 @@ mod tests { person::{Person, PersonInsertForm}, }, traits::{Bannable, Crud, Followable, Joinable}, - utils::{build_db_pool_for_tests, uplete::uplete::Count}, + utils::{build_db_pool_for_tests, uplete::Count}, CommunityVisibility, }; use pretty_assertions::assert_eq; diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index 428df331ff..874eff5f2f 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -3,13 +3,7 @@ use crate::{ schema::community_actions, source::community_block::{CommunityBlock, CommunityBlockForm}, traits::Blockable, - utils::{ - find_action, - get_conn, - now, - uplete, - DbPool, - }, + utils::{find_action, get_conn, now, uplete, DbPool}, }; use diesel::{ dsl::{exists, insert_into}, diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index 735824008d..9d53cec96e 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -3,13 +3,7 @@ use crate::{ schema::instance_actions, source::instance_block::{InstanceBlock, InstanceBlockForm}, traits::Blockable, - utils::{ - find_action, - get_conn, - now, - uplete, - DbPool, - }, + utils::{find_action, get_conn, now, uplete, DbPool}, }; use diesel::{ dsl::{exists, insert_into}, diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 446e75056e..d6d252f300 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -10,15 +10,7 @@ use crate::{ PersonUpdateForm, }, traits::{ApubActor, Crud, Followable}, - utils::{ - action_query, - functions::lower, - get_conn, - naive_now, - now, - uplete, - DbPool, - }, + utils::{action_query, functions::lower, get_conn, naive_now, now, uplete, DbPool}, }; use diesel::{ dsl::insert_into, @@ -247,7 +239,7 @@ mod tests { person::{Person, PersonFollower, PersonFollowerForm, PersonInsertForm, PersonUpdateForm}, }, traits::{Crud, Followable}, - utils::{build_db_pool_for_tests, uplete::uplete::Count}, + utils::{build_db_pool_for_tests, uplete::Count}, }; use pretty_assertions::assert_eq; use serial_test::serial; diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index 4ca0f682ee..12c484f06e 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -3,13 +3,7 @@ use crate::{ schema::person_actions, source::person_block::{PersonBlock, PersonBlockForm}, traits::Blockable, - utils::{ - find_action, - get_conn, - now, - uplete, - DbPool, - }, + utils::{find_action, get_conn, now, uplete, DbPool}, }; use diesel::{ dsl::{exists, insert_into}, @@ -64,9 +58,11 @@ impl Blockable for PersonBlock { person_block_form: &Self::Form, ) -> Result { let conn = &mut get_conn(pool).await?; - uplete::new(person_actions::table.find((person_block_form.person_id, person_block_form.target_id))) - .set_null(person_actions::blocked) - .get_result(conn) - .await + uplete::new( + person_actions::table.find((person_block_form.person_id, person_block_form.target_id)), + ) + .set_null(person_actions::blocked) + .get_result(conn) + .await } } diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 46e2e870e7..fe6637935a 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -415,7 +415,7 @@ mod tests { }, }, traits::{Crud, Likeable, Saveable}, - utils::{build_db_pool_for_tests, uplete::uplete::Count}, + utils::{build_db_pool_for_tests, uplete::Count}, }; use pretty_assertions::assert_eq; use serial_test::serial; diff --git a/crates/db_schema/src/traits.rs b/crates/db_schema/src/traits.rs index d9b64114a6..c6f70cb721 100644 --- a/crates/db_schema/src/traits.rs +++ b/crates/db_schema/src/traits.rs @@ -1,7 +1,7 @@ use crate::{ diesel::OptionalExtension, newtypes::{CommunityId, DbUrl, PersonId}, - utils::{get_conn, uplete::uplete::Count, DbPool}, + utils::{get_conn, uplete::Count, DbPool}, }; use diesel::{ associations::HasTable, From d124a58b6f046a3f3b60c8115f91f68d9189f879 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 20:42:06 -0700 Subject: [PATCH 207/241] fix --- crates/db_schema/src/impls/comment.rs | 2 +- crates/db_schema/src/impls/community.rs | 2 +- crates/db_schema/src/impls/person.rs | 2 +- crates/db_schema/src/impls/post.rs | 2 +- crates/db_schema/src/traits.rs | 2 +- crates/db_views/src/vote_view.rs | 4 ++-- crates/db_views_actor/src/comment_reply_view.rs | 2 +- crates/db_views_actor/src/person_mention_view.rs | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index d7a0aa10f9..280f8ae851 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -244,7 +244,7 @@ mod tests { post::{Post, PostInsertForm}, }, traits::{Crud, Likeable, Saveable}, - utils::{build_db_pool_for_tests, uplete::Count}, + utils::{build_db_pool_for_tests, uplete}, }; use diesel_ltree::Ltree; use pretty_assertions::assert_eq; diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index a6c1d76bbf..2d49a01f62 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -453,7 +453,7 @@ mod tests { person::{Person, PersonInsertForm}, }, traits::{Bannable, Crud, Followable, Joinable}, - utils::{build_db_pool_for_tests, uplete::Count}, + utils::{build_db_pool_for_tests, uplete}, CommunityVisibility, }; use pretty_assertions::assert_eq; diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index d6d252f300..cdf183cb05 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -239,7 +239,7 @@ mod tests { person::{Person, PersonFollower, PersonFollowerForm, PersonInsertForm, PersonUpdateForm}, }, traits::{Crud, Followable}, - utils::{build_db_pool_for_tests, uplete::Count}, + utils::{build_db_pool_for_tests, uplete}, }; use pretty_assertions::assert_eq; use serial_test::serial; diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index fe6637935a..bebdd57fe6 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -415,7 +415,7 @@ mod tests { }, }, traits::{Crud, Likeable, Saveable}, - utils::{build_db_pool_for_tests, uplete::Count}, + utils::{build_db_pool_for_tests, uplete}, }; use pretty_assertions::assert_eq; use serial_test::serial; diff --git a/crates/db_schema/src/traits.rs b/crates/db_schema/src/traits.rs index c6f70cb721..300e2e2bc9 100644 --- a/crates/db_schema/src/traits.rs +++ b/crates/db_schema/src/traits.rs @@ -1,7 +1,7 @@ use crate::{ diesel::OptionalExtension, newtypes::{CommunityId, DbUrl, PersonId}, - utils::{get_conn, uplete::Count, DbPool}, + utils::{get_conn, uplete, DbPool}, }; use diesel::{ associations::HasTable, diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index 6cb4c8c87f..0a0748df80 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -183,8 +183,8 @@ mod tests { // Timothy votes down his own comment let timmy_comment_vote_form = CommentLikeForm { - comment_id: inserted_comment.id, post_id: inserted_post.id, + comment_id: inserted_comment.id, person_id: inserted_timmy.id, score: -1, }; @@ -194,8 +194,8 @@ mod tests { // Sara upvotes timmy's comment let sara_comment_vote_form = CommentLikeForm { - comment_id: inserted_comment.id, post_id: inserted_post.id, + comment_id: inserted_comment.id, person_id: inserted_sara.id, score: 1, }; diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index 1c8b576e99..a20c72f6c9 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -188,7 +188,7 @@ impl CommentReplyView { } query - // Dont count replies from blocked users + // Don't count replies from blocked users .filter(person_actions::blocked.is_null()) .filter(comment_reply::recipient_id.eq(local_user.person_id)) .filter(comment_reply::read.eq(false)) diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index cbd513f730..3e25bbf7df 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -189,7 +189,7 @@ impl PersonMentionView { } query - // Dont count replies from blocked users + // Don't count replies from blocked users .filter(person_actions::blocked.is_null()) .filter(person_mention::recipient_id.eq(local_user.person_id)) .filter(person_mention::read.eq(false)) From c72ec047daddf96d7c4439ac38c34d9df3a5c3d4 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 20:52:00 -0700 Subject: [PATCH 208/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index f02bc59e07..2ab2173dd7 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -61,16 +61,15 @@ where fn as_query(self) -> Self::Query { let table = Q::Table::default(); + let primary_key_columns = table.primary_key().into_array(); let deletion_condition = || { AllNull( Q::Table::all_columns() .into_array() .into_iter() .filter(|c: DynColumn| { - table - .primary_key() - .into_array() - .into_iter() + primary_key_columns + .iter() .chain(&self.set_null_columns) .all(|excluded_column| excluded_column.type_id() != c.type_id()) }) From 2aaddb1b8def49e9b9553413f637d5da53777284 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 20:59:41 -0700 Subject: [PATCH 209/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 2ab2173dd7..4f3b415bbe 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -49,7 +49,7 @@ where Q::Table: Default + QueryFragment + Send + 'static, ::PrimaryKey: IntoArray + QueryFragment + Send + 'static, ::AllColumns: IntoArray, - <::PrimaryKey as IntoArray>::Output: IntoIterator, + <::PrimaryKey as IntoArray>::Output: AsRef<[DynColumn]>, <::AllColumns as IntoArray>::Output: IntoIterator, Q: Clone + FilterDsl + FilterDsl>, dsl::Filter: QueryFragment + Send + 'static, @@ -69,6 +69,7 @@ where .into_iter() .filter(|c: DynColumn| { primary_key_columns + .as_ref() .iter() .chain(&self.set_null_columns) .all(|excluded_column| excluded_column.type_id() != c.type_id()) From fa312c2b1dcd98667c501551a4e028bd3dbb8512 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Wed, 29 May 2024 21:11:42 -0700 Subject: [PATCH 210/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 4f3b415bbe..83057370ef 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -67,7 +67,7 @@ where Q::Table::all_columns() .into_array() .into_iter() - .filter(|c: DynColumn| { + .filter(|c: &DynColumn| { primary_key_columns .as_ref() .iter() From 4b0c0598c674702de01382b8ff45113c2ca28c8d Mon Sep 17 00:00:00 2001 From: dullbananas Date: Thu, 30 May 2024 05:59:18 -0700 Subject: [PATCH 211/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 83057370ef..472380d08b 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -60,8 +60,8 @@ where type SqlType = (sql_types::BigInt, sql_types::BigInt); fn as_query(self) -> Self::Query { - let table = Q::Table::default(); - let primary_key_columns = table.primary_key().into_array(); + let table = Q::Table::default; + let primary_key_columns = table().primary_key().into_array(); let deletion_condition = || { AllNull( Q::Table::all_columns() @@ -89,8 +89,8 @@ where // statement: only the update is performed." update_subquery: Box::new(self.query.clone().filter(dsl::not(deletion_condition()))), delete_subquery: Box::new(self.query.filter(deletion_condition())), - table: Box::new(table), - primary_key: Box::new(table.primary_key()), + table: Box::new(table()), + primary_key: Box::new(table().primary_key()), set_null_columns: self.set_null_columns, } } From 102f4b35660c2856b133a59cf49e9a9e133765d7 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Thu, 30 May 2024 10:29:33 -0700 Subject: [PATCH 212/241] fix --- crates/db_views/src/comment_view.rs | 1 - crates/db_views/src/post_view.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index c2b1717789..8d81f1ec21 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -32,7 +32,6 @@ use lemmy_db_schema::{ utils::{ actions, actions_alias, - functions::coalesce, fuzzy_search, limit_and_offset, DbConn, diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index ec3bfd01c9..9f7d669625 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -60,8 +60,8 @@ fn queries<'a>() -> Queries< impl ListFn<'a, PostView, (PostQuery<'a>, &'a Site)>, > { let creator_is_admin = exists( - post::table.filter( - comment::creator_id + local_user::table.filter( + post_aggregates::creator_id .eq(local_user::person_id) .and(local_user::admin.eq(true)), ), From 3ee0beca3dcdb00a318c375df9c33847b84cfcea Mon Sep 17 00:00:00 2001 From: dullbananas Date: Thu, 30 May 2024 10:48:09 -0700 Subject: [PATCH 213/241] fix test --- crates/db_schema/src/impls/comment.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 280f8ae851..426e11ca9b 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -348,7 +348,6 @@ mod tests { let expected_comment_saved = CommentSaved { comment_id: inserted_comment.id, - post_id: inserted_post.id, person_id: inserted_person.id, published: inserted_comment_saved.published, }; From ba13257618f8a75129aa4e4f249a72e8e7b0ad3c Mon Sep 17 00:00:00 2001 From: dullbananas Date: Thu, 30 May 2024 11:52:54 -0700 Subject: [PATCH 214/241] fix db_views_actor imports --- crates/db_views_actor/src/comment_reply_view.rs | 5 +++-- crates/db_views_actor/src/person_mention_view.rs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index a20c72f6c9..398c32ae8e 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -1,9 +1,11 @@ use crate::structs::CommentReplyView; use diesel::{ - dsl::exists, + dsl::{exists, not}, pg::Pg, result::Error, + BoolExpressionMethods, ExpressionMethods, + JoinOnDsl, NullableExpressionMethods, QueryDsl, }; @@ -27,7 +29,6 @@ use lemmy_db_schema::{ utils::{ actions, actions_alias, - functions::coalesce, get_conn, limit_and_offset, DbConn, diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index 3e25bbf7df..69ad2b281c 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -1,9 +1,11 @@ use crate::structs::PersonMentionView; use diesel::{ - dsl::exists, + dsl::{exists, not}, pg::Pg, result::Error, + BoolExpressionMethods, ExpressionMethods, + JoinOnDsl, NullableExpressionMethods, QueryDsl, }; @@ -27,7 +29,6 @@ use lemmy_db_schema::{ utils::{ actions, actions_alias, - functions::coalesce, get_conn, limit_and_offset, DbConn, From 37f34063d21812f52d09e2558603dc84a19d49ff Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Thu, 30 May 2024 19:13:02 +0000 Subject: [PATCH 215/241] fix uplete import in post_view test --- crates/db_views/src/post_view.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index 9f7d669625..2e91079227 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -613,7 +613,7 @@ mod tests { site::Site, }, traits::{Bannable, Blockable, Crud, Joinable, Likeable}, - utils::{build_db_pool, build_db_pool_for_tests, uplete::UpleteCount, DbPool, RANK_DEFAULT}, + utils::{build_db_pool, build_db_pool_for_tests, uplete, DbPool, RANK_DEFAULT}, CommunityVisibility, SortType, SubscribedType, @@ -953,7 +953,7 @@ mod tests { let like_removed = PostLike::remove(pool, data.local_user_view.person.id, data.inserted_post.id).await?; - assert_eq!(UpleteCount::only_deleted(1), like_removed); + assert_eq!(uplete::Count::only_deleted(1), like_removed); cleanup(data, pool).await } From ae7838e78be7baefa41cada13947689b32501678 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Thu, 30 May 2024 12:30:57 -0700 Subject: [PATCH 216/241] rerun ci --- crates/db_schema/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/Cargo.toml b/crates/db_schema/Cargo.toml index 79e15dba3e..f8f35517fa 100644 --- a/crates/db_schema/Cargo.toml +++ b/crates/db_schema/Cargo.toml @@ -38,8 +38,8 @@ full = [ "tokio-postgres-rustls", "rustls", "i-love-jesus", - "diesel-bind-if-some", "tuplex", + "diesel-bind-if-some", ] [dependencies] From 334c0f612e72b859f7e02ecdd0245e5287b9f900 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Thu, 30 May 2024 14:00:24 -0700 Subject: [PATCH 217/241] fix AllNull syntax error --- crates/db_schema/src/utils/uplete.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 472380d08b..639f76c109 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -170,12 +170,12 @@ impl ValidGrouping<()> for AllNull { impl QueryFragment for AllNull { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { - let mut item_prefix = "("; + // Must produce a valid expression even if `self.0` is empty + out.push_sql("(TRUE"); for column in &self.0 { - out.push_sql(item_prefix); + out.push_sql(" AND "); column.0.walk_ast(out.reborrow())?; out.push_sql(" IS NOT NULL"); - item_prefix = " AND "; } out.push_sql(")"); From acfc3a02a1c7fe0cc85f9e8b5666c2875673e31c Mon Sep 17 00:00:00 2001 From: dullbananas Date: Thu, 30 May 2024 16:57:24 -0700 Subject: [PATCH 218/241] fix DynColumn --- crates/db_schema/src/utils/uplete.rs | 58 +++++++++++++++------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 639f76c109..383ee7d4c6 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -11,7 +11,7 @@ use diesel::{ Expression, Table, }; -use std::any::Any; +use std::any::TypeId; use tuplex::IntoArray; /// Set columns (each specified with `UpleteBuilder::set_null`) to null in the rows found by @@ -49,7 +49,7 @@ where Q::Table: Default + QueryFragment + Send + 'static, ::PrimaryKey: IntoArray + QueryFragment + Send + 'static, ::AllColumns: IntoArray, - <::PrimaryKey as IntoArray>::Output: AsRef<[DynColumn]>, + <::PrimaryKey as IntoArray>::Output: IntoIterator, <::AllColumns as IntoArray>::Output: IntoIterator, Q: Clone + FilterDsl + FilterDsl>, dsl::Filter: QueryFragment + Send + 'static, @@ -61,22 +61,20 @@ where fn as_query(self) -> Self::Query { let table = Q::Table::default; - let primary_key_columns = table().primary_key().into_array(); - let deletion_condition = || { - AllNull( - Q::Table::all_columns() - .into_array() - .into_iter() - .filter(|c: &DynColumn| { - primary_key_columns - .as_ref() - .iter() - .chain(&self.set_null_columns) - .all(|excluded_column| excluded_column.type_id() != c.type_id()) - }) - .collect::>(), - ) - }; + let deletion_condition = AllNull( + Q::Table::all_columns() + .into_array() + .into_iter() + .filter(|c: DynColumn| { + table() + .primary_key() + .into_array(); + .into_iter() + .chain(self.set_null_columns.iter().copied()) + .all(|excluded_column| excluded_column.type_id != c.type_id) + }) + .collect::>(), + ); UpleteQuery { // Updated rows and deleted rows must not overlap, so updating all rows and using the returned // new rows to determine which ones to delete is not an option. @@ -87,8 +85,8 @@ where // the modifications takes place, but it is not easy (and sometimes not possible) to reliably // predict which one. This also applies to deleting a row that was already updated in the same // statement: only the update is performed." - update_subquery: Box::new(self.query.clone().filter(dsl::not(deletion_condition()))), - delete_subquery: Box::new(self.query.filter(deletion_condition())), + update_subquery: Box::new(self.query.clone().filter(dsl::not(deletion_condition.clone()))), + delete_subquery: Box::new(self.query.filter(deletion_condition)), table: Box::new(table()), primary_key: Box::new(table().primary_key()), set_null_columns: self.set_null_columns, @@ -135,7 +133,7 @@ impl QueryFragment for UpleteQuery { let mut item_prefix = " SET "; for column in &self.set_null_columns { out.push_sql(item_prefix); - column.0.walk_ast(out.reborrow())?; + out.push_identifier(column.name)?; out.push_sql(" = NULL"); item_prefix = ","; } @@ -158,6 +156,7 @@ impl QueryFragment for UpleteQuery { } } +#[derive(Clone)] pub struct AllNull(Vec); impl Expression for AllNull { @@ -174,7 +173,7 @@ impl QueryFragment for AllNull { out.push_sql("(TRUE"); for column in &self.0 { out.push_sql(" AND "); - column.0.walk_ast(out.reborrow())?; + out.push_identifier(column.name)?; out.push_sql(" IS NOT NULL"); } out.push_sql(")"); @@ -183,11 +182,18 @@ impl QueryFragment for AllNull { } } -pub struct DynColumn(Box + Send + 'static>); +#[derive(Clone)] +pub struct DynColumn { + type_id: TypeId, + name: &'static str, +}; -impl + Send + 'static> From for DynColumn { - fn from(value: T) -> Self { - DynColumn(Box::new(value)) +impl From for DynColumn { + fn from(_value: T) -> Self { + DynColumn { + type_id: TypeId::of::(), + name: T::NAME, + } } } From 314f336e67831c7aecf9de9123ff23e2bfd48ab1 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Thu, 30 May 2024 17:08:59 -0700 Subject: [PATCH 219/241] Fix rust syntax --- crates/db_schema/src/utils/uplete.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 383ee7d4c6..5fe466fdbc 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -68,7 +68,7 @@ where .filter(|c: DynColumn| { table() .primary_key() - .into_array(); + .into_array() .into_iter() .chain(self.set_null_columns.iter().copied()) .all(|excluded_column| excluded_column.type_id != c.type_id) @@ -186,7 +186,7 @@ impl QueryFragment for AllNull { pub struct DynColumn { type_id: TypeId, name: &'static str, -}; +} impl From for DynColumn { fn from(_value: T) -> Self { From 542ad8e6804f6f23c1bfa7054b321fad6d97b3e5 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Thu, 30 May 2024 17:14:17 -0700 Subject: [PATCH 220/241] fmt --- crates/db_schema/src/utils/uplete.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 5fe466fdbc..9be89a8b08 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -85,7 +85,12 @@ where // the modifications takes place, but it is not easy (and sometimes not possible) to reliably // predict which one. This also applies to deleting a row that was already updated in the same // statement: only the update is performed." - update_subquery: Box::new(self.query.clone().filter(dsl::not(deletion_condition.clone()))), + update_subquery: Box::new( + self + .query + .clone() + .filter(dsl::not(deletion_condition.clone())), + ), delete_subquery: Box::new(self.query.filter(deletion_condition)), table: Box::new(table()), primary_key: Box::new(table().primary_key()), From d469e9ad82784579fd6660b423f63544103a8445 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Thu, 30 May 2024 17:27:41 -0700 Subject: [PATCH 221/241] fix iter --- crates/db_schema/src/utils/uplete.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 9be89a8b08..ea9bc41c24 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -70,7 +70,7 @@ where .primary_key() .into_array() .into_iter() - .chain(self.set_null_columns.iter().copied()) + .chain(self.set_null_columns.iter().cloned()) .all(|excluded_column| excluded_column.type_id != c.type_id) }) .collect::>(), From 82cae91d72e2fc739f910746c1d8dc7af562bbe5 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Fri, 31 May 2024 16:56:04 +0000 Subject: [PATCH 222/241] pain --- Cargo.lock | 9 +++++++++ .../apub/src/collections/community_moderators.rs | 14 +++++++++++++- crates/db_schema/src/schema.rs | 1 + crates/db_schema/src/utils/uplete.rs | 14 ++++++++------ .../db_views_actor/src/community_moderator_view.rs | 2 ++ .../up.sql | 2 +- 6 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 716525950a..42bcf79ae2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1548,6 +1548,8 @@ dependencies = [ [[package]] name = "diesel-bind-if-some" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ed8ce9db476124d2eaf4c9db45dc6581b8e8c4c4d47d5e0f39de1fb55dfb2a7" dependencies = [ "diesel", ] @@ -2919,6 +2921,7 @@ dependencies = [ "tokio-postgres-rustls", "tracing", "ts-rs", + "tuplex", "typed-builder", "url", "uuid", @@ -6262,6 +6265,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "tuplex" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "676ac81d5454c4dcf37955d34fa8626ede3490f744b86ca14a7b90168d2a08aa" + [[package]] name = "typed-builder" version = "0.18.2" diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs index 02b912f44e..4a55a31730 100644 --- a/crates/apub/src/collections/community_moderators.rs +++ b/crates/apub/src/collections/community_moderators.rs @@ -69,7 +69,19 @@ impl Collection for ApubCommunityModerators { community_id: mod_user.community.id, person_id: mod_user.moderator.id, }; - CommunityModerator::leave(&mut data.pool(), &community_moderator_form).await?; + let c = CommunityModerator::leave(&mut data.pool(), &community_moderator_form).await?; + assert_eq!(c.deleted + c.updated, 1); + } + } + for mod_user in ¤t_moderators { + let mod_id = ObjectId::from(mod_user.moderator.actor_id.clone()); + if !apub.ordered_items.contains(&mod_id) { + let community_moderator_form = CommunityModeratorForm { + community_id: mod_user.community.id, + person_id: mod_user.moderator.id, + }; + let c = CommunityModerator::leave(&mut data.pool(), &community_moderator_form).await?; + panic!("pain"); } } diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 2ed170b0e3..1c6652ee60 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -102,6 +102,7 @@ diesel::table! { comment_actions (person_id, comment_id) { person_id -> Int4, comment_id -> Int4, + post_id -> Nullable, like_score -> Nullable, liked -> Nullable, saved -> Nullable, diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index ea9bc41c24..015068fc66 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -65,7 +65,7 @@ where Q::Table::all_columns() .into_array() .into_iter() - .filter(|c: DynColumn| { + .filter(|c: &DynColumn| { table() .primary_key() .into_array() @@ -129,7 +129,7 @@ impl QueryFragment for UpleteQuery { out.push_sql(prefix); out.push_sql(" AS ("); subquery.walk_ast(out.reborrow())?; - out.push_sql(" FOR UPDATE)"); + out.push_sql(" )"); } // Update rows that are referenced in `update_keys` @@ -154,8 +154,10 @@ impl QueryFragment for UpleteQuery { out.push_sql(") = ANY (SELECT * FROM update_keys) RETURNING 1)"); // Count updated rows and deleted rows (`RETURNING 1` makes this possible) - out.push_sql(" SELECT (SELECT count(*) FROM update_result)"); - out.push_sql(", (SELECT count(*) FROM delete_result)"); + //out.push_sql(" SELECT (SELECT count(*) FROM update_result)"); + //out.push_sql(", (SELECT count(*) FROM delete_result)"); + out.push_sql(" SELECT (SELECT count(*) FROM update_keys)"); + out.push_sql(", (SELECT count(*) FROM delete_keys)"); Ok(()) } @@ -177,9 +179,9 @@ impl QueryFragment for AllNull { // Must produce a valid expression even if `self.0` is empty out.push_sql("(TRUE"); for column in &self.0 { - out.push_sql(" AND "); + out.push_sql(" AND ("); out.push_identifier(column.name)?; - out.push_sql(" IS NOT NULL"); + out.push_sql(" IS NULL)"); } out.push_sql(")"); diff --git a/crates/db_views_actor/src/community_moderator_view.rs b/crates/db_views_actor/src/community_moderator_view.rs index e145394d47..0c1b81508b 100644 --- a/crates/db_views_actor/src/community_moderator_view.rs +++ b/crates/db_views_actor/src/community_moderator_view.rs @@ -40,11 +40,13 @@ impl CommunityModeratorView { pool: &mut DbPool<'_>, community_id: CommunityId, ) -> Result, Error> { + use diesel::NullableExpressionMethods; let conn = &mut get_conn(pool).await?; action_query(community_actions::became_moderator) .inner_join(community::table) .inner_join(person::table) .filter(community_actions::community_id.eq(community_id)) + .filter(community_actions::became_moderator.is_not_null()) .select((community::all_columns, person::all_columns)) .order_by(community_actions::became_moderator) .load::(conn) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql index daeabaa28e..e9127d11ad 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-03-17-134311_smoosh-tables-together/up.sql @@ -53,7 +53,7 @@ ALTER TABLE community_actions ADD COLUMN received_ban timestamptz, ADD COLUMN ban_expires timestamptz, ADD CONSTRAINT community_actions_check_followed CHECK ((followed IS NULL) = (follow_pending IS NULL)), - ADD CONSTRAINT community_actions_check_received_ban CHECK ((received_ban IS NULL, ban_expires IS NULL) != (FALSE, TRUE)); + ADD CONSTRAINT community_actions_check_received_ban CHECK ((received_ban IS NULL, ban_expires IS NULL) != (TRUE, FALSE)); ALTER TABLE instance_actions ALTER COLUMN blocked DROP NOT NULL, From c9482401a94d4dd7c2d0b5d48525fbce36af680b Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 31 May 2024 10:32:30 -0700 Subject: [PATCH 223/241] Update community_moderators.rs --- crates/apub/src/collections/community_moderators.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs index 4a55a31730..1845b4f428 100644 --- a/crates/apub/src/collections/community_moderators.rs +++ b/crates/apub/src/collections/community_moderators.rs @@ -73,14 +73,11 @@ impl Collection for ApubCommunityModerators { assert_eq!(c.deleted + c.updated, 1); } } + let current_moderators = + CommunityModeratorView::for_community(&mut data.pool(), community_id).await?; for mod_user in ¤t_moderators { let mod_id = ObjectId::from(mod_user.moderator.actor_id.clone()); if !apub.ordered_items.contains(&mod_id) { - let community_moderator_form = CommunityModeratorForm { - community_id: mod_user.community.id, - person_id: mod_user.moderator.id, - }; - let c = CommunityModerator::leave(&mut data.pool(), &community_moderator_form).await?; panic!("pain"); } } From ee1de0e20f9d8900d2fc948b56ff19a1b9c63e78 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 31 May 2024 10:46:06 -0700 Subject: [PATCH 224/241] Update community_moderator_view.rs --- crates/db_views_actor/src/community_moderator_view.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/db_views_actor/src/community_moderator_view.rs b/crates/db_views_actor/src/community_moderator_view.rs index 0c1b81508b..a1a01c5d3f 100644 --- a/crates/db_views_actor/src/community_moderator_view.rs +++ b/crates/db_views_actor/src/community_moderator_view.rs @@ -40,7 +40,6 @@ impl CommunityModeratorView { pool: &mut DbPool<'_>, community_id: CommunityId, ) -> Result, Error> { - use diesel::NullableExpressionMethods; let conn = &mut get_conn(pool).await?; action_query(community_actions::became_moderator) .inner_join(community::table) From 7fc191322a93504ae2cec44d278d459220a65c89 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 31 May 2024 11:21:21 -0700 Subject: [PATCH 225/241] Update uplete.rs --- crates/db_schema/src/utils/uplete.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 015068fc66..c2964686b8 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -154,10 +154,10 @@ impl QueryFragment for UpleteQuery { out.push_sql(") = ANY (SELECT * FROM update_keys) RETURNING 1)"); // Count updated rows and deleted rows (`RETURNING 1` makes this possible) - //out.push_sql(" SELECT (SELECT count(*) FROM update_result)"); - //out.push_sql(", (SELECT count(*) FROM delete_result)"); - out.push_sql(" SELECT (SELECT count(*) FROM update_keys)"); - out.push_sql(", (SELECT count(*) FROM delete_keys)"); + out.push_sql(" SELECT (SELECT count(*) FROM update_result)"); + out.push_sql(", (SELECT count(*) FROM delete_result)"); + //out.push_sql(" SELECT (SELECT count(*) FROM update_keys)"); + //out.push_sql(", (SELECT count(*) FROM delete_keys)"); Ok(()) } From 383dc4ae0089dc27e6929f4ddd520ed58f806b7f Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 31 May 2024 11:28:07 -0700 Subject: [PATCH 226/241] Fix mistake found by chatgpt --- crates/db_schema/src/utils/uplete.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index c2964686b8..db321db1d9 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -151,7 +151,7 @@ impl QueryFragment for UpleteQuery { self.table.walk_ast(out.reborrow())?; out.push_sql(" WHERE ("); self.primary_key.walk_ast(out.reborrow())?; - out.push_sql(") = ANY (SELECT * FROM update_keys) RETURNING 1)"); + out.push_sql(") = ANY (SELECT * FROM delete_keys) RETURNING 1)"); // Count updated rows and deleted rows (`RETURNING 1` makes this possible) out.push_sql(" SELECT (SELECT count(*) FROM update_result)"); From a47106170294348cd91867e23c7a1a718bb97421 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 31 May 2024 14:35:11 -0700 Subject: [PATCH 227/241] revert debugging stuff, change migration date, refactor constraint --- crates/apub/src/collections/community_moderators.rs | 11 +---------- crates/db_schema/src/utils/uplete.rs | 4 +--- crates/db_views_actor/src/community_moderator_view.rs | 1 - .../down.sql | 0 .../up.sql | 2 +- 5 files changed, 3 insertions(+), 15 deletions(-) rename migrations/{2024-03-17-134311_smoosh-tables-together => 2024-05-31-134311_smoosh-tables-together}/down.sql (100%) rename migrations/{2024-03-17-134311_smoosh-tables-together => 2024-05-31-134311_smoosh-tables-together}/up.sql (99%) diff --git a/crates/apub/src/collections/community_moderators.rs b/crates/apub/src/collections/community_moderators.rs index 1845b4f428..02b912f44e 100644 --- a/crates/apub/src/collections/community_moderators.rs +++ b/crates/apub/src/collections/community_moderators.rs @@ -69,16 +69,7 @@ impl Collection for ApubCommunityModerators { community_id: mod_user.community.id, person_id: mod_user.moderator.id, }; - let c = CommunityModerator::leave(&mut data.pool(), &community_moderator_form).await?; - assert_eq!(c.deleted + c.updated, 1); - } - } - let current_moderators = - CommunityModeratorView::for_community(&mut data.pool(), community_id).await?; - for mod_user in ¤t_moderators { - let mod_id = ObjectId::from(mod_user.moderator.actor_id.clone()); - if !apub.ordered_items.contains(&mod_id) { - panic!("pain"); + CommunityModerator::leave(&mut data.pool(), &community_moderator_form).await?; } } diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index db321db1d9..14f52eece5 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -129,7 +129,7 @@ impl QueryFragment for UpleteQuery { out.push_sql(prefix); out.push_sql(" AS ("); subquery.walk_ast(out.reborrow())?; - out.push_sql(" )"); + out.push_sql(" FOR UPDATE)"); } // Update rows that are referenced in `update_keys` @@ -156,8 +156,6 @@ impl QueryFragment for UpleteQuery { // Count updated rows and deleted rows (`RETURNING 1` makes this possible) out.push_sql(" SELECT (SELECT count(*) FROM update_result)"); out.push_sql(", (SELECT count(*) FROM delete_result)"); - //out.push_sql(" SELECT (SELECT count(*) FROM update_keys)"); - //out.push_sql(", (SELECT count(*) FROM delete_keys)"); Ok(()) } diff --git a/crates/db_views_actor/src/community_moderator_view.rs b/crates/db_views_actor/src/community_moderator_view.rs index a1a01c5d3f..e145394d47 100644 --- a/crates/db_views_actor/src/community_moderator_view.rs +++ b/crates/db_views_actor/src/community_moderator_view.rs @@ -45,7 +45,6 @@ impl CommunityModeratorView { .inner_join(community::table) .inner_join(person::table) .filter(community_actions::community_id.eq(community_id)) - .filter(community_actions::became_moderator.is_not_null()) .select((community::all_columns, person::all_columns)) .order_by(community_actions::became_moderator) .load::(conn) diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/down.sql b/migrations/2024-05-31-134311_smoosh-tables-together/down.sql similarity index 100% rename from migrations/2024-03-17-134311_smoosh-tables-together/down.sql rename to migrations/2024-05-31-134311_smoosh-tables-together/down.sql diff --git a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql b/migrations/2024-05-31-134311_smoosh-tables-together/up.sql similarity index 99% rename from migrations/2024-03-17-134311_smoosh-tables-together/up.sql rename to migrations/2024-05-31-134311_smoosh-tables-together/up.sql index e9127d11ad..ac1b66a20e 100644 --- a/migrations/2024-03-17-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-05-31-134311_smoosh-tables-together/up.sql @@ -53,7 +53,7 @@ ALTER TABLE community_actions ADD COLUMN received_ban timestamptz, ADD COLUMN ban_expires timestamptz, ADD CONSTRAINT community_actions_check_followed CHECK ((followed IS NULL) = (follow_pending IS NULL)), - ADD CONSTRAINT community_actions_check_received_ban CHECK ((received_ban IS NULL, ban_expires IS NULL) != (TRUE, FALSE)); + ADD CONSTRAINT community_actions_check_received_ban CHECK (NOT (received_ban IS NULL AND ban_expires IS NOT NULL)); ALTER TABLE instance_actions ALTER COLUMN blocked DROP NOT NULL, From 590542b7f7092ceff15dc1fb024ccfcbe1449c0e Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 31 May 2024 16:38:32 -0700 Subject: [PATCH 228/241] Update down.sql --- migrations/2024-05-31-134311_smoosh-tables-together/down.sql | 4 ---- 1 file changed, 4 deletions(-) diff --git a/migrations/2024-05-31-134311_smoosh-tables-together/down.sql b/migrations/2024-05-31-134311_smoosh-tables-together/down.sql index 38a273747c..f7ea48c6f3 100644 --- a/migrations/2024-05-31-134311_smoosh-tables-together/down.sql +++ b/migrations/2024-05-31-134311_smoosh-tables-together/down.sql @@ -254,8 +254,6 @@ ALTER INDEX comment_actions_pkey RENAME TO comment_like_pkey; ALTER INDEX idx_comment_actions_comment RENAME TO idx_comment_like_comment; -ALTER INDEX idx_comment_actions_post RENAME TO idx_comment_like_post; - ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_comment_id_fkey TO comment_like_comment_id_fkey; ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_person_id_fkey TO comment_like_person_id_fkey; @@ -298,8 +296,6 @@ CREATE INDEX idx_comment_saved_comment ON comment_saved (comment_id); CREATE INDEX idx_comment_saved_person ON comment_saved (person_id); -CREATE INDEX idx_comment_like_post ON comment_like (post_id); - CREATE INDEX idx_community_block_community ON community_block (community_id); CREATE INDEX idx_community_moderator_community ON community_moderator (community_id); From e1b0e1de3ac9357d5e14ecca496e5ae464ab124e Mon Sep 17 00:00:00 2001 From: dullbananas Date: Fri, 31 May 2024 17:10:03 -0700 Subject: [PATCH 229/241] Update down.sql --- migrations/2024-05-31-134311_smoosh-tables-together/down.sql | 2 -- 1 file changed, 2 deletions(-) diff --git a/migrations/2024-05-31-134311_smoosh-tables-together/down.sql b/migrations/2024-05-31-134311_smoosh-tables-together/down.sql index f7ea48c6f3..01cf9ef7ea 100644 --- a/migrations/2024-05-31-134311_smoosh-tables-together/down.sql +++ b/migrations/2024-05-31-134311_smoosh-tables-together/down.sql @@ -258,8 +258,6 @@ ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_comment_id_fkey TO co ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_person_id_fkey TO comment_like_person_id_fkey; -ALTER TABLE comment_like RENAME CONSTRAINT comment_actions_post_id_fkey TO comment_like_post_id_fkey; - ALTER INDEX community_actions_pkey RENAME TO community_follower_pkey; ALTER INDEX idx_community_actions_community RENAME TO idx_community_follower_community; From 076262317bc4c548f390d28b704fb03751ac4237 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 25 Jun 2024 19:37:55 +0000 Subject: [PATCH 230/241] fmt --- crates/db_views_actor/src/community_view.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs index 19c83208fd..069fa2215e 100644 --- a/crates/db_views_actor/src/community_view.rs +++ b/crates/db_views_actor/src/community_view.rs @@ -15,7 +15,7 @@ use lemmy_db_schema::{ schema::{community, community_actions, community_aggregates, instance_actions}, source::{community::CommunityFollower, local_user::LocalUser, site::Site}, utils::{ - actions, + actions, fuzzy_search, limit_and_offset, visible_communities_only, From f51764327de1f205664084da07c5ae95b0bb27b0 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 2 Jul 2024 17:04:05 +0000 Subject: [PATCH 231/241] make things added to db_schema::utils more understandable --- crates/db_schema/src/utils.rs | 41 +++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 75c3ecb77a..5fe88c8856 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -541,8 +541,7 @@ pub fn now() -> AsExprOf { diesel::dsl::now.into_sql::() } -pub type AsRecordOutput = dsl::AsExprOf::SqlType>>; - +/// Trait alias for a type that can be converted to an SQL tuple using `IntoSql::into_sql` pub trait AsRecord: Expression + AsExpression> where Self::SqlType: 'static, @@ -554,8 +553,25 @@ impl>> AsRecord for T { } +/// Output of `IntoSql::into_sql` for a type that implements `AsRecord` +pub type AsRecordOutput = dsl::AsExprOf::SqlType>>; + +/// Output of `t.on((l0, l1).into_sql().eq((r0, r1)))` type OnTupleEq = dsl::On, (R0, R1)>>; +/// Creates an `ON` clause for a table where a person ID and another column are used as the +/// primary key. Use with the `QueryDsl::left_join` method. +/// +/// This example modifies a query to make columns in `community_actions` available: +/// +/// ``` +/// community::table +/// .left_join(actions( +/// community_actions::table, +/// my_person_id, +/// community::id, +/// )) +/// ``` pub fn actions( actions_table: T, person_id: Option

, @@ -585,8 +601,7 @@ pub fn actions_alias( ) -> OnTupleEq, AliasedField, AliasedField, P, C> where Alias: QuerySource + Copy, - T: AliasSource + Default, - T::Target: Table, + T: AliasSource> + Default, K0: Column

, K1: Column
, (AliasedField, AliasedField): AsRecord, @@ -606,34 +621,32 @@ where /// `table_name::table.filter(table_name::action_name.is_not_null())`. pub fn action_query(column: C) -> dsl::Filter> where - C: Column, - C::Table: Default + FilterDsl>, - C::SqlType: SingleValue, + C: Column>, SqlType: SingleValue>, { action_query_with_fn(column, |t| t) } +/// `find_action(table_name::action_name, key)` is the same as +/// `table_name::table.find(key).filter(table_name::action_name.is_not_null())`. pub fn find_action( column: C, key: K, ) -> dsl::Filter, dsl::IsNotNull> where - C: Column, - C::Table: Default + FindDsl, - dsl::Find: FilterDsl>, - C::SqlType: SingleValue, + C: + Column>>, SqlType: SingleValue>, { action_query_with_fn(column, |t| t.find(key)) } +/// `action_query_with_fn(table_name::action_name, f)` is the same as +/// `f(table_name::table).filter(table_name::action_name.is_not_null())`. fn action_query_with_fn( column: C, f: impl FnOnce(C::Table) -> Q, ) -> dsl::Filter> where - C: Column, - C::Table: Default, - C::SqlType: SingleValue, + C: Column, Q: FilterDsl>, { f(C::Table::default()).filter(column.is_not_null()) From 02f0513389761389c69a866a4804e8d6203caeeb Mon Sep 17 00:00:00 2001 From: dullbananas Date: Tue, 2 Jul 2024 16:06:44 -0700 Subject: [PATCH 232/241] update rust version for woodpecker --- .woodpecker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index b77fd6615d..744daa1737 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -2,7 +2,7 @@ # See https://github.com/woodpecker-ci/woodpecker/issues/1677 variables: - - &rust_image "rust:1.78" + - &rust_image "rust:1.79" - &rust_nightly_image "rustlang/rust:nightly" - &install_pnpm "corepack enable pnpm" - &slow_check_paths From 9f262f50f4c2f344bec57e720d434140f9d0851c Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 15 Oct 2024 14:48:50 -0700 Subject: [PATCH 233/241] finish merge --- Cargo.lock | 9 ++++++- crates/db_schema/src/impls/community_block.rs | 17 +++++++------ crates/db_schema/src/impls/instance_block.rs | 20 +++++++-------- crates/db_schema/src/impls/person_block.rs | 17 +++++++------ crates/db_schema/src/schema.rs | 2 +- crates/db_views/src/comment_view.rs | 25 ++++++++----------- .../src/community_person_ban_view.rs | 10 +++----- .../down.sql | 0 .../up.sql | 0 src/scheduled_tasks.rs | 19 +++++++++----- 10 files changed, 64 insertions(+), 55 deletions(-) rename migrations/{2024-05-31-134311_smoosh-tables-together => 2024-10-14-134311_smoosh-tables-together}/down.sql (100%) rename migrations/{2024-05-31-134311_smoosh-tables-together => 2024-10-14-134311_smoosh-tables-together}/up.sql (100%) diff --git a/Cargo.lock b/Cargo.lock index 5681ddfd2d..20017a15ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2651,6 +2651,7 @@ dependencies = [ "tokio-postgres-rustls", "tracing", "ts-rs", + "tuplex", "url", "uuid", ] @@ -5247,6 +5248,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "tuplex" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "676ac81d5454c4dcf37955d34fa8626ede3490f744b86ca14a7b90168d2a08aa" + [[package]] name = "typenum" version = "1.17.0" @@ -5589,7 +5596,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index f12dd3a916..4f4c426bcb 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -1,12 +1,12 @@ use crate::{ newtypes::{CommunityId, PersonId}, - schema::community_actions, + schema::{community, community_actions}, source::{ community::Community, community_block::{CommunityBlock, CommunityBlockForm}, }, traits::Blockable, - utils::{find_action, get_conn, now, uplete, DbPool}, + utils::{action_query, find_action, get_conn, now, uplete, DbPool}, }; use diesel::{ dsl::{exists, insert_into, not}, @@ -27,9 +27,10 @@ impl CommunityBlock { for_community_id: CommunityId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(not(exists( - find_action(commmunity_actions::blocked, (for_person_id, for_community_id)), - ))) + select(not(exists(find_action( + community_actions::blocked, + (for_person_id, for_community_id), + )))) .get_result::(conn) .await? .then_some(()) @@ -41,13 +42,13 @@ impl CommunityBlock { person_id: PersonId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - action_query(community_actions::block) + action_query(community_actions::blocked) .inner_join(community::table) .select(community::all_columns) - .filter(community_block::person_id.eq(person_id)) + .filter(community_actions::person_id.eq(person_id)) .filter(community::deleted.eq(false)) .filter(community::removed.eq(false)) - .order_by(community_block::published) + .order_by(community_actions::blocked) .load::(conn) .await } diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index c7d27e0c34..8602a3c639 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -1,12 +1,12 @@ use crate::{ newtypes::{InstanceId, PersonId}, - schema::instance_actions, + schema::{instance, instance_actions}, source::{ instance::Instance, instance_block::{InstanceBlock, InstanceBlockForm}, }, traits::Blockable, - utils::{find_action, get_conn, now, uplete, DbPool}, + utils::{action_query, find_action, get_conn, now, uplete, DbPool}, }; use diesel::{ dsl::{exists, insert_into, not}, @@ -27,12 +27,10 @@ impl InstanceBlock { for_instance_id: InstanceId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(not(exists( - find_action( - instance_actions::blocked, - (for_person_id, for_instance_id), - ) - ))) + select(not(exists(find_action( + instance_actions::blocked, + (for_person_id, for_instance_id), + )))) .get_result::(conn) .await? .then_some(()) @@ -44,11 +42,11 @@ impl InstanceBlock { person_id: PersonId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - action_query(instance_actions::block) + action_query(instance_actions::blocked) .inner_join(instance::table) .select(instance::all_columns) - .filter(instance_block::person_id.eq(person_id)) - .order_by(instance_block::published) + .filter(instance_actions::person_id.eq(person_id)) + .order_by(instance_actions::blocked) .load::(conn) .await } diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index eef8238838..0813714417 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -1,12 +1,12 @@ use crate::{ newtypes::PersonId, - schema::person_actions, + schema::{person, person_actions}, source::{ person::Person, person_block::{PersonBlock, PersonBlockForm}, }, traits::Blockable, - utils::{find_action, get_conn, now, uplete, DbPool}, + utils::{action_query, find_action, get_conn, now, uplete, DbPool}, }; use diesel::{ dsl::{exists, insert_into, not}, @@ -14,8 +14,8 @@ use diesel::{ result::Error, select, ExpressionMethods, - NullableExpressionMethods, JoinOnDsl, + NullableExpressionMethods, QueryDsl, }; use diesel_async::RunQueryDsl; @@ -28,9 +28,10 @@ impl PersonBlock { for_recipient_id: PersonId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(not(exists( - person_block::table.find((for_person_id, for_recipient_id)), - ))) + select(not(exists(find_action( + person_actions::blocked, + (for_person_id, for_recipient_id), + )))) .get_result::(conn) .await? .then_some(()) @@ -44,7 +45,7 @@ impl PersonBlock { let conn = &mut get_conn(pool).await?; let target_person_alias = diesel::alias!(person as person1); - action_query(person_actions::block) + action_query(person_actions::blocked) .inner_join(person::table.on(person_actions::person_id.eq(person::id))) .inner_join( target_person_alias.on(person_actions::target_id.eq(target_person_alias.field(person::id))), @@ -52,7 +53,7 @@ impl PersonBlock { .select(target_person_alias.fields(person::all_columns)) .filter(person_actions::person_id.eq(person_id)) .filter(target_person_alias.field(person::deleted).eq(false)) - .order_by(person_actions::published) + .order_by(person_actions::blocked) .load::(conn) .await } diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index e5342bd805..1ed04094cb 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -733,6 +733,7 @@ diesel::table! { featured_local -> Bool, url_content_type -> Nullable, alt_text -> Nullable, + scheduled_publish_time -> Nullable, } } @@ -945,7 +946,6 @@ diesel::joinable!(community_actions -> person (person_id)); diesel::joinable!(community_aggregates -> community (community_id)); diesel::joinable!(community_language -> community (community_id)); diesel::joinable!(community_language -> language (language_id)); -diesel::joinable!(custom_emoji -> local_site (local_site_id)); diesel::joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id)); diesel::joinable!(email_verification -> local_user (local_user_id)); diesel::joinable!(federation_allowlist -> instance (instance_id)); diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index 443e1b03fd..2c9ee0a2e5 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -156,21 +156,18 @@ fn queries<'a>() -> Queries< let is_subscribed = community_actions::followed.is_not_null(); - - - match listing_type.unwrap_or_default() { - ListingType::Subscribed => query = query.filter(is_subscribed), /* TODO could be this: and(community_follower::person_id.eq(person_id_join)), */ - 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(community_actions::became_moderator.is_not_null()); - } + match options.listing_type.unwrap_or_default() { + ListingType::Subscribed => query = query.filter(is_subscribed), /* TODO could be this: and(community_follower::person_id.eq(person_id_join)), */ + 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(community_actions::became_moderator.is_not_null()); + } + } // If its saved only, then filter, and order by the saved time, not the comment creation time. if options.saved_only.unwrap_or_default() { diff --git a/crates/db_views_actor/src/community_person_ban_view.rs b/crates/db_views_actor/src/community_person_ban_view.rs index d635f238c3..f058b2061e 100644 --- a/crates/db_views_actor/src/community_person_ban_view.rs +++ b/crates/db_views_actor/src/community_person_ban_view.rs @@ -18,12 +18,10 @@ impl CommunityPersonBanView { from_community_id: CommunityId, ) -> LemmyResult<()> { let conn = &mut get_conn(pool).await?; - select(not(exists( - find_action( - community_actions::received_ban, - (from_person_id, from_community_id), - ) - ))) + select(not(exists(find_action( + community_actions::received_ban, + (from_person_id, from_community_id), + )))) .get_result::(conn) .await? .then_some(()) diff --git a/migrations/2024-05-31-134311_smoosh-tables-together/down.sql b/migrations/2024-10-14-134311_smoosh-tables-together/down.sql similarity index 100% rename from migrations/2024-05-31-134311_smoosh-tables-together/down.sql rename to migrations/2024-10-14-134311_smoosh-tables-together/down.sql diff --git a/migrations/2024-05-31-134311_smoosh-tables-together/up.sql b/migrations/2024-10-14-134311_smoosh-tables-together/up.sql similarity index 100% rename from migrations/2024-05-31-134311_smoosh-tables-together/up.sql rename to migrations/2024-10-14-134311_smoosh-tables-together/up.sql diff --git a/src/scheduled_tasks.rs b/src/scheduled_tasks.rs index dbc6d39865..6c9e8e5423 100644 --- a/src/scheduled_tasks.rs +++ b/src/scheduled_tasks.rs @@ -36,7 +36,15 @@ use lemmy_db_schema::{ post::{Post, PostUpdateForm}, }, traits::Crud, - utils::{functions::coalesce, get_conn, naive_now, now, DbPool, DELETED_REPLACEMENT_TEXT}, + utils::{ + find_action, + functions::coalesce, + get_conn, + naive_now, + now, + DbPool, + DELETED_REPLACEMENT_TEXT, + }, }; use lemmy_routes::nodeinfo::{NodeInfo, NodeInfoWellKnown}; use lemmy_utils::error::LemmyResult; @@ -470,11 +478,10 @@ async fn publish_scheduled_posts(context: &Data) { .filter(not(person::banned.or(person::deleted))) .filter(not(community::removed.or(community::deleted))) // ensure that user isnt banned from community - .filter(not(exists( - community_person_ban::table - .filter(community_person_ban::community_id.eq(community::id)) - .filter(community_person_ban::person_id.eq(person::id)), - ))) + .filter(not(exists(find_action( + community_actions::received_ban, + (person::id, community::id), + )))) .select((post::all_columns, community::all_columns)) .get_results::<(Post, Community)>(&mut conn) .await From c8aceb24a066c047767d0c6e39182b7a5f413409 Mon Sep 17 00:00:00 2001 From: dullbananas Date: Sun, 27 Oct 2024 15:06:36 -0700 Subject: [PATCH 234/241] Fix index that checked read_comments twice instead of also checking read_comments_amount --- migrations/2024-10-14-134311_smoosh-tables-together/up.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/2024-10-14-134311_smoosh-tables-together/up.sql b/migrations/2024-10-14-134311_smoosh-tables-together/up.sql index ac1b66a20e..e09e953ec1 100644 --- a/migrations/2024-10-14-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-10-14-134311_smoosh-tables-together/up.sql @@ -299,7 +299,7 @@ WHERE CREATE INDEX idx_post_actions_read_comments_not_null ON post_actions (person_id, post_id) WHERE - read_comments IS NOT NULL OR read_comments IS NOT NULL; + read_comments IS NOT NULL OR read_comments_amount IS NOT NULL; CREATE INDEX idx_post_actions_saved_not_null ON post_actions (person_id, post_id) WHERE From 53d135ec01df097ff72f218bd3e8a444465c7f35 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Wed, 30 Oct 2024 20:51:13 -0700 Subject: [PATCH 235/241] fix --- crates/db_schema/src/impls/comment.rs | 3 --- crates/db_views/src/vote_view.rs | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 5e73e6444f..11792e4283 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -179,9 +179,6 @@ impl Likeable for CommentLike { uplete::new(comment_actions::table.find((person_id, comment_id))) .set_null(comment_actions::like_score) .set_null(comment_actions::liked) - // Deleting empty `comment_actions` rows would not work without setting `post_id` to - // null, because it's not part of the primary key - .set_null(comment_actions::post_id) .get_result(conn) .await } diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index 0bd67c80c0..40f24704b0 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -53,8 +53,7 @@ impl VoteView { action_query(comment_actions::like_score) .inner_join(person::table) - .inner_join(comment::table) - .inner_join(post::table.on(comment::post_id.eq(post::id))) + .inner_join(comment::table.inner_join(post::table)) .left_join(actions_alias( creator_community_actions, comment_actions::person_id, From 2bac1b321ab7e6b917b7d4079100a8d3cd9e8e4a Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 5 Nov 2024 18:27:04 -0700 Subject: [PATCH 236/241] uplete: test_count, test_generated_sql_setting_one_column_null, test_count_methods --- crates/db_schema/src/utils/uplete.rs | 124 +++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 14f52eece5..3c225ed401 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -223,3 +223,127 @@ impl Count { } } } + +#[cfg(test)] +mod tests { + use crate::utils::{build_db_pool_for_tests, get_conn, DbConn}; + use diesel::{ + debug_query, + insert_into, + pg::Pg, + query_builder::AsQuery, + ExpressionMethods, + QueryDsl, + }; + use diesel_async::{RunQueryDsl, SimpleAsyncConnection}; + use lemmy_utils::error::LemmyResult; + use pretty_assertions::assert_eq; + use serial_test::serial; + + diesel::table! { + t (id1, id2) { + // uplete doesn't work for non-tuple primary key + id1 -> Int4, + id2 -> Int4, + a -> Nullable, + b -> Nullable, + } + } + + async fn expect_rows( + conn: &mut DbConn<'_>, + expected: &[(Option, Option)], + ) -> LemmyResult<()> { + let rows: Vec<(Option, Option)> = t::table + .select((t::a, t::b)) + .order_by(t::id1) + .load(conn) + .await?; + assert_eq!(expected, &rows); + + Ok(()) + } + + // Main purpose of this test is to check accuracy of the returned `Count`, which other modules' + // tests rely on + #[tokio::test] + #[serial] + async fn test_count() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let mut conn = get_conn(pool).await?; + + conn + .batch_execute("CREATE TABLE t (id1 serial, id2 int NOT NULL DEFAULT 1, a int, b int, PRIMARY KEY (id1, id2));") + .await?; + expect_rows(&mut conn, &[]).await?; + + insert_into(t::table) + .values(&[ + (t::a.eq(Some(1)), t::b.eq(Some(2))), + (t::a.eq(Some(3)), t::b.eq(None)), + (t::a.eq(Some(4)), t::b.eq(Some(5))), + ]) + .execute(&mut conn) + .await?; + expect_rows( + &mut conn, + &[(Some(1), Some(2)), (Some(3), None), (Some(4), Some(5))], + ) + .await?; + + let count1 = super::new(t::table) + .set_null(t::a) + .get_result(&mut conn) + .await?; + assert_eq!( + super::Count { + updated: 2, + deleted: 1 + }, + count1 + ); + expect_rows(&mut conn, &[(None, Some(2)), (None, Some(5))]).await?; + + let count2 = super::new(t::table) + .set_null(t::b) + .get_result(&mut conn) + .await?; + assert_eq!(super::Count::only_deleted(2), count2); + expect_rows(&mut conn, &[]).await?; + + conn.batch_execute("DROP TABLE t;").await?; + + Ok(()) + } + + // Unlike the `get_result` method, `debug_query` does not automatically call `as_query` + + #[test] + fn test_generated_sql_setting_one_column_null() -> LemmyResult<()> { + assert_eq!( + debug_query::(&super::new(t::table).set_null(t::b).as_query()).to_string(), + r#"WITH update_keys AS (SELECT "t"."id1", "t"."id2" FROM "t" WHERE NOT ((TRUE AND ("a" IS NULL))) FOR UPDATE), delete_keys AS (SELECT "t"."id1", "t"."id2" FROM "t" WHERE (TRUE AND ("a" IS NULL)) FOR UPDATE), update_result AS (UPDATE "t" SET "b" = NULL WHERE ("t"."id1", "t"."id2") = ANY (SELECT * FROM update_keys) RETURNING 1), delete_result AS (DELETE FROM "t" WHERE ("t"."id1", "t"."id2") = ANY (SELECT * FROM delete_keys) RETURNING 1) SELECT (SELECT count(*) FROM update_result), (SELECT count(*) FROM delete_result) -- binds: []"# + ); + + Ok(()) + } + + #[test] + fn test_count_methods() { + assert_eq!( + super::Count::only_updated(1), + super::Count { + updated: 1, + deleted: 0 + } + ); + assert_eq!( + super::Count::only_deleted(1), + super::Count { + updated: 0, + deleted: 1 + } + ); + } +} From 92b1c282782d34c8b0543a080a79efc306f32c05 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 5 Nov 2024 19:31:38 -0700 Subject: [PATCH 237/241] refactor uplete sql test --- crates/db_schema/src/utils/uplete.rs | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 3c225ed401..ba5bc05629 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -317,16 +317,34 @@ mod tests { Ok(()) } - // Unlike the `get_result` method, `debug_query` does not automatically call `as_query` + fn expected_sql(check_null: &str, set_null: &str) -> String { + let with_queries = { + let key = r#""t"."id1", "t"."id2""#; + let t = r#""t""#; + + let update_keys = format!("SELECT {key} FROM {t} WHERE NOT (({check_null})) FOR UPDATE"); + let delete_keys = format!("SELECT {key} FROM {t} WHERE ({check_null}) FOR UPDATE"); + let update_result = format!( + "UPDATE {t} SET {set_null} WHERE ({key}) = ANY (SELECT * FROM update_keys) RETURNING 1" + ); + let delete_result = + format!("DELETE FROM {t} WHERE ({key}) = ANY (SELECT * FROM delete_keys) RETURNING 1"); + + format!("update_keys AS ({update_keys}), delete_keys AS ({delete_keys}), update_result AS ({update_result}), delete_result AS ({delete_result})") + }; + let update_count = "SELECT count(*) FROM update_result"; + let delete_count = "SELECT count(*) FROM delete_result"; + + format!(r#"WITH {with_queries} SELECT ({update_count}), ({delete_count}) -- binds: []"#) + } #[test] - fn test_generated_sql_setting_one_column_null() -> LemmyResult<()> { + fn test_generated_sql() { + // Unlike the `get_result` method, `debug_query` does not automatically call `as_query` assert_eq!( debug_query::(&super::new(t::table).set_null(t::b).as_query()).to_string(), - r#"WITH update_keys AS (SELECT "t"."id1", "t"."id2" FROM "t" WHERE NOT ((TRUE AND ("a" IS NULL))) FOR UPDATE), delete_keys AS (SELECT "t"."id1", "t"."id2" FROM "t" WHERE (TRUE AND ("a" IS NULL)) FOR UPDATE), update_result AS (UPDATE "t" SET "b" = NULL WHERE ("t"."id1", "t"."id2") = ANY (SELECT * FROM update_keys) RETURNING 1), delete_result AS (DELETE FROM "t" WHERE ("t"."id1", "t"."id2") = ANY (SELECT * FROM delete_keys) RETURNING 1) SELECT (SELECT count(*) FROM update_result), (SELECT count(*) FROM delete_result) -- binds: []"# + expected_sql(r#"TRUE AND ("a" IS NULL)"#, r#""b" = NULL"#) ); - - Ok(()) } #[test] From 4e4444e0eb66f86c32cab5eb7af83401ae2eb8b7 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 5 Nov 2024 19:49:08 -0700 Subject: [PATCH 238/241] test setting both columns to null in uplete --- crates/db_schema/src/utils/uplete.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index ba5bc05629..8d73c893b6 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -345,6 +345,10 @@ mod tests { debug_query::(&super::new(t::table).set_null(t::b).as_query()).to_string(), expected_sql(r#"TRUE AND ("a" IS NULL)"#, r#""b" = NULL"#) ); + assert_eq!( + debug_query::(&super::new(t::table).set_null(t::a).set_null(t::b).as_query()).to_string(), + expected_sql(r#"TRUE"#, r#""a" = NULL,"b" = NULL"#) + ); } #[test] From 5ffd4b1d5cbbca998e114d03915cebe116532674 Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 5 Nov 2024 20:33:37 -0700 Subject: [PATCH 239/241] make AllNull generic --- crates/db_schema/src/utils/uplete.rs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 8d73c893b6..5f1c62c4b9 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -161,24 +161,25 @@ impl QueryFragment for UpleteQuery { } } +// Types other than `DynColumn` are only used in tests #[derive(Clone)] -pub struct AllNull(Vec); +pub struct AllNull(Vec); -impl Expression for AllNull { +impl Expression for AllNull { type SqlType = sql_types::Bool; } -impl ValidGrouping<()> for AllNull { +impl ValidGrouping<()> for AllNull { type IsAggregate = is_aggregate::No; } -impl QueryFragment for AllNull { +impl> QueryFragment for AllNull { fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { // Must produce a valid expression even if `self.0` is empty out.push_sql("(TRUE"); - for column in &self.0 { + for item in &self.0 { out.push_sql(" AND ("); - out.push_identifier(column.name)?; + item.walk_ast(out.reborrow())?; out.push_sql(" IS NULL)"); } out.push_sql(")"); @@ -202,6 +203,12 @@ impl From for DynColumn { } } +impl QueryFragment for DynColumn { + fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, Pg>) -> Result<(), Error> { + out.push_identifier(self.name) + } +} + #[derive(Queryable, PartialEq, Eq, Debug)] pub struct Count { pub updated: i64, @@ -346,7 +353,13 @@ mod tests { expected_sql(r#"TRUE AND ("a" IS NULL)"#, r#""b" = NULL"#) ); assert_eq!( - debug_query::(&super::new(t::table).set_null(t::a).set_null(t::b).as_query()).to_string(), + debug_query::( + &super::new(t::table) + .set_null(t::a) + .set_null(t::b) + .as_query() + ) + .to_string(), expected_sql(r#"TRUE"#, r#""a" = NULL,"b" = NULL"#) ); } From 83b4b9058ec3c4fbd0c24ebe7bce89376e97275d Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Tue, 5 Nov 2024 22:04:45 -0700 Subject: [PATCH 240/241] test AllNull --- crates/db_schema/src/utils/uplete.rs | 41 +++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 5f1c62c4b9..857ef66a3f 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -233,20 +233,35 @@ impl Count { #[cfg(test)] mod tests { + use super::AllNull; use crate::utils::{build_db_pool_for_tests, get_conn, DbConn}; use diesel::{ debug_query, insert_into, pg::Pg, - query_builder::AsQuery, + query_builder::{AsQuery, QueryId}, + select, + sql_types, + AppearsOnTable, ExpressionMethods, + IntoSql, QueryDsl, + SelectableExpression, }; use diesel_async::{RunQueryDsl, SimpleAsyncConnection}; use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; + impl AppearsOnTable for AllNull {} + + impl SelectableExpression for AllNull {} + + impl QueryId for AllNull { + type QueryId = (); + const HAS_STATIC_QUERY_ID: bool = false; + } + diesel::table! { t (id1, id2) { // uplete doesn't work for non-tuple primary key @@ -381,4 +396,28 @@ mod tests { } ); } + + #[tokio::test] + #[serial] + async fn test_all_null() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests().await; + let pool = &mut pool.into(); + let mut conn = get_conn(pool).await?; + + let some = Some(1).into_sql::>(); + let none = None::.into_sql::>(); + + // Allows type inference for `vec![]` + let mut all_null = |items| select(AllNull(items)).get_result::(&mut conn); + + assert!(all_null(vec![]).await?); + assert!(all_null(vec![none]).await?); + assert!(all_null(vec![none, none]).await?); + assert!(all_null(vec![none, none, none]).await?); + assert!(!all_null(vec![some]).await?); + assert!(!all_null(vec![some, none]).await?); + assert!(!all_null(vec![none, some, none]).await?); + + Ok(()) + } } From 9bde15ed419cd04df1817321c5456f2e86ad5baa Mon Sep 17 00:00:00 2001 From: Dull Bananas Date: Sun, 10 Nov 2024 21:49:41 -0700 Subject: [PATCH 241/241] Merge remote-tracking branch 'upstream/main' into smoosh-tables-together --- .woodpecker.yml | 17 +- Cargo.lock | 17 +- Cargo.toml | 6 +- api_tests/package.json | 7 +- api_tests/pnpm-lock.yaml | 446 ++++++++---------- api_tests/src/comment.spec.ts | 2 +- api_tests/src/private_community.spec.ts | 214 +++++++++ api_tests/src/shared.ts | 56 ++- config/defaults.hjson | 2 +- crates/api/src/comment/distinguish.rs | 4 +- crates/api/src/comment/like.rs | 5 +- crates/api/src/comment_report/create.rs | 5 +- crates/api/src/comment_report/resolve.rs | 2 +- crates/api/src/community/add_mod.rs | 12 +- crates/api/src/community/ban.rs | 14 +- crates/api/src/community/block.rs | 10 +- crates/api/src/community/follow.rs | 55 ++- crates/api/src/community/hide.rs | 3 +- crates/api/src/community/mod.rs | 1 + .../src/community/pending_follows/approve.rs | 46 ++ .../src/community/pending_follows/count.rs | 25 + .../api/src/community/pending_follows/list.rs | 29 ++ .../api/src/community/pending_follows/mod.rs | 3 + crates/api/src/community/transfer.rs | 8 +- crates/api/src/lib.rs | 9 +- crates/api/src/local_user/ban_person.rs | 3 +- crates/api/src/local_user/save_settings.rs | 1 + crates/api/src/post/feature.rs | 7 +- crates/api/src/post/get_link_metadata.rs | 5 +- crates/api/src/post/like.rs | 22 +- crates/api/src/post/lock.rs | 11 +- crates/api/src/post_report/create.rs | 5 +- crates/api/src/post_report/resolve.rs | 2 +- crates/api/src/site/purge/comment.rs | 3 +- crates/api/src/site/purge/community.rs | 3 +- crates/api/src/site/purge/person.rs | 3 +- crates/api/src/site/purge/post.rs | 3 +- .../site/registration_applications/tests.rs | 5 +- crates/api/src/sitemap.rs | 14 +- crates/api_common/src/claims.rs | 2 +- crates/api_common/src/comment.rs | 24 + crates/api_common/src/community.rs | 80 ++++ crates/api_common/src/context.rs | 2 +- crates/api_common/src/custom_emoji.rs | 4 + crates/api_common/src/lib.rs | 2 +- crates/api_common/src/oauth_provider.rs | 18 +- crates/api_common/src/person.rs | 65 +++ crates/api_common/src/post.rs | 50 ++ crates/api_common/src/private_message.rs | 7 + crates/api_common/src/request.rs | 2 +- crates/api_common/src/send_activity.rs | 7 +- crates/api_common/src/site.rs | 133 ++++++ crates/api_common/src/tagline.rs | 2 + crates/api_common/src/utils.rs | 38 +- crates/api_crud/src/comment/create.rs | 10 +- crates/api_crud/src/comment/delete.rs | 5 +- crates/api_crud/src/comment/remove.rs | 5 +- crates/api_crud/src/comment/update.rs | 5 +- crates/api_crud/src/community/create.rs | 11 +- crates/api_crud/src/community/delete.rs | 9 +- crates/api_crud/src/community/mod.rs | 17 + crates/api_crud/src/community/remove.rs | 6 +- crates/api_crud/src/community/update.rs | 7 +- crates/api_crud/src/post/create.rs | 15 +- crates/api_crud/src/post/delete.rs | 16 +- crates/api_crud/src/post/mod.rs | 2 +- crates/api_crud/src/post/remove.rs | 11 +- crates/api_crud/src/post/update.rs | 18 +- crates/api_crud/src/private_message/create.rs | 14 +- crates/api_crud/src/private_message/delete.rs | 3 +- crates/api_crud/src/private_message/update.rs | 3 +- crates/api_crud/src/user/delete.rs | 3 +- .../objects/{note.json => note_1.json} | 0 .../apub/assets/mastodon/objects/note_2.json | 79 ++++ .../apub/src/activities/block/block_user.rs | 21 +- crates/apub/src/activities/block/mod.rs | 12 + .../src/activities/block/undo_block_user.rs | 15 +- .../apub/src/activities/community/announce.rs | 9 +- .../activities/community/collection_add.rs | 11 +- .../activities/community/collection_remove.rs | 11 +- .../src/activities/community/lock_page.rs | 13 +- .../apub/src/activities/community/update.rs | 9 +- .../activities/create_or_update/comment.rs | 8 +- .../src/activities/create_or_update/post.rs | 8 +- crates/apub/src/activities/deletion/delete.rs | 4 +- crates/apub/src/activities/deletion/mod.rs | 26 +- .../src/activities/deletion/undo_delete.rs | 4 +- .../apub/src/activities/following/follow.rs | 24 +- crates/apub/src/activities/following/mod.rs | 49 +- .../apub/src/activities/following/reject.rs | 79 ++++ .../src/activities/following/undo_follow.rs | 6 +- crates/apub/src/activities/mod.rs | 30 ++ crates/apub/src/activity_lists.rs | 11 +- crates/apub/src/api/list_posts.rs | 2 + crates/apub/src/api/resolve_object.rs | 2 +- crates/apub/src/api/user_settings_backup.rs | 21 +- .../src/fetcher/site_or_community_or_user.rs | 11 + crates/apub/src/http/comment.rs | 13 +- crates/apub/src/http/community.rs | 55 ++- crates/apub/src/http/mod.rs | 47 +- crates/apub/src/http/person.rs | 2 +- crates/apub/src/http/post.rs | 14 +- crates/apub/src/lib.rs | 15 +- crates/apub/src/objects/comment.rs | 22 +- crates/apub/src/objects/community.rs | 9 +- crates/apub/src/objects/mod.rs | 19 +- crates/apub/src/objects/person.rs | 9 +- crates/apub/src/objects/post.rs | 7 +- crates/apub/src/objects/private_message.rs | 23 +- .../src/protocol/activities/following/mod.rs | 1 + .../protocol/activities/following/reject.rs | 24 + crates/apub/src/protocol/objects/group.rs | 2 + crates/apub/src/protocol/objects/mod.rs | 3 +- crates/apub/src/protocol/objects/note.rs | 13 +- crates/apub/src/protocol/objects/page.rs | 21 +- crates/db_perf/src/main.rs | 2 +- .../src/aggregates/comment_aggregates.rs | 2 +- .../src/aggregates/community_aggregates.rs | 19 +- .../src/aggregates/person_aggregates.rs | 2 +- .../src/aggregates/post_aggregates.rs | 4 +- .../src/aggregates/site_aggregates.rs | 4 +- crates/db_schema/src/impls/activity.rs | 4 +- crates/db_schema/src/impls/actor_language.rs | 12 +- crates/db_schema/src/impls/captcha_answer.rs | 6 +- crates/db_schema/src/impls/comment.rs | 2 +- crates/db_schema/src/impls/community.rs | 66 ++- crates/db_schema/src/impls/community_block.rs | 2 +- .../src/impls/federation_allowlist.rs | 2 +- crates/db_schema/src/impls/instance_block.rs | 2 +- crates/db_schema/src/impls/language.rs | 2 +- crates/db_schema/src/impls/local_user.rs | 5 +- crates/db_schema/src/impls/login_token.rs | 2 +- crates/db_schema/src/impls/moderator.rs | 2 +- .../src/impls/password_reset_request.rs | 2 +- crates/db_schema/src/impls/person.rs | 6 +- crates/db_schema/src/impls/person_block.rs | 2 +- crates/db_schema/src/impls/post.rs | 2 +- crates/db_schema/src/impls/post_report.rs | 4 +- crates/db_schema/src/impls/private_message.rs | 2 +- crates/db_schema/src/impls/site.rs | 2 +- crates/db_schema/src/lib.rs | 5 +- crates/db_schema/src/newtypes.rs | 16 +- crates/db_schema/src/schema.rs | 30 +- crates/db_schema/src/sensitive.rs | 19 +- crates/db_schema/src/source/comment.rs | 1 + crates/db_schema/src/source/comment_report.rs | 2 + crates/db_schema/src/source/community.rs | 38 +- crates/db_schema/src/source/custom_emoji.rs | 1 + .../src/source/federation_queue_state.rs | 3 + crates/db_schema/src/source/images.rs | 1 + crates/db_schema/src/source/instance.rs | 3 + crates/db_schema/src/source/local_site.rs | 4 + .../src/source/local_site_rate_limit.rs | 1 + .../src/source/local_site_url_blocklist.rs | 1 + crates/db_schema/src/source/local_user.rs | 6 + crates/db_schema/src/source/login_token.rs | 2 + crates/db_schema/src/source/moderator.rs | 12 + crates/db_schema/src/source/oauth_account.rs | 1 + crates/db_schema/src/source/oauth_provider.rs | 1 + crates/db_schema/src/source/person.rs | 7 + crates/db_schema/src/source/post.rs | 14 +- crates/db_schema/src/source/post_report.rs | 4 + .../db_schema/src/source/private_message.rs | 1 + .../src/source/private_message_report.rs | 2 + .../src/source/registration_application.rs | 2 + crates/db_schema/src/source/site.rs | 6 + crates/db_schema/src/source/tagline.rs | 1 + crates/db_schema/src/utils.rs | 62 +-- crates/db_schema/src/utils/uplete.rs | 4 +- crates/db_views/src/comment_report_view.rs | 2 +- crates/db_views/src/comment_view.rs | 148 +++++- crates/db_views/src/custom_emoji_view.rs | 8 +- crates/db_views/src/post_report_view.rs | 2 +- crates/db_views/src/post_view.rs | 264 ++++++++++- .../src/private_message_report_view.rs | 2 +- crates/db_views/src/private_message_view.rs | 6 +- .../src/registration_application_view.rs | 3 +- crates/db_views/src/site_view.rs | 2 +- crates/db_views/src/structs.rs | 9 + crates/db_views/src/vote_view.rs | 2 +- crates/db_views_actor/Cargo.toml | 3 +- .../db_views_actor/src/comment_reply_view.rs | 2 +- .../src/community_follower_view.rs | 238 +++++++++- .../src/community_moderator_view.rs | 10 +- .../src/community_person_ban_view.rs | 2 +- crates/db_views_actor/src/community_view.rs | 134 ++++-- .../db_views_actor/src/person_mention_view.rs | 2 +- crates/db_views_actor/src/person_view.rs | 8 +- crates/db_views_actor/src/structs.rs | 13 + crates/db_views_moderator/src/structs.rs | 22 + crates/utils/src/error.rs | 13 +- crates/utils/src/lib.rs | 1 - crates/utils/src/settings/structs.rs | 2 +- .../utils/src/utils/markdown/image_links.rs | 17 +- crates/utils/src/utils/markdown/mod.rs | 2 +- crates/utils/tests/test_errors_used.rs | 2 +- crates/utils/translations | 2 +- diesel.toml | 2 + docker/docker-compose.yml | 10 +- docker/federation/docker-compose.yml | 2 +- .../down.sql | 3 + .../up.sql | 3 + .../down.sql | 2 + .../up.sql | 2 + .../down.sql | 52 ++ .../up.sql | 47 ++ .../down.sql | 19 +- .../up.sql | 27 +- scripts/start_dev_db.sh | 6 +- scripts/test.sh | 4 +- scripts/ts_bindings_check.sh | 14 + src/api_routes_http.rs | 14 +- src/lib.rs | 2 +- src/prometheus_metrics.rs | 4 +- src/scheduled_tasks.rs | 12 +- src/session_middleware.rs | 2 +- 216 files changed, 3025 insertions(+), 978 deletions(-) create mode 100644 api_tests/src/private_community.spec.ts create mode 100644 crates/api/src/community/pending_follows/approve.rs create mode 100644 crates/api/src/community/pending_follows/count.rs create mode 100644 crates/api/src/community/pending_follows/list.rs create mode 100644 crates/api/src/community/pending_follows/mod.rs rename crates/apub/assets/mastodon/objects/{note.json => note_1.json} (100%) create mode 100644 crates/apub/assets/mastodon/objects/note_2.json create mode 100644 crates/apub/src/activities/following/reject.rs create mode 100644 crates/apub/src/protocol/activities/following/reject.rs create mode 100644 migrations/2023-10-24-140438_enable_private_messages/down.sql create mode 100644 migrations/2023-10-24-140438_enable_private_messages/up.sql create mode 100644 migrations/2024-10-29-090055_private-community/down.sql create mode 100644 migrations/2024-10-29-090055_private-community/up.sql rename migrations/{2024-10-27-134311_smoosh-tables-together => 2024-11-10-134311_smoosh-tables-together}/down.sql (95%) rename migrations/{2024-10-27-134311_smoosh-tables-together => 2024-11-10-134311_smoosh-tables-together}/up.sql (92%) create mode 100755 scripts/ts_bindings_check.sh diff --git a/.woodpecker.yml b/.woodpecker.yml index a2124c1cc4..060cc3e268 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -122,7 +122,6 @@ steps: environment: CARGO_HOME: .cargo_home commands: - - export LEMMY_CONFIG_LOCATION=./config/config.hjson - ./scripts/update_config_defaults.sh config/defaults_current.hjson - diff config/defaults.hjson config/defaults_current.hjson when: *slow_check_paths @@ -134,8 +133,8 @@ steps: DATABASE_URL: postgres://lemmy:password@database:5432/lemmy commands: - <<: *install_diesel_cli + - cp crates/db_schema/src/schema.rs tmp.schema - diesel migration run - - diesel print-schema --config-file=diesel.toml > tmp.schema - diff tmp.schema crates/db_schema/src/schema.rs when: *slow_check_paths @@ -147,7 +146,6 @@ steps: CARGO_HOME: .cargo_home commands: # same as scripts/db_perf.sh but without creating a new database server - - export LEMMY_CONFIG_LOCATION=config/config.hjson - cargo run --package lemmy_db_perf -- --posts 10 --read-post-pages 1 when: *slow_check_paths @@ -176,11 +174,20 @@ steps: RUST_BACKTRACE: "1" CARGO_HOME: .cargo_home LEMMY_TEST_FAST_FEDERATION: "1" + LEMMY_CONFIG_LOCATION: ../../config/config.hjson commands: - - export LEMMY_CONFIG_LOCATION=../../config/config.hjson - cargo test --workspace --no-fail-fast when: *slow_check_paths + check_ts_bindings: + image: *rust_image + environment: + CARGO_HOME: .cargo_home + commands: + - ./scripts/ts_bindings_check.sh + when: + - event: pull_request + check_diesel_migration: # TODO: use willsquire/diesel-cli image when shared libraries become optional in lemmy_server image: *rust_image @@ -215,7 +222,7 @@ steps: when: *slow_check_paths run_federation_tests: - image: node:20-bookworm-slim + image: node:22-bookworm-slim environment: LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432 DO_WRITE_HOSTS_FILE: "1" diff --git a/Cargo.lock b/Cargo.lock index 630b3e543c..caef131eaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "Inflector" -version = "0.11.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" - [[package]] name = "accept-language" version = "3.1.0" @@ -5266,22 +5260,23 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ts-rs" -version = "7.1.1" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2cae1fc5d05d47aa24b64f9a4f7cba24cdc9187a2084dd97ac57bef5eccae6" +checksum = "3a2f31991cee3dce1ca4f929a8a04fdd11fd8801aac0f2030b0fa8a0a3fef6b9" dependencies = [ "chrono", + "lazy_static", "thiserror", "ts-rs-macros", + "url", ] [[package]] name = "ts-rs-macros" -version = "7.1.1" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f7f9b821696963053a89a7bd8b292dc34420aea8294d7b225274d488f3ec92" +checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e" dependencies = [ - "Inflector", "proc-macro2", "quote", "syn 2.0.77", diff --git a/Cargo.toml b/Cargo.toml index 97428e20cf..807e24e3f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,10 +24,10 @@ doctest = false [lints] workspace = true +# See https://github.com/johnthagen/min-sized-rust for additional optimizations [profile.release] debug = 0 lto = "fat" -strip = true # Automatically strip symbols from the binary. opt-level = 3 # Optimize for speed, not size. codegen-units = 1 # Reduce parallel code generation. @@ -78,6 +78,7 @@ uninlined_format_args = "allow" unused_self = "deny" unwrap_used = "deny" unimplemented = "deny" +unused_async = "deny" [workspace.dependencies] lemmy_api = { version = "=0.19.6-beta.7", path = "./crates/api" } @@ -142,10 +143,11 @@ itertools = "0.13.0" futures = "0.3.30" http = "1.1" rosetta-i18n = "0.1.3" -ts-rs = { version = "7.1.1", features = [ +ts-rs = { version = "10.0.0", features = [ "serde-compat", "chrono-impl", "no-serde-warnings", + "url-impl", ] } rustls = { version = "0.23.12", features = ["ring"] } futures-util = "0.3.30" diff --git a/api_tests/package.json b/api_tests/package.json index 63c212d016..9a5057c001 100644 --- a/api_tests/package.json +++ b/api_tests/package.json @@ -6,16 +6,17 @@ "repository": "https://github.com/LemmyNet/lemmy", "author": "Dessalines", "license": "AGPL-3.0", - "packageManager": "pnpm@9.12.0", + "packageManager": "pnpm@9.12.3", "scripts": { "lint": "tsc --noEmit && eslint --report-unused-disable-directives && prettier --check 'src/**/*.ts'", "fix": "prettier --write src && eslint --fix src", - "api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ", + "api-test": "jest -i follow.spec.ts && jest -i image.spec.ts && jest -i user.spec.ts && jest -i private_message.spec.ts && jest -i community.spec.ts && jest -i private_community.spec.ts && jest -i post.spec.ts && jest -i comment.spec.ts ", "api-test-follow": "jest -i follow.spec.ts", "api-test-comment": "jest -i comment.spec.ts", "api-test-post": "jest -i post.spec.ts", "api-test-user": "jest -i user.spec.ts", "api-test-community": "jest -i community.spec.ts", + "api-test-private-community": "jest -i private_community.spec.ts", "api-test-private-message": "jest -i private_message.spec.ts", "api-test-image": "jest -i image.spec.ts" }, @@ -27,7 +28,7 @@ "eslint": "^9.9.0", "eslint-plugin-prettier": "^5.1.3", "jest": "^29.5.0", - "lemmy-js-client": "0.20.0-alpha.11", + "lemmy-js-client": "0.20.0-private-community.9", "prettier": "^3.2.5", "ts-jest": "^29.1.0", "typescript": "^5.5.4", diff --git a/api_tests/pnpm-lock.yaml b/api_tests/pnpm-lock.yaml index 6807f43f89..b1f18622ed 100644 --- a/api_tests/pnpm-lock.yaml +++ b/api_tests/pnpm-lock.yaml @@ -10,40 +10,40 @@ importers: devDependencies: '@types/jest': specifier: ^29.5.12 - version: 29.5.13 + version: 29.5.14 '@types/node': specifier: ^22.3.0 - version: 22.7.4 + version: 22.8.6 '@typescript-eslint/eslint-plugin': specifier: ^8.1.0 - version: 8.8.1(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.2))(eslint@9.12.0)(typescript@5.6.2) + version: 8.12.2(@typescript-eslint/parser@8.12.2(eslint@9.13.0)(typescript@5.6.3))(eslint@9.13.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: ^8.1.0 - version: 8.8.1(eslint@9.12.0)(typescript@5.6.2) + version: 8.12.2(eslint@9.13.0)(typescript@5.6.3) eslint: specifier: ^9.9.0 - version: 9.12.0 + version: 9.13.0 eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.2.1(eslint@9.12.0)(prettier@3.3.3) + version: 5.2.1(eslint@9.13.0)(prettier@3.3.3) jest: specifier: ^29.5.0 - version: 29.7.0(@types/node@22.7.4) + version: 29.7.0(@types/node@22.8.6) lemmy-js-client: - specifier: 0.20.0-alpha.11 - version: 0.20.0-alpha.11 + specifier: 0.20.0-private-community.9 + version: 0.20.0-private-community.9 prettier: specifier: ^3.2.5 version: 3.3.3 ts-jest: specifier: ^29.1.0 - version: 29.2.5(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.7.4))(typescript@5.6.2) + version: 29.2.5(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.8.6))(typescript@5.6.3) typescript: specifier: ^5.5.4 - version: 5.6.2 + version: 5.6.3 typescript-eslint: specifier: ^8.1.0 - version: 8.8.1(eslint@9.12.0)(typescript@5.6.2) + version: 8.12.2(eslint@9.13.0)(typescript@5.6.3) packages: @@ -51,8 +51,8 @@ packages: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} '@babel/compat-data@7.23.5': @@ -113,8 +113,8 @@ packages: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.23.5': @@ -125,10 +125,6 @@ packages: resolution: {integrity: sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==} engines: {node: '>=6.9.0'} - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - '@babel/parser@7.23.9': resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==} engines: {node: '>=6.0.0'} @@ -222,46 +218,46 @@ packages: '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@eslint-community/eslint-utils@4.4.0': - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.1': - resolution: {integrity: sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==} + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} '@eslint/config-array@0.18.0': resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.6.0': - resolution: {integrity: sha512-8I2Q8ykA4J0x0o7cg67FPVnehcqWTBehu/lmY+bolPFHGjh49YzGBMXTvpqVgEbBdvNCSxj6iFgiIyHzf03lzg==} + '@eslint/core@0.7.0': + resolution: {integrity: sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.1.0': resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.12.0': - resolution: {integrity: sha512-eohesHH8WFRUprDNyEREgqP6beG6htMeUYeCpkEgBCieCMme5r9zFWjzAJp//9S+Kub4rqE+jXe9Cp1a7IYIIA==} + '@eslint/js@9.13.0': + resolution: {integrity: sha512-IFLyoY4d72Z5y/6o/BazFBezupzI/taV8sGumxTAVw3lXG9A6md1Dc34T9s1FoD/an9pJH8RHbAxsaEbBed9lA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.4': resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.2.0': - resolution: {integrity: sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==} + '@eslint/plugin-kit@0.2.2': + resolution: {integrity: sha512-CXtq5nR4Su+2I47WPOlWud98Y5Lv8Kyxp2ukhgFx/eW6Blm18VXJO5WuQylPugRo8nbluoi6GvvxBLqHcvqUUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@humanfs/core@0.19.0': - resolution: {integrity: sha512-2cbWIHbZVEweE853g8jymffCA+NCMiuqeECeBBLm8dg2oFdjuGJhgN4UAbI+6v0CKbbhvtXA4qV8YR5Ji86nmw==} + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.5': - resolution: {integrity: sha512-KSPA4umqSG4LHYRodq31VDwKAvaTF4xmVlzM8Aeh4PlU1JQ3IG0wiA8C25d3RQ9nJyM3mBHyI53K06VVL/oFFg==} + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -416,14 +412,14 @@ packages: '@types/istanbul-reports@3.0.4': resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - '@types/jest@29.5.13': - resolution: {integrity: sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==} + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/node@22.7.4': - resolution: {integrity: sha512-y+NPi1rFzDs1NdQHHToqeiX2TIS79SWEAw9GYhkkx8bD0ChpfqC+n2j5OXOCpzfojBEBt6DnEnnG9MY0zk1XLg==} + '@types/node@22.8.6': + resolution: {integrity: sha512-tosuJYKrIqjQIlVCM4PEGxOmyg3FCPa/fViuJChnGeEIhjA46oy8FMVoF9su1/v8PNs2a8Q0iFNyOx0uOF91nw==} '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} @@ -434,8 +430,8 @@ packages: '@types/yargs@17.0.32': resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} - '@typescript-eslint/eslint-plugin@8.8.1': - resolution: {integrity: sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==} + '@typescript-eslint/eslint-plugin@8.12.2': + resolution: {integrity: sha512-gQxbxM8mcxBwaEmWdtLCIGLfixBMHhQjBqR8sVWNTPpcj45WlYL2IObS/DNMLH1DBP0n8qz+aiiLTGfopPEebw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 @@ -445,8 +441,8 @@ packages: typescript: optional: true - '@typescript-eslint/parser@8.8.1': - resolution: {integrity: sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==} + '@typescript-eslint/parser@8.12.2': + resolution: {integrity: sha512-MrvlXNfGPLH3Z+r7Tk+Z5moZAc0dzdVjTgUgwsdGweH7lydysQsnSww3nAmsq8blFuRD5VRlAr9YdEFw3e6PBw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -455,12 +451,12 @@ packages: typescript: optional: true - '@typescript-eslint/scope-manager@8.8.1': - resolution: {integrity: sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==} + '@typescript-eslint/scope-manager@8.12.2': + resolution: {integrity: sha512-gPLpLtrj9aMHOvxJkSbDBmbRuYdtiEbnvO25bCMza3DhMjTQw0u7Y1M+YR5JPbMsXXnSPuCf5hfq0nEkQDL/JQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@8.8.1': - resolution: {integrity: sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==} + '@typescript-eslint/type-utils@8.12.2': + resolution: {integrity: sha512-bwuU4TAogPI+1q/IJSKuD4shBLc/d2vGcRT588q+jzayQyjVK2X6v/fbR4InY2U2sgf8MEvVCqEWUzYzgBNcGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -468,12 +464,12 @@ packages: typescript: optional: true - '@typescript-eslint/types@8.8.1': - resolution: {integrity: sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==} + '@typescript-eslint/types@8.12.2': + resolution: {integrity: sha512-VwDwMF1SZ7wPBUZwmMdnDJ6sIFk4K4s+ALKLP6aIQsISkPv8jhiw65sAK6SuWODN/ix+m+HgbYDkH+zLjrzvOA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.8.1': - resolution: {integrity: sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==} + '@typescript-eslint/typescript-estree@8.12.2': + resolution: {integrity: sha512-mME5MDwGe30Pq9zKPvyduyU86PH7aixwqYR2grTglAdB+AN8xXQ1vFGpYaUSJ5o5P/5znsSBeNcs5g5/2aQwow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -481,14 +477,14 @@ packages: typescript: optional: true - '@typescript-eslint/utils@8.8.1': - resolution: {integrity: sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==} + '@typescript-eslint/utils@8.12.2': + resolution: {integrity: sha512-UTTuDIX3fkfAz6iSVa5rTuSfWIYZ6ATtEocQ/umkRSyC9O919lbZ8dcH7mysshrCdrAM03skJOEYaBugxN+M6A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/visitor-keys@8.8.1': - resolution: {integrity: sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==} + '@typescript-eslint/visitor-keys@8.12.2': + resolution: {integrity: sha512-PChz8UaKQAVNHghsHcPyx1OMHoFRUEA7rJSK/mDhdq85bk+PLsUHUBqTQTFt18VJZbmxBovM65fezlheQRsSDA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} acorn-jsx@5.3.2: @@ -496,8 +492,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true @@ -512,10 +508,6 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -609,10 +601,6 @@ packages: caniuse-lite@1.0.30001581: resolution: {integrity: sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -639,16 +627,10 @@ packages: collect-v8-coverage@1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -721,10 +703,6 @@ packages: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - escape-string-regexp@2.0.0: resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} engines: {node: '>=8'} @@ -747,20 +725,20 @@ packages: eslint-config-prettier: optional: true - eslint-scope@8.1.0: - resolution: {integrity: sha512-14dSvlhaVhKKsa9Fx1l8A17s7ah7Ef7wCakJ10LYk6+GYmP9yDti2oq2SEwcyndt6knfcZyhyxwY3i9yL78EQw==} + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.1.0: - resolution: {integrity: sha512-Q7lok0mqMUSf5a/AdAZkA5a/gHcO6snwQClVNNvFKCAVlxXucdU8pKydU5ZVZjBx5xr37vGbFFWtLQYreLzrZg==} + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.12.0: - resolution: {integrity: sha512-UVIOlTEWxwIopRL1wgSQYdnVDcEvs2wyaO6DGo5mXqe3r16IoCNWkR29iHhyaP4cICWjbgbmFUGAhh0GJRuGZw==} + eslint@9.13.0: + resolution: {integrity: sha512-EYZK6SX6zjFHST/HRytOdA/zE72Cq/bfw45LSyuwrdvcclb/gqV8RRQxywOBEWO2+WDpva6UZa4CcDeJKzUCFA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true peerDependencies: @@ -769,8 +747,8 @@ packages: jiti: optional: true - espree@10.2.0: - resolution: {integrity: sha512-upbkBJbckcCNBDBDXEbuhjbP68n+scUd3k/U2EkyM9nw+I/jPiL4cLF/Al06CF96wRltFda16sxDFrxsI1v0/g==} + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} esprima@4.0.1: @@ -911,10 +889,6 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1185,8 +1159,8 @@ packages: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} - lemmy-js-client@0.20.0-alpha.11: - resolution: {integrity: sha512-iRSG4xHMjPDIreQqVIoJ5JrMY71uk07G0Zbgyf068xKbib22J3+i1x/XgCTs6tiHlqTnw1Ig/KRq7p7qJoA4uw==} + lemmy-js-client@0.20.0-private-community.9: + resolution: {integrity: sha512-iuFezswCzIco5U5Q4Eo8HAWVE65pDW2zeO+fYLEyFl30SLw9a3gqJkip2vbDfVvoAjDXyUskZKddf1Nnj8mVcg==} leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} @@ -1330,8 +1304,8 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - picocolors@1.1.0: - resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -1480,10 +1454,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1518,8 +1488,8 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - ts-api-utils@1.3.0: - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + ts-api-utils@1.4.0: + resolution: {integrity: sha512-032cPxaEKwM+GT3vA5JXNzIaizx388rhsSW79vGRNGXfRRAdEAn2mvk36PvK5HnOchyWZ7afLEXqYCvPCrzuzQ==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' @@ -1563,8 +1533,8 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - typescript-eslint@8.8.1: - resolution: {integrity: sha512-R0dsXFt6t4SAFjUSKFjMh4pXDtq04SsFKCVGDP3ZOzNP7itF0jBcZYU4fMsZr4y7O7V7Nc751dDeESbe4PbQMQ==} + typescript-eslint@8.12.2: + resolution: {integrity: sha512-UbuVUWSrHVR03q9CWx+JDHeO6B/Hr9p4U5lRH++5tq/EbFq1faYZe50ZSBePptgfIKLEti0aPQ3hFgnPVcd8ZQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '*' @@ -1572,8 +1542,8 @@ packages: typescript: optional: true - typescript@5.6.2: - resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true @@ -1642,17 +1612,18 @@ snapshots: '@jridgewell/gen-mapping': 0.3.3 '@jridgewell/trace-mapping': 0.3.22 - '@babel/code-frame@7.24.7': + '@babel/code-frame@7.26.2': dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.1.0 + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 '@babel/compat-data@7.23.5': {} '@babel/core@7.23.9': dependencies: '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@babel/generator': 7.23.6 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.9) @@ -1706,7 +1677,7 @@ snapshots: '@babel/helper-module-imports': 7.22.15 '@babel/helper-simple-access': 7.22.5 '@babel/helper-split-export-declaration': 7.22.6 - '@babel/helper-validator-identifier': 7.22.20 + '@babel/helper-validator-identifier': 7.25.9 '@babel/helper-plugin-utils@7.22.5': {} @@ -1722,7 +1693,7 @@ snapshots: '@babel/helper-validator-identifier@7.22.20': {} - '@babel/helper-validator-identifier@7.24.7': {} + '@babel/helper-validator-identifier@7.25.9': {} '@babel/helper-validator-option@7.23.5': {} @@ -1734,13 +1705,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/highlight@7.24.7': - dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.0 - '@babel/parser@7.23.9': dependencies: '@babel/types': 7.23.9 @@ -1817,13 +1781,13 @@ snapshots: '@babel/template@7.23.9': dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@babel/parser': 7.23.9 '@babel/types': 7.23.9 '@babel/traverse@7.23.9': dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@babel/generator': 7.23.6 '@babel/helper-environment-visitor': 7.22.20 '@babel/helper-function-name': 7.23.0 @@ -1844,12 +1808,12 @@ snapshots: '@bcoe/v8-coverage@0.2.3': {} - '@eslint-community/eslint-utils@4.4.0(eslint@9.12.0)': + '@eslint-community/eslint-utils@4.4.1(eslint@9.13.0)': dependencies: - eslint: 9.12.0 + eslint: 9.13.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.1': {} + '@eslint-community/regexpp@4.12.1': {} '@eslint/config-array@0.18.0': dependencies: @@ -1859,13 +1823,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/core@0.6.0': {} + '@eslint/core@0.7.0': {} '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 debug: 4.3.7 - espree: 10.2.0 + espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.0 @@ -1875,19 +1839,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.12.0': {} + '@eslint/js@9.13.0': {} '@eslint/object-schema@2.1.4': {} - '@eslint/plugin-kit@0.2.0': + '@eslint/plugin-kit@0.2.2': dependencies: levn: 0.4.1 - '@humanfs/core@0.19.0': {} + '@humanfs/core@0.19.1': {} - '@humanfs/node@0.16.5': + '@humanfs/node@0.16.6': dependencies: - '@humanfs/core': 0.19.0 + '@humanfs/core': 0.19.1 '@humanwhocodes/retry': 0.3.1 '@humanwhocodes/module-importer@1.0.1': {} @@ -1907,7 +1871,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 22.8.6 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -1920,14 +1884,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 22.8.6 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@22.7.4) + jest-config: 29.7.0(@types/node@22.8.6) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -1952,7 +1916,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 22.8.6 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -1970,7 +1934,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 22.7.4 + '@types/node': 22.8.6 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -1992,7 +1956,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.22 - '@types/node': 22.7.4 + '@types/node': 22.8.6 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -2062,7 +2026,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 22.7.4 + '@types/node': 22.8.6 '@types/yargs': 17.0.32 chalk: 4.1.2 @@ -2132,7 +2096,7 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 22.7.4 + '@types/node': 22.8.6 '@types/istanbul-lib-coverage@2.0.6': {} @@ -2144,14 +2108,14 @@ snapshots: dependencies: '@types/istanbul-lib-report': 3.0.3 - '@types/jest@29.5.13': + '@types/jest@29.5.14': dependencies: expect: 29.7.0 pretty-format: 29.7.0 '@types/json-schema@7.0.15': {} - '@types/node@22.7.4': + '@types/node@22.8.6': dependencies: undici-types: 6.19.8 @@ -2163,92 +2127,92 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.8.1(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.2))(eslint@9.12.0)(typescript@5.6.2)': + '@typescript-eslint/eslint-plugin@8.12.2(@typescript-eslint/parser@8.12.2(eslint@9.13.0)(typescript@5.6.3))(eslint@9.13.0)(typescript@5.6.3)': dependencies: - '@eslint-community/regexpp': 4.11.1 - '@typescript-eslint/parser': 8.8.1(eslint@9.12.0)(typescript@5.6.2) - '@typescript-eslint/scope-manager': 8.8.1 - '@typescript-eslint/type-utils': 8.8.1(eslint@9.12.0)(typescript@5.6.2) - '@typescript-eslint/utils': 8.8.1(eslint@9.12.0)(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 8.8.1 - eslint: 9.12.0 + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.12.2(eslint@9.13.0)(typescript@5.6.3) + '@typescript-eslint/scope-manager': 8.12.2 + '@typescript-eslint/type-utils': 8.12.2(eslint@9.13.0)(typescript@5.6.3) + '@typescript-eslint/utils': 8.12.2(eslint@9.13.0)(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.12.2 + eslint: 9.13.0 graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.2)': + '@typescript-eslint/parser@8.12.2(eslint@9.13.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/scope-manager': 8.8.1 - '@typescript-eslint/types': 8.8.1 - '@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 8.8.1 + '@typescript-eslint/scope-manager': 8.12.2 + '@typescript-eslint/types': 8.12.2 + '@typescript-eslint/typescript-estree': 8.12.2(typescript@5.6.3) + '@typescript-eslint/visitor-keys': 8.12.2 debug: 4.3.7 - eslint: 9.12.0 + eslint: 9.13.0 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.8.1': + '@typescript-eslint/scope-manager@8.12.2': dependencies: - '@typescript-eslint/types': 8.8.1 - '@typescript-eslint/visitor-keys': 8.8.1 + '@typescript-eslint/types': 8.12.2 + '@typescript-eslint/visitor-keys': 8.12.2 - '@typescript-eslint/type-utils@8.8.1(eslint@9.12.0)(typescript@5.6.2)': + '@typescript-eslint/type-utils@8.12.2(eslint@9.13.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.2) - '@typescript-eslint/utils': 8.8.1(eslint@9.12.0)(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 8.12.2(typescript@5.6.3) + '@typescript-eslint/utils': 8.12.2(eslint@9.13.0)(typescript@5.6.3) debug: 4.3.7 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - eslint - supports-color - '@typescript-eslint/types@8.8.1': {} + '@typescript-eslint/types@8.12.2': {} - '@typescript-eslint/typescript-estree@8.8.1(typescript@5.6.2)': + '@typescript-eslint/typescript-estree@8.12.2(typescript@5.6.3)': dependencies: - '@typescript-eslint/types': 8.8.1 - '@typescript-eslint/visitor-keys': 8.8.1 + '@typescript-eslint/types': 8.12.2 + '@typescript-eslint/visitor-keys': 8.12.2 debug: 4.3.7 fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 semver: 7.6.3 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.4.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.8.1(eslint@9.12.0)(typescript@5.6.2)': + '@typescript-eslint/utils@8.12.2(eslint@9.13.0)(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.12.0) - '@typescript-eslint/scope-manager': 8.8.1 - '@typescript-eslint/types': 8.8.1 - '@typescript-eslint/typescript-estree': 8.8.1(typescript@5.6.2) - eslint: 9.12.0 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.13.0) + '@typescript-eslint/scope-manager': 8.12.2 + '@typescript-eslint/types': 8.12.2 + '@typescript-eslint/typescript-estree': 8.12.2(typescript@5.6.3) + eslint: 9.13.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/visitor-keys@8.8.1': + '@typescript-eslint/visitor-keys@8.12.2': dependencies: - '@typescript-eslint/types': 8.8.1 + '@typescript-eslint/types': 8.12.2 eslint-visitor-keys: 3.4.3 - acorn-jsx@5.3.2(acorn@8.12.1): + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: - acorn: 8.12.1 + acorn: 8.14.0 - acorn@8.12.1: {} + acorn@8.14.0: {} ajv@6.12.6: dependencies: @@ -2263,10 +2227,6 @@ snapshots: ansi-regex@5.0.1: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -2382,12 +2342,6 @@ snapshots: caniuse-lite@1.0.30001581: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2409,29 +2363,23 @@ snapshots: collect-v8-coverage@1.0.2: {} - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} concat-map@0.0.1: {} convert-source-map@2.0.0: {} - create-jest@29.7.0(@types/node@22.7.4): + create-jest@29.7.0(@types/node@22.8.6): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@22.7.4) + jest-config: 29.7.0(@types/node@22.8.6) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -2476,38 +2424,36 @@ snapshots: escalade@3.1.1: {} - escape-string-regexp@1.0.5: {} - escape-string-regexp@2.0.0: {} escape-string-regexp@4.0.0: {} - eslint-plugin-prettier@5.2.1(eslint@9.12.0)(prettier@3.3.3): + eslint-plugin-prettier@5.2.1(eslint@9.13.0)(prettier@3.3.3): dependencies: - eslint: 9.12.0 + eslint: 9.13.0 prettier: 3.3.3 prettier-linter-helpers: 1.0.0 synckit: 0.9.1 - eslint-scope@8.1.0: + eslint-scope@8.2.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.1.0: {} + eslint-visitor-keys@4.2.0: {} - eslint@9.12.0: + eslint@9.13.0: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.12.0) - '@eslint-community/regexpp': 4.11.1 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.13.0) + '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.18.0 - '@eslint/core': 0.6.0 + '@eslint/core': 0.7.0 '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.12.0 - '@eslint/plugin-kit': 0.2.0 - '@humanfs/node': 0.16.5 + '@eslint/js': 9.13.0 + '@eslint/plugin-kit': 0.2.2 + '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.3.1 '@types/estree': 1.0.6 @@ -2517,9 +2463,9 @@ snapshots: cross-spawn: 7.0.3 debug: 4.3.7 escape-string-regexp: 4.0.0 - eslint-scope: 8.1.0 - eslint-visitor-keys: 4.1.0 - espree: 10.2.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -2538,11 +2484,11 @@ snapshots: transitivePeerDependencies: - supports-color - espree@10.2.0: + espree@10.3.0: dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) - eslint-visitor-keys: 4.1.0 + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 esprima@4.0.1: {} @@ -2677,8 +2623,6 @@ snapshots: graphemer@1.4.0: {} - has-flag@3.0.0: {} - has-flag@4.0.0: {} hasown@2.0.0: @@ -2792,7 +2736,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 22.8.6 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.1 @@ -2812,16 +2756,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@22.7.4): + jest-cli@29.7.0(@types/node@22.8.6): dependencies: '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@22.7.4) + create-jest: 29.7.0(@types/node@22.8.6) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@22.7.4) + jest-config: 29.7.0(@types/node@22.8.6) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -2831,7 +2775,7 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@22.7.4): + jest-config@29.7.0(@types/node@22.8.6): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 @@ -2856,7 +2800,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 22.7.4 + '@types/node': 22.8.6 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -2885,7 +2829,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 22.8.6 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -2895,7 +2839,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 22.7.4 + '@types/node': 22.8.6 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -2921,7 +2865,7 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.3 chalk: 4.1.2 @@ -2934,7 +2878,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 22.8.6 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -2969,7 +2913,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 22.8.6 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -2997,7 +2941,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 22.8.6 chalk: 4.1.2 cjs-module-lexer: 1.2.3 collect-v8-coverage: 1.0.2 @@ -3043,7 +2987,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 22.8.6 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -3062,7 +3006,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 22.7.4 + '@types/node': 22.8.6 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -3071,17 +3015,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 22.7.4 + '@types/node': 22.8.6 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@22.7.4): + jest@29.7.0(@types/node@22.8.6): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@22.7.4) + jest-cli: 29.7.0(@types/node@22.8.6) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -3117,7 +3061,7 @@ snapshots: kleur@3.0.3: {} - lemmy-js-client@0.20.0-alpha.11: {} + lemmy-js-client@0.20.0-private-community.9: {} leven@3.1.0: {} @@ -3237,7 +3181,7 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -3250,7 +3194,7 @@ snapshots: path-parse@1.0.7: {} - picocolors@1.1.0: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -3363,10 +3307,6 @@ snapshots: strip-json-comments@3.1.1: {} - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -3398,22 +3338,22 @@ snapshots: dependencies: is-number: 7.0.0 - ts-api-utils@1.3.0(typescript@5.6.2): + ts-api-utils@1.4.0(typescript@5.6.3): dependencies: - typescript: 5.6.2 + typescript: 5.6.3 - ts-jest@29.2.5(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.7.4))(typescript@5.6.2): + ts-jest@29.2.5(@babel/core@7.23.9)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.23.9))(jest@29.7.0(@types/node@22.8.6))(typescript@5.6.3): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@22.7.4) + jest: 29.7.0(@types/node@22.8.6) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 semver: 7.6.3 - typescript: 5.6.2 + typescript: 5.6.3 yargs-parser: 21.1.1 optionalDependencies: '@babel/core': 7.23.9 @@ -3431,18 +3371,18 @@ snapshots: type-fest@0.21.3: {} - typescript-eslint@8.8.1(eslint@9.12.0)(typescript@5.6.2): + typescript-eslint@8.12.2(eslint@9.13.0)(typescript@5.6.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.8.1(@typescript-eslint/parser@8.8.1(eslint@9.12.0)(typescript@5.6.2))(eslint@9.12.0)(typescript@5.6.2) - '@typescript-eslint/parser': 8.8.1(eslint@9.12.0)(typescript@5.6.2) - '@typescript-eslint/utils': 8.8.1(eslint@9.12.0)(typescript@5.6.2) + '@typescript-eslint/eslint-plugin': 8.12.2(@typescript-eslint/parser@8.12.2(eslint@9.13.0)(typescript@5.6.3))(eslint@9.13.0)(typescript@5.6.3) + '@typescript-eslint/parser': 8.12.2(eslint@9.13.0)(typescript@5.6.3) + '@typescript-eslint/utils': 8.12.2(eslint@9.13.0)(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - eslint - supports-color - typescript@5.6.2: {} + typescript@5.6.3: {} undici-types@6.19.8: {} @@ -3450,7 +3390,7 @@ snapshots: dependencies: browserslist: 4.22.3 escalade: 3.1.1 - picocolors: 1.1.0 + picocolors: 1.1.1 uri-js@4.4.1: dependencies: diff --git a/api_tests/src/comment.spec.ts b/api_tests/src/comment.spec.ts index 1534058206..c3f4b3efea 100644 --- a/api_tests/src/comment.spec.ts +++ b/api_tests/src/comment.spec.ts @@ -860,7 +860,7 @@ test("Dont send a comment reply to a blocked community", async () => { /// Fetching a deeply nested comment can lead to stack overflow as all parent comments are also /// fetched recursively. Ensure that it works properly. -test("Fetch a deeply nested comment", async () => { +test.skip("Fetch a deeply nested comment", async () => { let lastComment; for (let i = 0; i < 50; i++) { let commentRes = await createComment( diff --git a/api_tests/src/private_community.spec.ts b/api_tests/src/private_community.spec.ts new file mode 100644 index 0000000000..76faf800fb --- /dev/null +++ b/api_tests/src/private_community.spec.ts @@ -0,0 +1,214 @@ +jest.setTimeout(120000); + +import { FollowCommunity } from "lemmy-js-client"; +import { + alpha, + setupLogins, + createCommunity, + unfollows, + registerUser, + listCommunityPendingFollows, + getCommunity, + getCommunityPendingFollowsCount, + approveCommunityPendingFollow, + randomString, + createPost, + createComment, + beta, + resolveCommunity, + betaUrl, + resolvePost, + resolveComment, + likeComment, + waitUntil, +} from "./shared"; + +beforeAll(setupLogins); +afterAll(unfollows); + +test("Follow a private community", async () => { + // create private community + const community = await createCommunity(alpha, randomString(10), "Private"); + expect(community.community_view.community.visibility).toBe("Private"); + const alphaCommunityId = community.community_view.community.id; + + // No pending follows yet + const pendingFollows0 = await listCommunityPendingFollows(alpha); + expect(pendingFollows0.items.length).toBe(0); + const pendingFollowsCount0 = await getCommunityPendingFollowsCount( + alpha, + alphaCommunityId, + ); + expect(pendingFollowsCount0.count).toBe(0); + + // follow as new user + const user = await registerUser(beta, betaUrl); + const betaCommunity = ( + await resolveCommunity(user, community.community_view.community.actor_id) + ).community; + expect(betaCommunity).toBeDefined(); + const betaCommunityId = betaCommunity!.community.id; + const follow_form: FollowCommunity = { + community_id: betaCommunityId, + follow: true, + }; + await user.followCommunity(follow_form); + + // Follow listed as pending + const follow1 = await getCommunity(user, betaCommunityId); + expect(follow1.community_view.subscribed).toBe("ApprovalRequired"); + + // Wait for follow to federate, shown as pending + let pendingFollows1 = await waitUntil( + () => listCommunityPendingFollows(alpha), + f => f.items.length == 1, + ); + expect(pendingFollows1.items[0].is_new_instance).toBe(true); + const pendingFollowsCount1 = await getCommunityPendingFollowsCount( + alpha, + alphaCommunityId, + ); + expect(pendingFollowsCount1.count).toBe(1); + + // user still sees approval required at this point + const betaCommunity2 = await getCommunity(user, betaCommunityId); + expect(betaCommunity2.community_view.subscribed).toBe("ApprovalRequired"); + + // Approve the follow + const approve = await approveCommunityPendingFollow( + alpha, + alphaCommunityId, + pendingFollows1.items[0].person.id, + ); + expect(approve.success).toBe(true); + + // Follow is confirmed + await waitUntil( + () => getCommunity(user, betaCommunityId), + c => c.community_view.subscribed == "Subscribed", + ); + const pendingFollows2 = await listCommunityPendingFollows(alpha); + expect(pendingFollows2.items.length).toBe(0); + const pendingFollowsCount2 = await getCommunityPendingFollowsCount( + alpha, + alphaCommunityId, + ); + expect(pendingFollowsCount2.count).toBe(0); + + // follow with another user from that instance, is_new_instance should be false now + const user2 = await registerUser(beta, betaUrl); + await user2.followCommunity(follow_form); + let pendingFollows3 = await waitUntil( + () => listCommunityPendingFollows(alpha), + f => f.items.length == 1, + ); + expect(pendingFollows3.items[0].is_new_instance).toBe(false); + + // cleanup pending follow + const approve2 = await approveCommunityPendingFollow( + alpha, + alphaCommunityId, + pendingFollows3.items[0].person.id, + ); + expect(approve2.success).toBe(true); +}); + +test("Only followers can view and interact with private community content", async () => { + // create private community + const community = await createCommunity(alpha, randomString(10), "Private"); + expect(community.community_view.community.visibility).toBe("Private"); + const alphaCommunityId = community.community_view.community.id; + + // create post and comment + const post0 = await createPost(alpha, alphaCommunityId); + const post_id = post0.post_view.post.id; + expect(post_id).toBeDefined(); + const comment = await createComment(alpha, post_id); + const comment_id = comment.comment_view.comment.id; + expect(comment_id).toBeDefined(); + + // user is not following the community and cannot view nor create posts + const user = await registerUser(beta, betaUrl); + const betaCommunity = ( + await resolveCommunity(user, community.community_view.community.actor_id) + ).community!.community; + await expect(resolvePost(user, post0.post_view.post)).rejects.toStrictEqual( + Error("not_found"), + ); + await expect( + resolveComment(user, comment.comment_view.comment), + ).rejects.toStrictEqual(Error("not_found")); + await expect(createPost(user, betaCommunity.id)).rejects.toStrictEqual( + Error("not_found"), + ); + + // follow the community and approve + const follow_form: FollowCommunity = { + community_id: betaCommunity.id, + follow: true, + }; + await user.followCommunity(follow_form); + const pendingFollows1 = await waitUntil( + () => listCommunityPendingFollows(alpha), + f => f.items.length == 1, + ); + const approve = await approveCommunityPendingFollow( + alpha, + alphaCommunityId, + pendingFollows1.items[0].person.id, + ); + expect(approve.success).toBe(true); + + // now user can fetch posts and comments in community (using signed fetch), and create posts + await waitUntil( + () => resolvePost(user, post0.post_view.post), + p => p?.post?.post.id != undefined, + ); + const resolvedComment = ( + await resolveComment(user, comment.comment_view.comment) + ).comment; + expect(resolvedComment?.comment.id).toBeDefined(); + + const post1 = await createPost(user, betaCommunity.id); + expect(post1.post_view).toBeDefined(); + const like = await likeComment(user, 1, resolvedComment!.comment); + expect(like.comment_view.my_vote).toBe(1); +}); + +test("Reject follower", async () => { + // create private community + const community = await createCommunity(alpha, randomString(10), "Private"); + expect(community.community_view.community.visibility).toBe("Private"); + const alphaCommunityId = community.community_view.community.id; + + // user is not following the community and cannot view nor create posts + const user = await registerUser(beta, betaUrl); + const betaCommunity1 = ( + await resolveCommunity(user, community.community_view.community.actor_id) + ).community!.community; + + // follow the community and reject + const follow_form: FollowCommunity = { + community_id: betaCommunity1.id, + follow: true, + }; + const follow = await user.followCommunity(follow_form); + expect(follow.community_view.subscribed).toBe("ApprovalRequired"); + + const pendingFollows1 = await waitUntil( + () => listCommunityPendingFollows(alpha), + f => f.items.length == 1, + ); + const approve = await approveCommunityPendingFollow( + alpha, + alphaCommunityId, + pendingFollows1.items[0].person.id, + false, + ); + expect(approve.success).toBe(true); + + await waitUntil( + () => getCommunity(user, betaCommunity1.id), + c => c.community_view.subscribed == "NotSubscribed", + ); +}); diff --git a/api_tests/src/shared.ts b/api_tests/src/shared.ts index 8ec4b29edf..95e916ef2e 100644 --- a/api_tests/src/shared.ts +++ b/api_tests/src/shared.ts @@ -1,17 +1,24 @@ import { + ApproveCommunityPendingFollower, BlockCommunity, BlockCommunityResponse, BlockInstance, BlockInstanceResponse, CommunityId, + CommunityVisibility, CreatePrivateMessageReport, DeleteImage, EditCommunity, + GetCommunityPendingFollowsCount, + GetCommunityPendingFollowsCountResponse, GetReplies, GetRepliesResponse, GetUnreadCountResponse, InstanceId, LemmyHttp, + ListCommunityPendingFollows, + ListCommunityPendingFollowsResponse, + PersonId, PostView, PrivateMessageReportResponse, SuccessResponse, @@ -83,7 +90,7 @@ export const fetchFunction = fetch; export const imageFetchLimit = 50; export const sampleImage = "https://i.pinimg.com/originals/df/5f/5b/df5f5b1b174a2b4b6026cc6c8f9395c1.jpg"; -export const sampleSite = "https://yahoo.com"; +export const sampleSite = "https://w3.org"; export const alphaUrl = "http://127.0.0.1:8541"; export const betaUrl = "http://127.0.0.1:8551"; @@ -198,7 +205,7 @@ export async function setupLogins() { // only needed the first time so do in this try await delay(10_000); } catch { - console.log("Communities already exist"); + //console.log("Communities already exist"); } } @@ -554,12 +561,14 @@ export async function likeComment( export async function createCommunity( api: LemmyHttp, name_: string = randomString(10), + visibility: CommunityVisibility = "Public", ): Promise { let description = "a sample description"; let form: CreateCommunity = { name: name_, title: name_, description, + visibility, }; return api.createCommunity(form); } @@ -688,7 +697,6 @@ export async function saveUserSettingsBio( let form: SaveUserSettings = { show_nsfw: true, blur_nsfw: false, - auto_expand: true, theme: "darkly", default_post_sort_type: "Active", default_listing_type: "All", @@ -709,7 +717,6 @@ export async function saveUserSettingsFederated( let form: SaveUserSettings = { show_nsfw: false, blur_nsfw: true, - auto_expand: false, default_post_sort_type: "Hot", default_listing_type: "All", interface_language: "", @@ -872,6 +879,39 @@ export function blockCommunity( return api.blockCommunity(form); } +export function listCommunityPendingFollows( + api: LemmyHttp, +): Promise { + let form: ListCommunityPendingFollows = { + pending_only: true, + all_communities: false, + page: 1, + limit: 50, + }; + return api.listCommunityPendingFollows(form); +} + +export function getCommunityPendingFollowsCount( + api: LemmyHttp, + community_id: CommunityId, +): Promise { + return api.getCommunityPendingFollowsCount(community_id); +} + +export function approveCommunityPendingFollow( + api: LemmyHttp, + community_id: CommunityId, + follower_id: PersonId, + approve: boolean = true, +): Promise { + let form: ApproveCommunityPendingFollower = { + community_id, + follower_id, + approve, + }; + return api.approveCommunityPendingFollow(form); +} + export function delay(millis = 500) { return new Promise(resolve => setTimeout(resolve, millis)); } @@ -962,8 +1002,12 @@ export async function waitUntil( let retry = 0; let result; while (retry++ < retries) { - result = await fetcher(); - if (checker(result)) return result; + try { + result = await fetcher(); + if (checker(result)) return result; + } catch (error) { + //console.error(error); + } await delay( delaySeconds[Math.min(retry - 1, delaySeconds.length - 1)] * 1000, ); diff --git a/config/defaults.hjson b/config/defaults.hjson index f0b9d56df5..96dc30b79f 100644 --- a/config/defaults.hjson +++ b/config/defaults.hjson @@ -76,7 +76,7 @@ # Timeout for uploading images to pictrs (in seconds) upload_timeout: 30 # Resize post thumbnails to this maximum width/height. - max_thumbnail_size: 256 + max_thumbnail_size: 512 } # Email sending configuration. All options except login/password are mandatory email: { diff --git a/crates/api/src/comment/distinguish.rs b/crates/api/src/comment/distinguish.rs index a1b25ea44c..17608a2301 100644 --- a/crates/api/src/comment/distinguish.rs +++ b/crates/api/src/comment/distinguish.rs @@ -26,7 +26,7 @@ pub async fn distinguish_comment( check_community_user_action( &local_user_view.person, - orig_comment.community.id, + &orig_comment.community, &mut context.pool(), ) .await?; @@ -39,7 +39,7 @@ pub async fn distinguish_comment( // Verify that only a mod or admin can distinguish a comment check_community_mod_action( &local_user_view.person, - orig_comment.community.id, + &orig_comment.community, false, &mut context.pool(), ) diff --git a/crates/api/src/comment/like.rs b/crates/api/src/comment/like.rs index e93b8513fe..fbc7201024 100644 --- a/crates/api/src/comment/like.rs +++ b/crates/api/src/comment/like.rs @@ -50,7 +50,7 @@ pub async fn like_comment( check_community_user_action( &local_user_view.person, - orig_comment.community.id, + &orig_comment.community, &mut context.pool(), ) .await?; @@ -92,8 +92,7 @@ pub async fn like_comment( score: data.score, }, &context, - ) - .await?; + )?; Ok(Json( build_comment_response( diff --git a/crates/api/src/comment_report/create.rs b/crates/api/src/comment_report/create.rs index a0ff4be775..48066cfe66 100644 --- a/crates/api/src/comment_report/create.rs +++ b/crates/api/src/comment_report/create.rs @@ -44,7 +44,7 @@ pub async fn create_comment_report( check_community_user_action( &local_user_view.person, - comment_view.community.id, + &comment_view.community, &mut context.pool(), ) .await?; @@ -85,8 +85,7 @@ pub async fn create_comment_report( reason: data.reason.clone(), }, &context, - ) - .await?; + )?; Ok(Json(CommentReportResponse { comment_report_view, diff --git a/crates/api/src/comment_report/resolve.rs b/crates/api/src/comment_report/resolve.rs index a663fdf747..58d5041dcf 100644 --- a/crates/api/src/comment_report/resolve.rs +++ b/crates/api/src/comment_report/resolve.rs @@ -22,7 +22,7 @@ pub async fn resolve_comment_report( let person_id = local_user_view.person.id; check_community_mod_action( &local_user_view.person, - report.community.id, + &report.community, true, &mut context.pool(), ) diff --git a/crates/api/src/community/add_mod.rs b/crates/api/src/community/add_mod.rs index 7d04f6bb00..9e85788ea8 100644 --- a/crates/api/src/community/add_mod.rs +++ b/crates/api/src/community/add_mod.rs @@ -24,12 +24,11 @@ pub async fn add_mod_to_community( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let community_id = data.community_id; - + let community = Community::read(&mut context.pool(), data.community_id).await?; // Verify that only mods or admins can add mod check_community_mod_action( &local_user_view.person, - community_id, + &community, false, &mut context.pool(), ) @@ -39,15 +38,13 @@ pub async fn add_mod_to_community( if !data.added { LocalUser::is_higher_mod_or_admin_check( &mut context.pool(), - community_id, + community.id, local_user_view.person.id, vec![data.person_id], ) .await?; } - let community = Community::read(&mut context.pool(), community_id).await?; - // If user is admin and community is remote, explicitly check that he is a // moderator. This is necessary because otherwise the action would be rejected // by the community's home instance. @@ -98,8 +95,7 @@ pub async fn add_mod_to_community( added: data.added, }, &context, - ) - .await?; + )?; Ok(Json(AddModToCommunityResponse { moderators })) } diff --git a/crates/api/src/community/ban.rs b/crates/api/src/community/ban.rs index 64b1c71967..a0e57061bd 100644 --- a/crates/api/src/community/ban.rs +++ b/crates/api/src/community/ban.rs @@ -13,6 +13,7 @@ use lemmy_api_common::{ use lemmy_db_schema::{ source::{ community::{ + Community, CommunityFollower, CommunityFollowerForm, CommunityPersonBan, @@ -38,11 +39,12 @@ pub async fn ban_from_community( ) -> LemmyResult> { let banned_person_id = data.person_id; let expires = check_expire_time(data.expires)?; + let community = Community::read(&mut context.pool(), data.community_id).await?; // Verify that only mods or admins can ban check_community_mod_action( &local_user_view.person, - data.community_id, + &community, false, &mut context.pool(), ) @@ -72,12 +74,7 @@ pub async fn ban_from_community( .with_lemmy_type(LemmyErrorType::CommunityUserAlreadyBanned)?; // Also unsubscribe them from the community, if they are subscribed - let community_follower_form = CommunityFollowerForm { - community_id: data.community_id, - person_id: banned_person_id, - pending: false, - }; - + let community_follower_form = CommunityFollowerForm::new(data.community_id, banned_person_id); CommunityFollower::unfollow(&mut context.pool(), &community_follower_form) .await .ok(); @@ -123,8 +120,7 @@ pub async fn ban_from_community( data: data.0.clone(), }, &context, - ) - .await?; + )?; Ok(Json(BanFromCommunityResponse { person_view, diff --git a/crates/api/src/community/block.rs b/crates/api/src/community/block.rs index 90931c7623..a6a48e2e75 100644 --- a/crates/api/src/community/block.rs +++ b/crates/api/src/community/block.rs @@ -35,12 +35,7 @@ pub async fn block_community( .with_lemmy_type(LemmyErrorType::CommunityBlockAlreadyExists)?; // Also, unfollow the community, and send a federated unfollow - let community_follower_form = CommunityFollowerForm { - community_id: data.community_id, - person_id, - pending: false, - }; - + let community_follower_form = CommunityFollowerForm::new(data.community_id, person_id); CommunityFollower::unfollow(&mut context.pool(), &community_follower_form) .await .ok(); @@ -65,8 +60,7 @@ pub async fn block_community( false, ), &context, - ) - .await?; + )?; Ok(Json(BlockCommunityResponse { blocked: data.block, diff --git a/crates/api/src/community/follow.rs b/crates/api/src/community/follow.rs index d0f5bbf0d2..d5cd3e5b19 100644 --- a/crates/api/src/community/follow.rs +++ b/crates/api/src/community/follow.rs @@ -4,17 +4,18 @@ use lemmy_api_common::{ community::{CommunityResponse, FollowCommunity}, context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, - utils::check_community_user_action, + utils::{check_community_deleted_removed, check_user_valid}, }; use lemmy_db_schema::{ source::{ actor_language::CommunityLanguage, - community::{Community, CommunityFollower, CommunityFollowerForm}, + community::{Community, CommunityFollower, CommunityFollowerForm, CommunityFollowerState}, }, traits::{Crud, Followable}, + CommunityVisibility, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_db_views_actor::structs::CommunityView; +use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView}; use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; #[tracing::instrument(skip(context))] @@ -23,40 +24,52 @@ pub async fn follow_community( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { + check_user_valid(&local_user_view.person)?; let community = Community::read(&mut context.pool(), data.community_id).await?; - let mut community_follower_form = CommunityFollowerForm { - community_id: community.id, - person_id: local_user_view.person.id, - pending: false, - }; + let form = CommunityFollowerForm::new(community.id, local_user_view.person.id); if data.follow { + // Only run these checks for local community, in case of remote community the local + // state may be outdated. Can't use check_community_user_action() here as it only allows + // actions from existing followers for private community (so following would be impossible). if community.local { - check_community_user_action(&local_user_view.person, community.id, &mut context.pool()) + check_community_deleted_removed(&community)?; + CommunityPersonBanView::check(&mut context.pool(), local_user_view.person.id, community.id) .await?; + } - CommunityFollower::follow(&mut context.pool(), &community_follower_form) - .await - .with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?; + let state = if community.local { + // Local follow is accepted immediately + Some(CommunityFollowerState::Accepted) + } else if community.visibility == CommunityVisibility::Private { + // Private communities require manual approval + Some(CommunityFollowerState::ApprovalRequired) } else { - // Mark as pending, the actual federation activity is sent via `SendActivity` handler - community_follower_form.pending = true; - CommunityFollower::follow(&mut context.pool(), &community_follower_form) - .await - .with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?; - } + // remote follow needs to be federated first + Some(CommunityFollowerState::Pending) + }; + + let form = CommunityFollowerForm { + state, + ..CommunityFollowerForm::new(community.id, local_user_view.person.id) + }; + + // Write to db + CommunityFollower::follow(&mut context.pool(), &form) + .await + .with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?; } else { - CommunityFollower::unfollow(&mut context.pool(), &community_follower_form) + CommunityFollower::unfollow(&mut context.pool(), &form) .await .with_lemmy_type(LemmyErrorType::CommunityFollowerAlreadyExists)?; } + // Send the federated follow if !community.local { ActivityChannel::submit_activity( SendActivityData::FollowCommunity(community, local_user_view.person.clone(), data.follow), &context, - ) - .await?; + )?; } let community_id = data.community_id; diff --git a/crates/api/src/community/hide.rs b/crates/api/src/community/hide.rs index 997d88de3e..077ed1c5ef 100644 --- a/crates/api/src/community/hide.rs +++ b/crates/api/src/community/hide.rs @@ -48,8 +48,7 @@ pub async fn hide_community( ActivityChannel::submit_activity( SendActivityData::UpdateCommunity(local_user_view.person.clone(), community), &context, - ) - .await?; + )?; Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/community/mod.rs b/crates/api/src/community/mod.rs index 54bdbef28d..121e181c69 100644 --- a/crates/api/src/community/mod.rs +++ b/crates/api/src/community/mod.rs @@ -3,5 +3,6 @@ pub mod ban; pub mod block; pub mod follow; pub mod hide; +pub mod pending_follows; pub mod random; pub mod transfer; diff --git a/crates/api/src/community/pending_follows/approve.rs b/crates/api/src/community/pending_follows/approve.rs new file mode 100644 index 0000000000..468e9d9d03 --- /dev/null +++ b/crates/api/src/community/pending_follows/approve.rs @@ -0,0 +1,46 @@ +use activitypub_federation::config::Data; +use actix_web::web::Json; +use lemmy_api_common::{ + community::ApproveCommunityPendingFollower, + context::LemmyContext, + send_activity::{ActivityChannel, SendActivityData}, + utils::is_mod_or_admin, + SuccessResponse, +}; +use lemmy_db_schema::{ + source::community::{CommunityFollower, CommunityFollowerForm}, + traits::Followable, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyResult; + +pub async fn post_pending_follows_approve( + data: Json, + context: Data, + local_user_view: LocalUserView, +) -> LemmyResult> { + is_mod_or_admin( + &mut context.pool(), + &local_user_view.person, + data.community_id, + ) + .await?; + + let activity_data = if data.approve { + CommunityFollower::approve( + &mut context.pool(), + data.community_id, + data.follower_id, + local_user_view.person.id, + ) + .await?; + SendActivityData::AcceptFollower(data.community_id, data.follower_id) + } else { + let form = CommunityFollowerForm::new(data.community_id, data.follower_id); + CommunityFollower::unfollow(&mut context.pool(), &form).await?; + SendActivityData::RejectFollower(data.community_id, data.follower_id) + }; + ActivityChannel::submit_activity(activity_data, &context)?; + + Ok(Json(SuccessResponse::default())) +} diff --git a/crates/api/src/community/pending_follows/count.rs b/crates/api/src/community/pending_follows/count.rs new file mode 100644 index 0000000000..e8e333c842 --- /dev/null +++ b/crates/api/src/community/pending_follows/count.rs @@ -0,0 +1,25 @@ +use actix_web::web::{Data, Json, Query}; +use lemmy_api_common::{ + community::{GetCommunityPendingFollowsCount, GetCommunityPendingFollowsCountResponse}, + context::LemmyContext, + utils::is_mod_or_admin, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views_actor::structs::CommunityFollowerView; +use lemmy_utils::error::LemmyResult; + +pub async fn get_pending_follows_count( + data: Query, + context: Data, + local_user_view: LocalUserView, +) -> LemmyResult> { + is_mod_or_admin( + &mut context.pool(), + &local_user_view.person, + data.community_id, + ) + .await?; + let count = + CommunityFollowerView::count_approval_required(&mut context.pool(), data.community_id).await?; + Ok(Json(GetCommunityPendingFollowsCountResponse { count })) +} diff --git a/crates/api/src/community/pending_follows/list.rs b/crates/api/src/community/pending_follows/list.rs new file mode 100644 index 0000000000..9f300a74fb --- /dev/null +++ b/crates/api/src/community/pending_follows/list.rs @@ -0,0 +1,29 @@ +use actix_web::web::{Data, Json, Query}; +use lemmy_api_common::{ + community::{ListCommunityPendingFollows, ListCommunityPendingFollowsResponse}, + context::LemmyContext, + utils::check_community_mod_of_any_or_admin_action, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views_actor::structs::CommunityFollowerView; +use lemmy_utils::error::LemmyResult; + +pub async fn get_pending_follows_list( + data: Query, + context: Data, + local_user_view: LocalUserView, +) -> LemmyResult> { + check_community_mod_of_any_or_admin_action(&local_user_view, &mut context.pool()).await?; + let all_communities = + data.all_communities.unwrap_or_default() && local_user_view.local_user.admin; + let items = CommunityFollowerView::list_approval_required( + &mut context.pool(), + local_user_view.person.id, + all_communities, + data.pending_only.unwrap_or_default(), + data.page, + data.limit, + ) + .await?; + Ok(Json(ListCommunityPendingFollowsResponse { items })) +} diff --git a/crates/api/src/community/pending_follows/mod.rs b/crates/api/src/community/pending_follows/mod.rs new file mode 100644 index 0000000000..dcc82e250e --- /dev/null +++ b/crates/api/src/community/pending_follows/mod.rs @@ -0,0 +1,3 @@ +pub mod approve; +pub mod count; +pub mod list; diff --git a/crates/api/src/community/transfer.rs b/crates/api/src/community/transfer.rs index 195adbd8d0..a5255e5e1b 100644 --- a/crates/api/src/community/transfer.rs +++ b/crates/api/src/community/transfer.rs @@ -7,7 +7,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ source::{ - community::{CommunityModerator, CommunityModeratorForm}, + community::{Community, CommunityModerator, CommunityModeratorForm}, moderator::{ModTransferCommunity, ModTransferCommunityForm}, }, traits::{Crud, Joinable}, @@ -27,11 +27,11 @@ pub async fn transfer_community( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let community_id = data.community_id; + let community = Community::read(&mut context.pool(), data.community_id).await?; let mut community_mods = - CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; + CommunityModeratorView::for_community(&mut context.pool(), community.id).await?; - check_community_user_action(&local_user_view.person, community_id, &mut context.pool()).await?; + check_community_user_action(&local_user_view.person, &community, &mut context.pool()).await?; // Make sure transferrer is either the top community mod, or an admin if !(is_top_mod(&local_user_view, &community_mods).is_ok() || is_admin(&local_user_view).is_ok()) diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 6ffa52f772..3ab2ba277c 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -197,11 +197,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities( .ok(); // Also unsubscribe them from the community, if they are subscribed - let community_follower_form = CommunityFollowerForm { - community_id, - person_id: target.id, - pending: false, - }; + let community_follower_form = CommunityFollowerForm::new(community_id, target.id); CommunityFollower::unfollow(&mut context.pool(), &community_follower_form) .await @@ -242,8 +238,7 @@ pub(crate) async fn ban_nonlocal_user_from_local_communities( data: ban_from_community, }, context, - ) - .await?; + )?; } } diff --git a/crates/api/src/local_user/ban_person.rs b/crates/api/src/local_user/ban_person.rs index 2ace7f0316..9349cc6321 100644 --- a/crates/api/src/local_user/ban_person.rs +++ b/crates/api/src/local_user/ban_person.rs @@ -111,8 +111,7 @@ pub async fn ban_from_site( expires: data.expires, }, &context, - ) - .await?; + )?; Ok(Json(BanPersonResponse { person_view, diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index 08820caddb..ac2e321a1c 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -141,6 +141,7 @@ pub async fn save_user_settings( post_listing_mode: data.post_listing_mode, enable_keyboard_navigation: data.enable_keyboard_navigation, enable_animated_images: data.enable_animated_images, + enable_private_messages: data.enable_private_messages, collapse_bot_comments: data.collapse_bot_comments, ..Default::default() }; diff --git a/crates/api/src/post/feature.rs b/crates/api/src/post/feature.rs index cb6e6c144e..6fc2f443cf 100644 --- a/crates/api/src/post/feature.rs +++ b/crates/api/src/post/feature.rs @@ -9,6 +9,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ source::{ + community::Community, moderator::{ModFeaturePost, ModFeaturePostForm}, post::{Post, PostUpdateForm}, }, @@ -27,9 +28,10 @@ pub async fn feature_post( let post_id = data.post_id; let orig_post = Post::read(&mut context.pool(), post_id).await?; + let community = Community::read(&mut context.pool(), orig_post.community_id).await?; check_community_mod_action( &local_user_view.person, - orig_post.community_id, + &community, false, &mut context.pool(), ) @@ -67,8 +69,7 @@ pub async fn feature_post( ActivityChannel::submit_activity( SendActivityData::FeaturePost(post, local_user_view.person.clone(), data.featured), &context, - ) - .await?; + )?; build_post_response(&context, orig_post.community_id, local_user_view, post_id).await } diff --git a/crates/api/src/post/get_link_metadata.rs b/crates/api/src/post/get_link_metadata.rs index e469b51c72..a777cab170 100644 --- a/crates/api/src/post/get_link_metadata.rs +++ b/crates/api/src/post/get_link_metadata.rs @@ -5,10 +5,7 @@ use lemmy_api_common::{ request::fetch_link_metadata, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::{ - error::{LemmyErrorExt, LemmyResult}, - LemmyErrorType, -}; +use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; use url::Url; #[tracing::instrument(skip(context))] diff --git a/crates/api/src/post/like.rs b/crates/api/src/post/like.rs index c81d9630a2..ec01e3e8c5 100644 --- a/crates/api/src/post/like.rs +++ b/crates/api/src/post/like.rs @@ -15,13 +15,12 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ source::{ - community::Community, local_site::LocalSite, - post::{Post, PostLike, PostLikeForm}, + post::{PostLike, PostLikeForm}, }, - traits::{Crud, Likeable}, + traits::Likeable, }; -use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views::structs::{LocalUserView, PostView}; use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult}; use std::ops::Deref; @@ -45,11 +44,11 @@ pub async fn like_post( check_bot_account(&local_user_view.person)?; // Check for a community ban - let post = Post::read(&mut context.pool(), post_id).await?; + let post = PostView::read(&mut context.pool(), post_id, None, false).await?; check_community_user_action( &local_user_view.person, - post.community_id, + &post.community, &mut context.pool(), ) .await?; @@ -75,18 +74,15 @@ pub async fn like_post( mark_post_as_read(person_id, post_id, &mut context.pool()).await?; - let community = Community::read(&mut context.pool(), post.community_id).await?; - ActivityChannel::submit_activity( SendActivityData::LikePostOrComment { - object_id: post.ap_id, + object_id: post.post.ap_id, actor: local_user_view.person.clone(), - community, + community: post.community.clone(), score: data.score, }, &context, - ) - .await?; + )?; - build_post_response(context.deref(), post.community_id, local_user_view, post_id).await + build_post_response(context.deref(), post.community.id, local_user_view, post_id).await } diff --git a/crates/api/src/post/lock.rs b/crates/api/src/post/lock.rs index 548947b787..011770c2e9 100644 --- a/crates/api/src/post/lock.rs +++ b/crates/api/src/post/lock.rs @@ -14,7 +14,7 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views::structs::{LocalUserView, PostView}; use lemmy_utils::error::LemmyResult; #[tracing::instrument(skip(context))] @@ -24,11 +24,11 @@ pub async fn lock_post( local_user_view: LocalUserView, ) -> LemmyResult> { let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id).await?; + let orig_post = PostView::read(&mut context.pool(), post_id, None, false).await?; check_community_mod_action( &local_user_view.person, - orig_post.community_id, + &orig_post.community, false, &mut context.pool(), ) @@ -58,8 +58,7 @@ pub async fn lock_post( ActivityChannel::submit_activity( SendActivityData::LockPost(post, local_user_view.person.clone(), data.locked), &context, - ) - .await?; + )?; - build_post_response(&context, orig_post.community_id, local_user_view, post_id).await + build_post_response(&context, orig_post.community.id, local_user_view, post_id).await } diff --git a/crates/api/src/post_report/create.rs b/crates/api/src/post_report/create.rs index 590c9af40d..b9edf35c57 100644 --- a/crates/api/src/post_report/create.rs +++ b/crates/api/src/post_report/create.rs @@ -39,7 +39,7 @@ pub async fn create_post_report( check_community_user_action( &local_user_view.person, - post_view.community.id, + &post_view.community, &mut context.pool(), ) .await?; @@ -80,8 +80,7 @@ pub async fn create_post_report( reason: data.reason.clone(), }, &context, - ) - .await?; + )?; Ok(Json(PostReportResponse { post_report_view })) } diff --git a/crates/api/src/post_report/resolve.rs b/crates/api/src/post_report/resolve.rs index a3cb85c6c8..6523275131 100644 --- a/crates/api/src/post_report/resolve.rs +++ b/crates/api/src/post_report/resolve.rs @@ -22,7 +22,7 @@ pub async fn resolve_post_report( let person_id = local_user_view.person.id; check_community_mod_action( &local_user_view.person, - report.community.id, + &report.community, true, &mut context.pool(), ) diff --git a/crates/api/src/site/purge/comment.rs b/crates/api/src/site/purge/comment.rs index b21ffbc800..ae79a835a4 100644 --- a/crates/api/src/site/purge/comment.rs +++ b/crates/api/src/site/purge/comment.rs @@ -67,8 +67,7 @@ pub async fn purge_comment( reason: data.reason.clone(), }, &context, - ) - .await?; + )?; Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/site/purge/community.rs b/crates/api/src/site/purge/community.rs index bf06bd529c..f0252e303e 100644 --- a/crates/api/src/site/purge/community.rs +++ b/crates/api/src/site/purge/community.rs @@ -75,8 +75,7 @@ pub async fn purge_community( removed: true, }, &context, - ) - .await?; + )?; Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/site/purge/person.rs b/crates/api/src/site/purge/person.rs index 7ab573cbce..6dad4ce654 100644 --- a/crates/api/src/site/purge/person.rs +++ b/crates/api/src/site/purge/person.rs @@ -80,8 +80,7 @@ pub async fn purge_person( expires: None, }, &context, - ) - .await?; + )?; Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/site/purge/post.rs b/crates/api/src/site/purge/post.rs index d2cacdae19..f808269e7c 100644 --- a/crates/api/src/site/purge/post.rs +++ b/crates/api/src/site/purge/post.rs @@ -66,8 +66,7 @@ pub async fn purge_post( removed: true, }, &context, - ) - .await?; + )?; Ok(Json(SuccessResponse::default())) } diff --git a/crates/api/src/site/registration_applications/tests.rs b/crates/api/src/site/registration_applications/tests.rs index 022cbf236a..bdfb1535ec 100644 --- a/crates/api/src/site/registration_applications/tests.rs +++ b/crates/api/src/site/registration_applications/tests.rs @@ -31,7 +31,10 @@ use lemmy_db_schema::{ RegistrationMode, }; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::{error::LemmyResult, LemmyErrorType, CACHE_DURATION_API}; +use lemmy_utils::{ + error::{LemmyErrorType, LemmyResult}, + CACHE_DURATION_API, +}; use serial_test::serial; async fn create_test_site(context: &Data) -> LemmyResult<(Instance, LocalUserView)> { diff --git a/crates/api/src/sitemap.rs b/crates/api/src/sitemap.rs index c3c3c417cf..4d3799b1b3 100644 --- a/crates/api/src/sitemap.rs +++ b/crates/api/src/sitemap.rs @@ -9,14 +9,12 @@ use lemmy_utils::error::LemmyResult; use sitemap_rs::{url::Url, url_set::UrlSet}; use tracing::info; -async fn generate_urlset( - posts: Vec<(DbUrl, chrono::DateTime)>, -) -> LemmyResult { +fn generate_urlset(posts: Vec<(DbUrl, chrono::DateTime)>) -> LemmyResult { let urls = posts .into_iter() - .map_while(|post| { - Url::builder(post.0.to_string()) - .last_modified(post.1.into()) + .map_while(|(url, date_time)| { + Url::builder(url.to_string()) + .last_modified(date_time.into()) .build() .ok() }) @@ -31,7 +29,7 @@ pub async fn get_sitemap(context: Data) -> LemmyResult::new(); - generate_urlset(posts).await?.write(&mut buf)?; + generate_urlset(posts)?.write(&mut buf)?; Ok( HttpResponse::Ok() @@ -74,7 +72,7 @@ pub(crate) mod tests { ]; let mut buf = Vec::::new(); - generate_urlset(posts).await?.write(&mut buf)?; + generate_urlset(posts)?.write(&mut buf)?; let root = Element::from_reader(buf.as_slice())?; assert_eq!(root.tag().name(), "urlset"); diff --git a/crates/api_common/src/claims.rs b/crates/api_common/src/claims.rs index 6476f855af..759673f4bd 100644 --- a/crates/api_common/src/claims.rs +++ b/crates/api_common/src/claims.rs @@ -92,7 +92,7 @@ mod tests { #[tokio::test] #[serial] async fn test_should_not_validate_user_token_after_password_change() -> LemmyResult<()> { - let pool_ = build_db_pool_for_tests().await; + let pool_ = build_db_pool_for_tests(); let pool = &mut (&pool_).into(); let secret = Secret::init(pool).await?; let context = LemmyContext::create( diff --git a/crates/api_common/src/comment.rs b/crates/api_common/src/comment.rs index 48800cf8d1..e083657893 100644 --- a/crates/api_common/src/comment.rs +++ b/crates/api_common/src/comment.rs @@ -17,7 +17,9 @@ use ts_rs::TS; pub struct CreateComment { pub content: String, pub post_id: PostId, + #[cfg_attr(feature = "full", ts(optional))] pub parent_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub language_id: Option, } @@ -37,7 +39,9 @@ pub struct GetComment { /// Edit a comment. pub struct EditComment { pub comment_id: CommentId, + #[cfg_attr(feature = "full", ts(optional))] pub content: Option, + #[cfg_attr(feature = "full", ts(optional))] pub language_id: Option, } @@ -69,6 +73,7 @@ pub struct DeleteComment { pub struct RemoveComment { pub comment_id: CommentId, pub removed: bool, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, } @@ -107,17 +112,29 @@ pub struct CreateCommentLike { #[cfg_attr(feature = "full", ts(export))] /// Get a list of comments. pub struct GetComments { + #[cfg_attr(feature = "full", ts(optional))] pub type_: Option, + #[cfg_attr(feature = "full", ts(optional))] pub sort: Option, + #[cfg_attr(feature = "full", ts(optional))] pub max_depth: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, + #[cfg_attr(feature = "full", ts(optional))] pub community_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub community_name: Option, + #[cfg_attr(feature = "full", ts(optional))] pub post_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub parent_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub saved_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub liked_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub disliked_only: Option, } @@ -161,12 +178,17 @@ pub struct ResolveCommentReport { #[cfg_attr(feature = "full", ts(export))] /// List comment reports. pub struct ListCommentReports { + #[cfg_attr(feature = "full", ts(optional))] pub comment_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, /// Only shows the unresolved reports + #[cfg_attr(feature = "full", ts(optional))] pub unresolved_only: Option, /// if no community is given, it returns reports for all communities moderated by the auth user + #[cfg_attr(feature = "full", ts(optional))] pub community_id: Option, } @@ -185,7 +207,9 @@ pub struct ListCommentReportsResponse { /// List comment likes. Admins-only. pub struct ListCommentLikes { pub comment_id: CommentId, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, } diff --git a/crates/api_common/src/community.rs b/crates/api_common/src/community.rs index 1def2111b6..898767b345 100644 --- a/crates/api_common/src/community.rs +++ b/crates/api_common/src/community.rs @@ -8,6 +8,7 @@ use lemmy_db_views_actor::structs::{ CommunityModeratorView, CommunitySortType, CommunityView, + PendingFollow, PersonView, }; use serde::{Deserialize, Serialize}; @@ -19,10 +20,13 @@ use ts_rs::TS; #[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] +// TODO make this into a tagged enum /// Get a community. Must provide either an id, or a name. pub struct GetCommunity { + #[cfg_attr(feature = "full", ts(optional))] pub id: Option, /// Example: star_trek , or star_trek@xyz.tld + #[cfg_attr(feature = "full", ts(optional))] pub name: Option, } @@ -33,6 +37,7 @@ pub struct GetCommunity { /// The community response. pub struct GetCommunityResponse { pub community_view: CommunityView, + #[cfg_attr(feature = "full", ts(optional))] pub site: Option, pub moderators: Vec, pub discussion_languages: Vec, @@ -49,18 +54,26 @@ pub struct CreateCommunity { /// A longer title. pub title: String, /// A sidebar for the community in markdown. + #[cfg_attr(feature = "full", ts(optional))] pub sidebar: Option, /// A shorter, one line description of your community. + #[cfg_attr(feature = "full", ts(optional))] pub description: Option, /// An icon URL. + #[cfg_attr(feature = "full", ts(optional))] pub icon: Option, /// A banner URL. + #[cfg_attr(feature = "full", ts(optional))] pub banner: Option, /// Whether its an NSFW community. + #[cfg_attr(feature = "full", ts(optional))] pub nsfw: Option, /// Whether to restrict posting only to moderators. + #[cfg_attr(feature = "full", ts(optional))] pub posting_restricted_to_mods: Option, + #[cfg_attr(feature = "full", ts(optional))] pub discussion_languages: Option>, + #[cfg_attr(feature = "full", ts(optional))] pub visibility: Option, } @@ -79,10 +92,15 @@ pub struct CommunityResponse { #[cfg_attr(feature = "full", ts(export))] /// Fetches a list of communities. pub struct ListCommunities { + #[cfg_attr(feature = "full", ts(optional))] pub type_: Option, + #[cfg_attr(feature = "full", ts(optional))] pub sort: Option, + #[cfg_attr(feature = "full", ts(optional))] pub show_nsfw: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, } @@ -105,11 +123,14 @@ pub struct BanFromCommunity { pub ban: bool, /// Optionally remove or restore all their data. Useful for new troll accounts. /// If ban is true, then this means remove. If ban is false, it means restore. + #[cfg_attr(feature = "full", ts(optional))] pub remove_or_restore_data: Option, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, /// A time that the ban will expire, in unix epoch seconds. /// /// An i64 unix timestamp is used for a simpler API client implementation. + #[cfg_attr(feature = "full", ts(optional))] pub expires: Option, } @@ -148,20 +169,29 @@ pub struct AddModToCommunityResponse { pub struct EditCommunity { pub community_id: CommunityId, /// A longer title. + #[cfg_attr(feature = "full", ts(optional))] pub title: Option, /// A sidebar for the community in markdown. + #[cfg_attr(feature = "full", ts(optional))] pub sidebar: Option, /// A shorter, one line description of your community. + #[cfg_attr(feature = "full", ts(optional))] pub description: Option, /// An icon URL. + #[cfg_attr(feature = "full", ts(optional))] pub icon: Option, /// A banner URL. + #[cfg_attr(feature = "full", ts(optional))] pub banner: Option, /// Whether its an NSFW community. + #[cfg_attr(feature = "full", ts(optional))] pub nsfw: Option, /// Whether to restrict posting only to moderators. + #[cfg_attr(feature = "full", ts(optional))] pub posting_restricted_to_mods: Option, + #[cfg_attr(feature = "full", ts(optional))] pub discussion_languages: Option>, + #[cfg_attr(feature = "full", ts(optional))] pub visibility: Option, } @@ -173,6 +203,7 @@ pub struct EditCommunity { pub struct HideCommunity { pub community_id: CommunityId, pub hidden: bool, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, } @@ -194,6 +225,7 @@ pub struct DeleteCommunity { pub struct RemoveCommunity { pub community_id: CommunityId, pub removed: bool, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, } @@ -240,5 +272,53 @@ pub struct TransferCommunity { #[cfg_attr(feature = "full", ts(export))] /// Fetches a random community pub struct GetRandomCommunity { + #[cfg_attr(feature = "full", ts(optional))] pub type_: Option, } + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +pub struct ListCommunityPendingFollows { + /// Only shows the unapproved applications + #[cfg_attr(feature = "full", ts(optional))] + pub pending_only: Option, + // Only for admins, show pending follows for communities which you dont moderate + #[cfg_attr(feature = "full", ts(optional))] + pub all_communities: Option, + #[cfg_attr(feature = "full", ts(optional))] + pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] + pub limit: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +pub struct GetCommunityPendingFollowsCount { + pub community_id: CommunityId, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +pub struct GetCommunityPendingFollowsCountResponse { + pub count: i64, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +pub struct ListCommunityPendingFollowsResponse { + pub items: Vec, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +pub struct ApproveCommunityPendingFollower { + pub community_id: CommunityId, + pub follower_id: PersonId, + pub approve: bool, +} diff --git a/crates/api_common/src/context.rs b/crates/api_common/src/context.rs index 334983b209..c6ab23bfcb 100644 --- a/crates/api_common/src/context.rs +++ b/crates/api_common/src/context.rs @@ -57,7 +57,7 @@ impl LemmyContext { /// Do not use this in production code. pub async fn init_test_federation_config() -> FederationConfig { // call this to run migrations - let pool = build_db_pool_for_tests().await; + let pool = build_db_pool_for_tests(); let client = client_builder(&SETTINGS).build().expect("build client"); diff --git a/crates/api_common/src/custom_emoji.rs b/crates/api_common/src/custom_emoji.rs index 3804b71af2..76bc9c9e2b 100644 --- a/crates/api_common/src/custom_emoji.rs +++ b/crates/api_common/src/custom_emoji.rs @@ -62,8 +62,12 @@ pub struct ListCustomEmojisResponse { #[cfg_attr(feature = "full", ts(export))] /// Fetches a list of custom emojis. pub struct ListCustomEmojis { + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, + #[cfg_attr(feature = "full", ts(optional))] pub category: Option, + #[cfg_attr(feature = "full", ts(optional))] pub ignore_page_limits: Option, } diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index 68eeadecc5..6e09d904d6 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -26,7 +26,7 @@ pub extern crate lemmy_db_views_actor; pub extern crate lemmy_db_views_moderator; pub extern crate lemmy_utils; -pub use lemmy_utils::LemmyErrorType; +pub use lemmy_utils::error::LemmyErrorType; use serde::{Deserialize, Serialize}; use std::{cmp::min, time::Duration}; diff --git a/crates/api_common/src/oauth_provider.rs b/crates/api_common/src/oauth_provider.rs index 14847edf1e..36fef3b186 100644 --- a/crates/api_common/src/oauth_provider.rs +++ b/crates/api_common/src/oauth_provider.rs @@ -20,8 +20,11 @@ pub struct CreateOAuthProvider { pub client_id: String, pub client_secret: String, pub scopes: String, + #[cfg_attr(feature = "full", ts(optional))] pub auto_verify_email: Option, + #[cfg_attr(feature = "full", ts(optional))] pub account_linking_enabled: Option, + #[cfg_attr(feature = "full", ts(optional))] pub enabled: Option, } @@ -32,15 +35,25 @@ pub struct CreateOAuthProvider { /// Edit an external auth method. pub struct EditOAuthProvider { pub id: OAuthProviderId, + #[cfg_attr(feature = "full", ts(optional))] pub display_name: Option, + #[cfg_attr(feature = "full", ts(optional))] pub authorization_endpoint: Option, + #[cfg_attr(feature = "full", ts(optional))] pub token_endpoint: Option, + #[cfg_attr(feature = "full", ts(optional))] pub userinfo_endpoint: Option, + #[cfg_attr(feature = "full", ts(optional))] pub id_claim: Option, + #[cfg_attr(feature = "full", ts(optional))] pub client_secret: Option, + #[cfg_attr(feature = "full", ts(optional))] pub scopes: Option, + #[cfg_attr(feature = "full", ts(optional))] pub auto_verify_email: Option, + #[cfg_attr(feature = "full", ts(optional))] pub account_linking_enabled: Option, + #[cfg_attr(feature = "full", ts(optional))] pub enabled: Option, } @@ -59,13 +72,14 @@ pub struct DeleteOAuthProvider { /// Logging in with an OAuth 2.0 authorization pub struct AuthenticateWithOauth { pub code: String, - #[cfg_attr(feature = "full", ts(type = "string"))] pub oauth_provider_id: OAuthProviderId, - #[cfg_attr(feature = "full", ts(type = "string"))] pub redirect_uri: Url, + #[cfg_attr(feature = "full", ts(optional))] pub show_nsfw: Option, /// Username is mandatory at registration time + #[cfg_attr(feature = "full", ts(optional))] pub username: Option, /// An answer is mandatory if require application is enabled on the server + #[cfg_attr(feature = "full", ts(optional))] pub answer: Option, } diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index 6f1ddfe437..742dc88db7 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -28,6 +28,7 @@ pub struct Login { pub username_or_email: SensitiveString, pub password: SensitiveString, /// May be required, if totp is enabled for their account. + #[cfg_attr(feature = "full", ts(optional))] pub totp_2fa_token: Option, } @@ -40,16 +41,22 @@ pub struct Register { pub username: String, pub password: SensitiveString, pub password_verify: SensitiveString, + #[cfg_attr(feature = "full", ts(optional))] pub show_nsfw: Option, /// email is mandatory if email verification is enabled on the server + #[cfg_attr(feature = "full", ts(optional))] pub email: Option, /// The UUID of the captcha item. + #[cfg_attr(feature = "full", ts(optional))] pub captcha_uuid: Option, /// Your captcha answer. + #[cfg_attr(feature = "full", ts(optional))] pub captcha_answer: Option, /// A form field to trick signup bots. Should be None. + #[cfg_attr(feature = "full", ts(optional))] pub honeypot: Option, /// An answer is mandatory if require application is enabled on the server + #[cfg_attr(feature = "full", ts(optional))] pub answer: Option, } @@ -60,6 +67,7 @@ pub struct Register { /// A wrapper for the captcha response. pub struct GetCaptchaResponse { /// Will be None if captchas are disabled. + #[cfg_attr(feature = "full", ts(optional))] pub ok: Option, } @@ -83,60 +91,92 @@ pub struct CaptchaResponse { /// Saves settings for your user. pub struct SaveUserSettings { /// Show nsfw posts. + #[cfg_attr(feature = "full", ts(optional))] pub show_nsfw: Option, /// Blur nsfw posts. + #[cfg_attr(feature = "full", ts(optional))] pub blur_nsfw: Option, /// Your user's theme. + #[cfg_attr(feature = "full", ts(optional))] pub theme: Option, /// The default post listing type, usually "local" + #[cfg_attr(feature = "full", ts(optional))] pub default_listing_type: Option, /// A post-view mode that changes how multiple post listings look. + #[cfg_attr(feature = "full", ts(optional))] pub post_listing_mode: Option, /// The default post sort, usually "active" + #[cfg_attr(feature = "full", ts(optional))] pub default_post_sort_type: Option, /// The default comment sort, usually "hot" + #[cfg_attr(feature = "full", ts(optional))] pub default_comment_sort_type: Option, /// The language of the lemmy interface + #[cfg_attr(feature = "full", ts(optional))] pub interface_language: Option, /// A URL for your avatar. + #[cfg_attr(feature = "full", ts(optional))] pub avatar: Option, /// A URL for your banner. + #[cfg_attr(feature = "full", ts(optional))] pub banner: Option, /// Your display name, which can contain strange characters, and does not need to be unique. + #[cfg_attr(feature = "full", ts(optional))] pub display_name: Option, /// Your email. + #[cfg_attr(feature = "full", ts(optional))] pub email: Option, /// Your bio / info, in markdown. + #[cfg_attr(feature = "full", ts(optional))] pub bio: Option, /// Your matrix user id. Ex: @my_user:matrix.org + #[cfg_attr(feature = "full", ts(optional))] pub matrix_user_id: Option, /// Whether to show or hide avatars. + #[cfg_attr(feature = "full", ts(optional))] pub show_avatars: Option, /// Sends notifications to your email. + #[cfg_attr(feature = "full", ts(optional))] pub send_notifications_to_email: Option, /// Whether this account is a bot account. Users can hide these accounts easily if they wish. + #[cfg_attr(feature = "full", ts(optional))] pub bot_account: Option, /// Whether to show bot accounts. + #[cfg_attr(feature = "full", ts(optional))] pub show_bot_accounts: Option, /// Whether to show read posts. + #[cfg_attr(feature = "full", ts(optional))] pub show_read_posts: Option, /// A list of languages you are able to see discussion in. + #[cfg_attr(feature = "full", ts(optional))] pub discussion_languages: Option>, /// Open links in a new tab + #[cfg_attr(feature = "full", ts(optional))] pub open_links_in_new_tab: Option, /// Enable infinite scroll + #[cfg_attr(feature = "full", ts(optional))] pub infinite_scroll_enabled: Option, /// Whether to allow keyboard navigation (for browsing and interacting with posts and comments). + #[cfg_attr(feature = "full", ts(optional))] pub enable_keyboard_navigation: Option, /// Whether user avatars or inline images in the UI that are gifs should be allowed to play or /// should be paused + #[cfg_attr(feature = "full", ts(optional))] pub enable_animated_images: Option, + /// Whether a user can send / receive private messages + #[cfg_attr(feature = "full", ts(optional))] + pub enable_private_messages: Option, /// Whether to auto-collapse bot comments. + #[cfg_attr(feature = "full", ts(optional))] pub collapse_bot_comments: Option, /// Some vote display mode settings + #[cfg_attr(feature = "full", ts(optional))] pub show_scores: Option, + #[cfg_attr(feature = "full", ts(optional))] pub show_upvotes: Option, + #[cfg_attr(feature = "full", ts(optional))] pub show_downvotes: Option, + #[cfg_attr(feature = "full", ts(optional))] pub show_upvote_percentage: Option, } @@ -158,6 +198,7 @@ pub struct ChangePassword { pub struct LoginResponse { /// This is None in response to `Register` if email verification is enabled, or the server /// requires registration applications. + #[cfg_attr(feature = "full", ts(optional))] pub jwt: Option, /// If registration applications are required, this will return true for a signup response. pub registration_created: bool, @@ -173,13 +214,20 @@ pub struct LoginResponse { /// /// Either person_id, or username are required. pub struct GetPersonDetails { + #[cfg_attr(feature = "full", ts(optional))] pub person_id: Option, /// Example: dessalines , or dessalines@xyz.tld + #[cfg_attr(feature = "full", ts(optional))] pub username: Option, + #[cfg_attr(feature = "full", ts(optional))] pub sort: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, + #[cfg_attr(feature = "full", ts(optional))] pub community_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub saved_only: Option, } @@ -190,6 +238,7 @@ pub struct GetPersonDetails { /// A person's details response. pub struct GetPersonDetailsResponse { pub person_view: PersonView, + #[cfg_attr(feature = "full", ts(optional))] pub site: Option, pub comments: Vec, pub posts: Vec, @@ -223,11 +272,14 @@ pub struct BanPerson { pub ban: bool, /// Optionally remove or restore all their data. Useful for new troll accounts. /// If ban is true, then this means remove. If ban is false, it means restore. + #[cfg_attr(feature = "full", ts(optional))] pub remove_or_restore_data: Option, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, /// A time that the ban will expire, in unix epoch seconds. /// /// An i64 unix timestamp is used for a simpler API client implementation. + #[cfg_attr(feature = "full", ts(optional))] pub expires: Option, } @@ -273,9 +325,13 @@ pub struct BlockPersonResponse { #[cfg_attr(feature = "full", ts(export))] /// Get comment replies. pub struct GetReplies { + #[cfg_attr(feature = "full", ts(optional))] pub sort: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, + #[cfg_attr(feature = "full", ts(optional))] pub unread_only: Option, } @@ -294,9 +350,13 @@ pub struct GetRepliesResponse { #[cfg_attr(feature = "full", ts(export))] /// Get mentions for your user. pub struct GetPersonMentions { + #[cfg_attr(feature = "full", ts(optional))] pub sort: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, + #[cfg_attr(feature = "full", ts(optional))] pub unread_only: Option, } @@ -375,6 +435,7 @@ pub struct PasswordChangeAfterReset { #[cfg_attr(feature = "full", ts(export))] /// Get a count of the number of reports. pub struct GetReportCount { + #[cfg_attr(feature = "full", ts(optional))] pub community_id: Option, } @@ -384,9 +445,11 @@ pub struct GetReportCount { #[cfg_attr(feature = "full", ts(export))] /// A response for the number of reports. pub struct GetReportCountResponse { + #[cfg_attr(feature = "full", ts(optional))] pub community_id: Option, pub comment_reports: i64, pub post_reports: i64, + #[cfg_attr(feature = "full", ts(optional))] pub private_message_reports: Option, } @@ -436,7 +499,9 @@ pub struct UpdateTotpResponse { #[cfg_attr(feature = "full", ts(export))] /// Get your user's image / media uploads. pub struct ListMedia { + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, } diff --git a/crates/api_common/src/post.rs b/crates/api_common/src/post.rs index fa45459e2b..ca4f53e9d1 100644 --- a/crates/api_common/src/post.rs +++ b/crates/api_common/src/post.rs @@ -19,18 +19,26 @@ use ts_rs::TS; pub struct CreatePost { pub name: String, pub community_id: CommunityId, + #[cfg_attr(feature = "full", ts(optional))] pub url: Option, /// An optional body for the post in markdown. + #[cfg_attr(feature = "full", ts(optional))] pub body: Option, /// An optional alt_text, usable for image posts. + #[cfg_attr(feature = "full", ts(optional))] pub alt_text: Option, /// A honeypot to catch bots. Should be None. + #[cfg_attr(feature = "full", ts(optional))] pub honeypot: Option, + #[cfg_attr(feature = "full", ts(optional))] pub nsfw: Option, + #[cfg_attr(feature = "full", ts(optional))] pub language_id: Option, /// Instead of fetching a thumbnail, use a custom one. + #[cfg_attr(feature = "full", ts(optional))] pub custom_thumbnail: Option, /// Time when this post should be scheduled. Null means publish immediately. + #[cfg_attr(feature = "full", ts(optional))] pub scheduled_publish_time: Option, } @@ -45,9 +53,12 @@ pub struct PostResponse { #[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] +// TODO this should be made into a tagged enum /// Get a post. Needs either the post id, or comment_id. pub struct GetPost { + #[cfg_attr(feature = "full", ts(optional))] pub id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub comment_id: Option, } @@ -70,21 +81,37 @@ pub struct GetPostResponse { #[cfg_attr(feature = "full", ts(export))] /// Get a list of posts. pub struct GetPosts { + #[cfg_attr(feature = "full", ts(optional))] pub type_: Option, + #[cfg_attr(feature = "full", ts(optional))] pub sort: Option, /// DEPRECATED, use page_cursor + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, + #[cfg_attr(feature = "full", ts(optional))] pub community_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub community_name: Option, + #[cfg_attr(feature = "full", ts(optional))] pub saved_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub liked_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub disliked_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub show_hidden: Option, /// If true, then show the read posts (even if your user setting is to hide them) + #[cfg_attr(feature = "full", ts(optional))] pub show_read: Option, /// If true, then show the nsfw posts (even if your user setting is to hide them) + #[cfg_attr(feature = "full", ts(optional))] pub show_nsfw: Option, + #[cfg_attr(feature = "full", ts(optional))] + /// If true, then only show posts with no comments + pub no_comments_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page_cursor: Option, } @@ -96,6 +123,7 @@ pub struct GetPosts { pub struct GetPostsResponse { pub posts: Vec, /// the pagination cursor to use to fetch the next page + #[cfg_attr(feature = "full", ts(optional))] pub next_page: Option, } @@ -116,17 +144,25 @@ pub struct CreatePostLike { /// Edit a post. pub struct EditPost { pub post_id: PostId, + #[cfg_attr(feature = "full", ts(optional))] pub name: Option, + #[cfg_attr(feature = "full", ts(optional))] pub url: Option, /// An optional body for the post in markdown. + #[cfg_attr(feature = "full", ts(optional))] pub body: Option, /// An optional alt_text, usable for image posts. + #[cfg_attr(feature = "full", ts(optional))] pub alt_text: Option, + #[cfg_attr(feature = "full", ts(optional))] pub nsfw: Option, + #[cfg_attr(feature = "full", ts(optional))] pub language_id: Option, /// Instead of fetching a thumbnail, use a custom one. + #[cfg_attr(feature = "full", ts(optional))] pub custom_thumbnail: Option, /// Time when this post should be scheduled. Null means publish immediately. + #[cfg_attr(feature = "full", ts(optional))] pub scheduled_publish_time: Option, } @@ -147,6 +183,7 @@ pub struct DeletePost { pub struct RemovePost { pub post_id: PostId, pub removed: bool, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, } @@ -230,12 +267,18 @@ pub struct ResolvePostReport { #[cfg_attr(feature = "full", ts(export))] /// List post reports. pub struct ListPostReports { + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, /// Only shows the unresolved reports + #[cfg_attr(feature = "full", ts(optional))] pub unresolved_only: Option, + // TODO make into tagged enum at some point /// if no community is given, it returns reports for all communities moderated by the auth user + #[cfg_attr(feature = "full", ts(optional))] pub community_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub post_id: Option, } @@ -271,6 +314,7 @@ pub struct GetSiteMetadataResponse { pub struct LinkMetadata { #[serde(flatten)] pub opengraph_data: OpenGraphData, + #[cfg_attr(feature = "full", ts(optional))] pub content_type: Option, } @@ -280,9 +324,13 @@ pub struct LinkMetadata { #[cfg_attr(feature = "full", ts(export))] /// Site metadata, from its opengraph tags. pub struct OpenGraphData { + #[cfg_attr(feature = "full", ts(optional))] pub title: Option, + #[cfg_attr(feature = "full", ts(optional))] pub description: Option, + #[cfg_attr(feature = "full", ts(optional))] pub(crate) image: Option, + #[cfg_attr(feature = "full", ts(optional))] pub embed_video_url: Option, } @@ -293,7 +341,9 @@ pub struct OpenGraphData { /// List post likes. Admins-only. pub struct ListPostLikes { pub post_id: PostId, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, } diff --git a/crates/api_common/src/private_message.rs b/crates/api_common/src/private_message.rs index 429d686435..666fe38659 100644 --- a/crates/api_common/src/private_message.rs +++ b/crates/api_common/src/private_message.rs @@ -47,9 +47,13 @@ pub struct MarkPrivateMessageAsRead { #[cfg_attr(feature = "full", ts(export))] /// Get your private messages. pub struct GetPrivateMessages { + #[cfg_attr(feature = "full", ts(optional))] pub unread_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, + #[cfg_attr(feature = "full", ts(optional))] pub creator_id: Option, } @@ -102,9 +106,12 @@ pub struct ResolvePrivateMessageReport { /// List private message reports. // TODO , perhaps GetReports should be a tagged enum list too. pub struct ListPrivateMessageReports { + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, /// Only shows the unresolved reports + #[cfg_attr(feature = "full", ts(optional))] pub unresolved_only: Option, } diff --git a/crates/api_common/src/request.rs b/crates/api_common/src/request.rs index b0da6cf4d8..96d64d0e5d 100644 --- a/crates/api_common/src/request.rs +++ b/crates/api_common/src/request.rs @@ -175,7 +175,7 @@ pub async fn generate_post_link_metadata( }; let updated_post = Post::update(&mut context.pool(), post.id, &form).await?; if let Some(send_activity) = send_activity(updated_post) { - ActivityChannel::submit_activity(send_activity, &context).await?; + ActivityChannel::submit_activity(send_activity, &context)?; } Ok(()) } diff --git a/crates/api_common/src/send_activity.rs b/crates/api_common/src/send_activity.rs index 465e074f44..b606c9a901 100644 --- a/crates/api_common/src/send_activity.rs +++ b/crates/api_common/src/send_activity.rs @@ -59,6 +59,8 @@ pub enum SendActivityData { score: i16, }, FollowCommunity(Community, Person, bool), + AcceptFollower(CommunityId, PersonId), + RejectFollower(CommunityId, PersonId), UpdateCommunity(Person, Community), DeleteCommunity(Person, Community, bool), RemoveCommunity { @@ -123,10 +125,7 @@ impl ActivityChannel { lock.recv().await } - pub async fn submit_activity( - data: SendActivityData, - _context: &Data, - ) -> LemmyResult<()> { + pub fn submit_activity(data: SendActivityData, _context: &Data) -> LemmyResult<()> { // could do `ACTIVITY_CHANNEL.keepalive_sender.lock()` instead and get rid of weak_sender, // not sure which way is more efficient if let Some(sender) = ACTIVITY_CHANNEL.weak_sender.upgrade() { diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index 8fc091e9dc..40a5cc42d5 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -71,18 +71,31 @@ use ts_rs::TS; /// Searches the site, given a query string, and some optional filters. pub struct Search { pub q: String, + #[cfg_attr(feature = "full", ts(optional))] pub community_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub community_name: Option, + #[cfg_attr(feature = "full", ts(optional))] pub creator_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub type_: Option, + #[cfg_attr(feature = "full", ts(optional))] pub sort: Option, + #[cfg_attr(feature = "full", ts(optional))] pub listing_type: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, + #[cfg_attr(feature = "full", ts(optional))] pub title_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub post_url_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub saved_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub liked_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub disliked_only: Option, } @@ -115,9 +128,13 @@ pub struct ResolveObject { // TODO Change this to an enum /// The response of an apub object fetch. pub struct ResolveObjectResponse { + #[cfg_attr(feature = "full", ts(optional))] pub comment: Option, + #[cfg_attr(feature = "full", ts(optional))] pub post: Option, + #[cfg_attr(feature = "full", ts(optional))] pub community: Option, + #[cfg_attr(feature = "full", ts(optional))] pub person: Option, } @@ -127,13 +144,21 @@ pub struct ResolveObjectResponse { #[cfg_attr(feature = "full", ts(export))] /// Fetches the modlog. pub struct GetModlog { + #[cfg_attr(feature = "full", ts(optional))] pub mod_person_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub community_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, + #[cfg_attr(feature = "full", ts(optional))] pub type_: Option, + #[cfg_attr(feature = "full", ts(optional))] pub other_person_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub post_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub comment_id: Option, } @@ -167,50 +192,95 @@ pub struct GetModlogResponse { /// Creates a site. Should be done after first running lemmy. pub struct CreateSite { pub name: String, + #[cfg_attr(feature = "full", ts(optional))] pub sidebar: Option, + #[cfg_attr(feature = "full", ts(optional))] pub description: Option, + #[cfg_attr(feature = "full", ts(optional))] pub icon: Option, + #[cfg_attr(feature = "full", ts(optional))] pub banner: Option, + #[cfg_attr(feature = "full", ts(optional))] pub enable_nsfw: Option, + #[cfg_attr(feature = "full", ts(optional))] pub community_creation_admin_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub require_email_verification: Option, + #[cfg_attr(feature = "full", ts(optional))] pub application_question: Option, + #[cfg_attr(feature = "full", ts(optional))] pub private_instance: Option, + #[cfg_attr(feature = "full", ts(optional))] pub default_theme: Option, + #[cfg_attr(feature = "full", ts(optional))] pub default_post_listing_type: Option, + #[cfg_attr(feature = "full", ts(optional))] pub default_post_listing_mode: Option, + #[cfg_attr(feature = "full", ts(optional))] pub default_post_sort_type: Option, + #[cfg_attr(feature = "full", ts(optional))] pub default_comment_sort_type: Option, + #[cfg_attr(feature = "full", ts(optional))] pub legal_information: Option, + #[cfg_attr(feature = "full", ts(optional))] pub application_email_admins: Option, + #[cfg_attr(feature = "full", ts(optional))] pub hide_modlog_mod_names: Option, + #[cfg_attr(feature = "full", ts(optional))] pub discussion_languages: Option>, + #[cfg_attr(feature = "full", ts(optional))] pub slur_filter_regex: Option, + #[cfg_attr(feature = "full", ts(optional))] pub actor_name_max_length: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_message: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_message_per_second: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_post: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_post_per_second: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_register: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_register_per_second: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_image: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_image_per_second: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_comment: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_comment_per_second: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_search: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_search_per_second: Option, + #[cfg_attr(feature = "full", ts(optional))] pub federation_enabled: Option, + #[cfg_attr(feature = "full", ts(optional))] pub federation_debug: Option, + #[cfg_attr(feature = "full", ts(optional))] pub captcha_enabled: Option, + #[cfg_attr(feature = "full", ts(optional))] pub captcha_difficulty: Option, + #[cfg_attr(feature = "full", ts(optional))] pub allowed_instances: Option>, + #[cfg_attr(feature = "full", ts(optional))] pub blocked_instances: Option>, + #[cfg_attr(feature = "full", ts(optional))] pub registration_mode: Option, + #[cfg_attr(feature = "full", ts(optional))] pub oauth_registration: Option, + #[cfg_attr(feature = "full", ts(optional))] pub content_warning: Option, + #[cfg_attr(feature = "full", ts(optional))] pub post_upvotes: Option, + #[cfg_attr(feature = "full", ts(optional))] pub post_downvotes: Option, + #[cfg_attr(feature = "full", ts(optional))] pub comment_upvotes: Option, + #[cfg_attr(feature = "full", ts(optional))] pub comment_downvotes: Option, } @@ -220,94 +290,142 @@ pub struct CreateSite { #[cfg_attr(feature = "full", ts(export))] /// Edits a site. pub struct EditSite { + #[cfg_attr(feature = "full", ts(optional))] pub name: Option, /// A sidebar for the site, in markdown. + #[cfg_attr(feature = "full", ts(optional))] pub sidebar: Option, /// A shorter, one line description of your site. + #[cfg_attr(feature = "full", ts(optional))] pub description: Option, /// A url for your site's icon. + #[cfg_attr(feature = "full", ts(optional))] pub icon: Option, /// A url for your site's banner. + #[cfg_attr(feature = "full", ts(optional))] pub banner: Option, /// Whether to enable NSFW. + #[cfg_attr(feature = "full", ts(optional))] pub enable_nsfw: Option, /// Limits community creation to admins only. + #[cfg_attr(feature = "full", ts(optional))] pub community_creation_admin_only: Option, /// Whether to require email verification. + #[cfg_attr(feature = "full", ts(optional))] pub require_email_verification: Option, /// Your application question form. This is in markdown, and can be many questions. + #[cfg_attr(feature = "full", ts(optional))] pub application_question: Option, /// Whether your instance is public, or private. + #[cfg_attr(feature = "full", ts(optional))] pub private_instance: Option, /// The default theme. Usually "browser" + #[cfg_attr(feature = "full", ts(optional))] pub default_theme: Option, /// The default post listing type, usually "local" + #[cfg_attr(feature = "full", ts(optional))] pub default_post_listing_type: Option, /// Default value for listing mode, usually "list" + #[cfg_attr(feature = "full", ts(optional))] pub default_post_listing_mode: Option, /// The default post sort, usually "active" + #[cfg_attr(feature = "full", ts(optional))] pub default_post_sort_type: Option, /// The default comment sort, usually "hot" + #[cfg_attr(feature = "full", ts(optional))] pub default_comment_sort_type: Option, /// An optional page of legal information + #[cfg_attr(feature = "full", ts(optional))] pub legal_information: Option, /// Whether to email admins when receiving a new application. + #[cfg_attr(feature = "full", ts(optional))] pub application_email_admins: Option, /// Whether to hide moderator names from the modlog. + #[cfg_attr(feature = "full", ts(optional))] pub hide_modlog_mod_names: Option, /// A list of allowed discussion languages. + #[cfg_attr(feature = "full", ts(optional))] pub discussion_languages: Option>, /// A regex string of items to filter. + #[cfg_attr(feature = "full", ts(optional))] pub slur_filter_regex: Option, /// The max length of actor names. + #[cfg_attr(feature = "full", ts(optional))] pub actor_name_max_length: Option, /// The number of messages allowed in a given time frame. + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_message: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_message_per_second: Option, /// The number of posts allowed in a given time frame. + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_post: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_post_per_second: Option, /// The number of registrations allowed in a given time frame. + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_register: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_register_per_second: Option, /// The number of image uploads allowed in a given time frame. + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_image: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_image_per_second: Option, /// The number of comments allowed in a given time frame. + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_comment: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_comment_per_second: Option, /// The number of searches allowed in a given time frame. + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_search: Option, + #[cfg_attr(feature = "full", ts(optional))] pub rate_limit_search_per_second: Option, /// Whether to enable federation. + #[cfg_attr(feature = "full", ts(optional))] pub federation_enabled: Option, /// Enables federation debugging. + #[cfg_attr(feature = "full", ts(optional))] pub federation_debug: Option, /// Whether to enable captchas for signups. + #[cfg_attr(feature = "full", ts(optional))] pub captcha_enabled: Option, /// The captcha difficulty. Can be easy, medium, or hard + #[cfg_attr(feature = "full", ts(optional))] pub captcha_difficulty: Option, /// A list of allowed instances. If none are set, federation is open. + #[cfg_attr(feature = "full", ts(optional))] pub allowed_instances: Option>, /// A list of blocked instances. + #[cfg_attr(feature = "full", ts(optional))] pub blocked_instances: Option>, /// A list of blocked URLs + #[cfg_attr(feature = "full", ts(optional))] pub blocked_urls: Option>, + #[cfg_attr(feature = "full", ts(optional))] pub registration_mode: Option, /// Whether to email admins for new reports. + #[cfg_attr(feature = "full", ts(optional))] pub reports_email_admins: Option, /// If present, nsfw content is visible by default. Should be displayed by frontends/clients /// when the site is first opened by a user. + #[cfg_attr(feature = "full", ts(optional))] pub content_warning: Option, /// Whether or not external auth methods can auto-register users. + #[cfg_attr(feature = "full", ts(optional))] pub oauth_registration: Option, /// What kind of post upvotes your site allows. + #[cfg_attr(feature = "full", ts(optional))] pub post_upvotes: Option, /// What kind of post downvotes your site allows. + #[cfg_attr(feature = "full", ts(optional))] pub post_downvotes: Option, /// What kind of comment upvotes your site allows. + #[cfg_attr(feature = "full", ts(optional))] pub comment_upvotes: Option, /// What kind of comment downvotes your site allows. + #[cfg_attr(feature = "full", ts(optional))] pub comment_downvotes: Option, } @@ -330,6 +448,7 @@ pub struct GetSiteResponse { pub site_view: SiteView, pub admins: Vec, pub version: String, + #[cfg_attr(feature = "full", ts(optional))] pub my_user: Option, pub all_languages: Vec, pub discussion_languages: Vec, @@ -338,9 +457,12 @@ pub struct GetSiteResponse { /// deprecated, use /api/v3/custom_emoji/list pub custom_emojis: Vec<()>, /// If the site has any taglines, a random one is included here for displaying + #[cfg_attr(feature = "full", ts(optional))] pub tagline: Option, /// A list of external auth methods your site supports. + #[cfg_attr(feature = "full", ts(optional))] pub oauth_providers: Option>, + #[cfg_attr(feature = "full", ts(optional))] pub admin_oauth_providers: Option>, pub blocked_urls: Vec, } @@ -352,6 +474,7 @@ pub struct GetSiteResponse { /// A response of federated instances. pub struct GetFederatedInstancesResponse { /// Optional, because federation may be disabled. + #[cfg_attr(feature = "full", ts(optional))] pub federated_instances: Option, } @@ -387,6 +510,7 @@ pub struct ReadableFederationState { #[serde(flatten)] internal_state: FederationQueueState, /// timestamp of the next retry attempt (null if fail count is 0) + #[cfg_attr(feature = "full", ts(optional))] next_retry: Option>, } @@ -411,6 +535,7 @@ pub struct InstanceWithFederationState { pub instance: Instance, /// if federation to this instance is or was active, show state of outgoing federation to this /// instance + #[cfg_attr(feature = "full", ts(optional))] pub federation_state: Option, } @@ -421,6 +546,7 @@ pub struct InstanceWithFederationState { /// Purges a person from the database. This will delete all content attached to that person. pub struct PurgePerson { pub person_id: PersonId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, } @@ -431,6 +557,7 @@ pub struct PurgePerson { /// Purges a community from the database. This will delete all content attached to that community. pub struct PurgeCommunity { pub community_id: CommunityId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, } @@ -441,6 +568,7 @@ pub struct PurgeCommunity { /// Purges a post from the database. This will delete all content attached to that post. pub struct PurgePost { pub post_id: PostId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, } @@ -451,6 +579,7 @@ pub struct PurgePost { /// Purges a comment from the database. This will delete all content attached to that comment. pub struct PurgeComment { pub comment_id: CommentId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, } @@ -461,8 +590,11 @@ pub struct PurgeComment { /// Fetches a list of registration applications. pub struct ListRegistrationApplications { /// Only shows the unread applications (IE those without an admin actor) + #[cfg_attr(feature = "full", ts(optional))] pub unread_only: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, } @@ -491,6 +623,7 @@ pub struct GetRegistrationApplication { pub struct ApproveRegistrationApplication { pub id: RegistrationApplicationId, pub approve: bool, + #[cfg_attr(feature = "full", ts(optional))] pub deny_reason: Option, } diff --git a/crates/api_common/src/tagline.rs b/crates/api_common/src/tagline.rs index 3090a2678a..528d379474 100644 --- a/crates/api_common/src/tagline.rs +++ b/crates/api_common/src/tagline.rs @@ -50,6 +50,8 @@ pub struct ListTaglinesResponse { #[cfg_attr(feature = "full", ts(export))] /// Fetches a list of taglines. pub struct ListTaglines { + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, } diff --git a/crates/api_common/src/utils.rs b/crates/api_common/src/utils.rs index d684e5c554..f2c03509df 100644 --- a/crates/api_common/src/utils.rs +++ b/crates/api_common/src/utils.rs @@ -42,6 +42,7 @@ use lemmy_db_views::{ structs::{LocalImageView, LocalUserView, SiteView}, }; use lemmy_db_views_actor::structs::{ + CommunityFollowerView, CommunityModeratorView, CommunityPersonBanView, CommunityView, @@ -215,7 +216,9 @@ pub async fn check_registration_application( let local_user_id = local_user_view.local_user.id; let registration = RegistrationApplication::find_by_local_user_id(pool, local_user_id).await?; if registration.admin_id.is_some() { - Err(LemmyErrorType::RegistrationDenied(registration.deny_reason))? + Err(LemmyErrorType::RegistrationDenied { + reason: registration.deny_reason, + })? } else { Err(LemmyErrorType::RegistrationApplicationIsPending)? } @@ -229,20 +232,17 @@ pub async fn check_registration_application( /// the user isn't banned. pub async fn check_community_user_action( person: &Person, - community_id: CommunityId, + community: &Community, pool: &mut DbPool<'_>, ) -> LemmyResult<()> { check_user_valid(person)?; - check_community_deleted_removed(community_id, pool).await?; - CommunityPersonBanView::check(pool, person.id, community_id).await?; + check_community_deleted_removed(community)?; + CommunityPersonBanView::check(pool, person.id, community.id).await?; + CommunityFollowerView::check_private_community_action(pool, person.id, community).await?; Ok(()) } -async fn check_community_deleted_removed( - community_id: CommunityId, - pool: &mut DbPool<'_>, -) -> LemmyResult<()> { - let community = Community::read(pool, community_id).await?; +pub fn check_community_deleted_removed(community: &Community) -> LemmyResult<()> { if community.deleted || community.removed { Err(LemmyErrorType::Deleted)? } @@ -255,16 +255,16 @@ async fn check_community_deleted_removed( /// removed/deleted. pub async fn check_community_mod_action( person: &Person, - community_id: CommunityId, + community: &Community, allow_deleted: bool, pool: &mut DbPool<'_>, ) -> LemmyResult<()> { - is_mod_or_admin(pool, person, community_id).await?; - CommunityPersonBanView::check(pool, person.id, community_id).await?; + is_mod_or_admin(pool, person, community.id).await?; + CommunityPersonBanView::check(pool, person.id, community.id).await?; // it must be possible to restore deleted community if !allow_deleted { - check_community_deleted_removed(community_id, pool).await?; + check_community_deleted_removed(community)?; } Ok(()) } @@ -354,6 +354,16 @@ pub fn check_private_instance( } } +/// If private messages are disabled, dont allow them to be sent / received +#[tracing::instrument(skip_all)] +pub fn check_private_messages_enabled(local_user_view: &LocalUserView) -> Result<(), LemmyError> { + if !local_user_view.local_user.enable_private_messages { + Err(LemmyErrorType::CouldntCreatePrivateMessage)? + } else { + Ok(()) + } +} + #[tracing::instrument(skip_all)] pub async fn build_federated_instances( local_site: &LocalSite, @@ -1109,7 +1119,7 @@ async fn proxy_image_link_internal( /// Rewrite a link to go through `/api/v3/image_proxy` endpoint. This is only for remote urls and /// if image_proxy setting is enabled. -pub(crate) async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult { +pub async fn proxy_image_link(link: Url, context: &LemmyContext) -> LemmyResult { proxy_image_link_internal( link, context.settings().pictrs_config()?.image_mode(), diff --git a/crates/api_crud/src/comment/create.rs b/crates/api_crud/src/comment/create.rs index 2f67fa7e7e..65aa1f612c 100644 --- a/crates/api_crud/src/comment/create.rs +++ b/crates/api_crud/src/comment/create.rs @@ -61,7 +61,12 @@ pub async fn create_comment( let post = post_view.post; let community_id = post_view.community.id; - check_community_user_action(&local_user_view.person, community_id, &mut context.pool()).await?; + check_community_user_action( + &local_user_view.person, + &post_view.community, + &mut context.pool(), + ) + .await?; check_post_deleted_or_removed(&post)?; // Check if post is locked, no new comments @@ -143,8 +148,7 @@ pub async fn create_comment( ActivityChannel::submit_activity( SendActivityData::CreateComment(inserted_comment.clone()), &context, - ) - .await?; + )?; // Update the read comments, so your own new comment doesn't appear as a +1 unread update_read_comments( diff --git a/crates/api_crud/src/comment/delete.rs b/crates/api_crud/src/comment/delete.rs index 2b5f35827f..60a22a2efc 100644 --- a/crates/api_crud/src/comment/delete.rs +++ b/crates/api_crud/src/comment/delete.rs @@ -35,7 +35,7 @@ pub async fn delete_comment( check_community_user_action( &local_user_view.person, - orig_comment.community.id, + &orig_comment.community, &mut context.pool(), ) .await?; @@ -76,8 +76,7 @@ pub async fn delete_comment( orig_comment.community, ), &context, - ) - .await?; + )?; Ok(Json( build_comment_response( diff --git a/crates/api_crud/src/comment/remove.rs b/crates/api_crud/src/comment/remove.rs index 3c137a9844..4e8a1871af 100644 --- a/crates/api_crud/src/comment/remove.rs +++ b/crates/api_crud/src/comment/remove.rs @@ -35,7 +35,7 @@ pub async fn remove_comment( check_community_mod_action( &local_user_view.person, - orig_comment.community.id, + &orig_comment.community, false, &mut context.pool(), ) @@ -99,8 +99,7 @@ pub async fn remove_comment( reason: data.reason.clone(), }, &context, - ) - .await?; + )?; Ok(Json( build_comment_response( diff --git a/crates/api_crud/src/comment/update.rs b/crates/api_crud/src/comment/update.rs index 51f65aa670..95cc85fe49 100644 --- a/crates/api_crud/src/comment/update.rs +++ b/crates/api_crud/src/comment/update.rs @@ -45,7 +45,7 @@ pub async fn update_comment( check_community_user_action( &local_user_view.person, - orig_comment.community.id, + &orig_comment.community, &mut context.pool(), ) .await?; @@ -98,8 +98,7 @@ pub async fn update_comment( ActivityChannel::submit_activity( SendActivityData::UpdateComment(updated_comment.clone()), &context, - ) - .await?; + )?; Ok(Json( build_comment_response( diff --git a/crates/api_crud/src/community/create.rs b/crates/api_crud/src/community/create.rs index cd0fc985ef..c81157950d 100644 --- a/crates/api_crud/src/community/create.rs +++ b/crates/api_crud/src/community/create.rs @@ -1,3 +1,4 @@ +use super::check_community_visibility_allowed; use activitypub_federation::{config::Data, http_signatures::generate_actor_keypair}; use actix_web::web::Json; use lemmy_api_common::{ @@ -23,6 +24,7 @@ use lemmy_db_schema::{ Community, CommunityFollower, CommunityFollowerForm, + CommunityFollowerState, CommunityInsertForm, CommunityModerator, CommunityModeratorForm, @@ -82,6 +84,12 @@ pub async fn create_community( is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?; + if let Some(desc) = &data.description { + is_valid_body_field(desc, false)?; + } + + check_community_visibility_allowed(data.visibility, &local_user_view)?; + // Double check for duplicate community actor_ids let community_actor_id = generate_local_apub_endpoint( EndpointType::Community, @@ -135,7 +143,8 @@ pub async fn create_community( let community_follower_form = CommunityFollowerForm { community_id: inserted_community.id, person_id: local_user_view.person.id, - pending: false, + state: Some(CommunityFollowerState::Accepted), + approver_id: None, }; CommunityFollower::follow(&mut context.pool(), &community_follower_form) diff --git a/crates/api_crud/src/community/delete.rs b/crates/api_crud/src/community/delete.rs index a2ceaff509..7f9d049339 100644 --- a/crates/api_crud/src/community/delete.rs +++ b/crates/api_crud/src/community/delete.rs @@ -22,13 +22,13 @@ pub async fn delete_community( local_user_view: LocalUserView, ) -> LemmyResult> { // Fetch the community mods - let community_id = data.community_id; let community_mods = - CommunityModeratorView::for_community(&mut context.pool(), community_id).await?; + CommunityModeratorView::for_community(&mut context.pool(), data.community_id).await?; + let community = Community::read(&mut context.pool(), data.community_id).await?; check_community_mod_action( &local_user_view.person, - community_id, + &community, true, &mut context.pool(), ) @@ -54,8 +54,7 @@ pub async fn delete_community( ActivityChannel::submit_activity( SendActivityData::DeleteCommunity(local_user_view.person.clone(), community, data.deleted), &context, - ) - .await?; + )?; build_community_response(&context, local_user_view, community_id).await } diff --git a/crates/api_crud/src/community/mod.rs b/crates/api_crud/src/community/mod.rs index 4bd0284828..0c9a507f15 100644 --- a/crates/api_crud/src/community/mod.rs +++ b/crates/api_crud/src/community/mod.rs @@ -1,5 +1,22 @@ +use lemmy_api_common::utils::is_admin; +use lemmy_db_schema::CommunityVisibility; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyResult; + pub mod create; pub mod delete; pub mod list; pub mod remove; pub mod update; + +/// For now only admins can make communities private, in order to prevent abuse. +/// Need to implement admin approval for new communities to get rid of this. +fn check_community_visibility_allowed( + visibility: Option, + local_user_view: &LocalUserView, +) -> LemmyResult<()> { + if visibility == Some(lemmy_db_schema::CommunityVisibility::Private) { + is_admin(local_user_view)?; + } + Ok(()) +} diff --git a/crates/api_crud/src/community/remove.rs b/crates/api_crud/src/community/remove.rs index f4271565d6..c506bde1b4 100644 --- a/crates/api_crud/src/community/remove.rs +++ b/crates/api_crud/src/community/remove.rs @@ -23,9 +23,10 @@ pub async fn remove_community( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { + let community = Community::read(&mut context.pool(), data.community_id).await?; check_community_mod_action( &local_user_view.person, - data.community_id, + &community, true, &mut context.pool(), ) @@ -65,8 +66,7 @@ pub async fn remove_community( removed: data.removed, }, &context, - ) - .await?; + )?; build_community_response(&context, local_user_view, community_id).await } diff --git a/crates/api_crud/src/community/update.rs b/crates/api_crud/src/community/update.rs index cde8058ee4..3dca7d8928 100644 --- a/crates/api_crud/src/community/update.rs +++ b/crates/api_crud/src/community/update.rs @@ -1,3 +1,4 @@ +use super::check_community_visibility_allowed; use activitypub_federation::config::Data; use actix_web::web::Json; use lemmy_api_common::{ @@ -51,6 +52,7 @@ pub async fn update_community( is_valid_body_field(sidebar, false)?; } + check_community_visibility_allowed(data.visibility, &local_user_view)?; let description = diesel_string_update(data.description.as_deref()); let old_community = Community::read(&mut context.pool(), data.community_id).await?; @@ -66,7 +68,7 @@ pub async fn update_community( // Verify its a mod (only mods can edit it) check_community_mod_action( &local_user_view.person, - data.community_id, + &old_community, false, &mut context.pool(), ) @@ -105,8 +107,7 @@ pub async fn update_community( ActivityChannel::submit_activity( SendActivityData::UpdateCommunity(local_user_view.person.clone(), community), &context, - ) - .await?; + )?; build_community_response(&context, local_user_view, community_id).await } diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 90c68bdbd9..16932cacba 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -85,15 +85,9 @@ pub async fn create_post( is_valid_body_field(body, true)?; } - check_community_user_action( - &local_user_view.person, - data.community_id, - &mut context.pool(), - ) - .await?; + let community = Community::read(&mut context.pool(), data.community_id).await?; + check_community_user_action(&local_user_view.person, &community, &mut context.pool()).await?; - let community_id = data.community_id; - let community = Community::read(&mut context.pool(), community_id).await?; if community.posting_restricted_to_mods { let community_id = data.community_id; CommunityModeratorView::check_is_community_moderator( @@ -110,7 +104,7 @@ pub async fn create_post( None => { default_post_language( &mut context.pool(), - community_id, + community.id, local_user_view.local_user.id, ) .await? @@ -119,7 +113,7 @@ pub async fn create_post( // Only need to check if language is allowed in case user set it explicitly. When using default // language, it already only returns allowed languages. - CommunityLanguage::is_allowed_community_language(&mut context.pool(), language_id, community_id) + CommunityLanguage::is_allowed_community_language(&mut context.pool(), language_id, community.id) .await?; let scheduled_publish_time = @@ -142,6 +136,7 @@ pub async fn create_post( .await .with_lemmy_type(LemmyErrorType::CouldntCreatePost)?; + let community_id = community.id; let federate_post = if scheduled_publish_time.is_none() { send_webmention(inserted_post.clone(), community); |post| Some(SendActivityData::CreatePost(post)) diff --git a/crates/api_crud/src/post/delete.rs b/crates/api_crud/src/post/delete.rs index be31759d53..e54086911d 100644 --- a/crates/api_crud/src/post/delete.rs +++ b/crates/api_crud/src/post/delete.rs @@ -8,7 +8,10 @@ use lemmy_api_common::{ utils::check_community_user_action, }; use lemmy_db_schema::{ - source::post::{Post, PostUpdateForm}, + source::{ + community::Community, + post::{Post, PostUpdateForm}, + }, traits::Crud, }; use lemmy_db_views::structs::LocalUserView; @@ -28,12 +31,8 @@ pub async fn delete_post( Err(LemmyErrorType::CouldntUpdatePost)? } - check_community_user_action( - &local_user_view.person, - orig_post.community_id, - &mut context.pool(), - ) - .await?; + let community = Community::read(&mut context.pool(), orig_post.community_id).await?; + check_community_user_action(&local_user_view.person, &community, &mut context.pool()).await?; // Verify that only the creator can delete if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) { @@ -54,8 +53,7 @@ pub async fn delete_post( ActivityChannel::submit_activity( SendActivityData::DeletePost(post, local_user_view.person.clone(), data.0), &context, - ) - .await?; + )?; build_post_response( &context, diff --git a/crates/api_crud/src/post/mod.rs b/crates/api_crud/src/post/mod.rs index 95df9663c1..5db0ad5d03 100644 --- a/crates/api_crud/src/post/mod.rs +++ b/crates/api_crud/src/post/mod.rs @@ -2,7 +2,7 @@ use chrono::{DateTime, TimeZone, Utc}; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::source::post::Post; use lemmy_db_views::structs::LocalUserView; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; pub mod create; pub mod delete; diff --git a/crates/api_crud/src/post/remove.rs b/crates/api_crud/src/post/remove.rs index c53a4552c8..7e3261e6fa 100644 --- a/crates/api_crud/src/post/remove.rs +++ b/crates/api_crud/src/post/remove.rs @@ -9,6 +9,7 @@ use lemmy_api_common::{ }; use lemmy_db_schema::{ source::{ + community::Community, local_user::LocalUser, moderator::{ModRemovePost, ModRemovePostForm}, post::{Post, PostUpdateForm}, @@ -26,11 +27,16 @@ pub async fn remove_post( local_user_view: LocalUserView, ) -> LemmyResult> { let post_id = data.post_id; + + // We cannot use PostView to avoid a database read here, as it doesn't return removed items + // by default. So we would have to pass in `is_mod_or_admin`, but that is impossible without + // knowing which community the post belongs to. let orig_post = Post::read(&mut context.pool(), post_id).await?; + let community = Community::read(&mut context.pool(), orig_post.community_id).await?; check_community_mod_action( &local_user_view.person, - orig_post.community_id, + &community, false, &mut context.pool(), ) @@ -77,8 +83,7 @@ pub async fn remove_post( removed: data.removed, }, &context, - ) - .await?; + )?; build_post_response(&context, orig_post.community_id, local_user_view, post_id).await } diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index cef8bfea8e..fc23e7d9e3 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -24,7 +24,7 @@ use lemmy_db_schema::{ traits::Crud, utils::{diesel_string_update, diesel_url_update, naive_now}, }; -use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views::structs::{LocalUserView, PostView}; use lemmy_utils::{ error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, utils::{ @@ -87,17 +87,17 @@ pub async fn update_post( } let post_id = data.post_id; - let orig_post = Post::read(&mut context.pool(), post_id).await?; + let orig_post = PostView::read(&mut context.pool(), post_id, None, false).await?; check_community_user_action( &local_user_view.person, - orig_post.community_id, + &orig_post.community, &mut context.pool(), ) .await?; // Verify that only the creator can edit - if !Post::is_post_creator(local_user_view.person.id, orig_post.creator_id) { + if !Post::is_post_creator(local_user_view.person.id, orig_post.post.creator_id) { Err(LemmyErrorType::NoPostEditAllowed)? } @@ -105,14 +105,14 @@ pub async fn update_post( CommunityLanguage::is_allowed_community_language( &mut context.pool(), language_id, - orig_post.community_id, + orig_post.community.id, ) .await?; } // handle changes to scheduled_publish_time let scheduled_publish_time = match ( - orig_post.scheduled_publish_time, + orig_post.post.scheduled_publish_time, data.scheduled_publish_time, ) { // schedule time can be changed if post is still scheduled (and not published yet) @@ -144,12 +144,12 @@ pub async fn update_post( // send out federation/webmention if necessary match ( - orig_post.scheduled_publish_time, + orig_post.post.scheduled_publish_time, data.scheduled_publish_time, ) { // schedule was removed, send create activity and webmention (Some(_), None) => { - let community = Community::read(&mut context.pool(), orig_post.community_id).await?; + let community = Community::read(&mut context.pool(), orig_post.community.id).await?; send_webmention(updated_post.clone(), community); generate_post_link_metadata( updated_post.clone(), @@ -175,7 +175,7 @@ pub async fn update_post( build_post_response( context.deref(), - orig_post.community_id, + orig_post.community.id, local_user_view, post_id, ) diff --git a/crates/api_crud/src/private_message/create.rs b/crates/api_crud/src/private_message/create.rs index 2a49e4ac03..1a6a78d003 100644 --- a/crates/api_crud/src/private_message/create.rs +++ b/crates/api_crud/src/private_message/create.rs @@ -5,6 +5,7 @@ use lemmy_api_common::{ private_message::{CreatePrivateMessage, PrivateMessageResponse}, send_activity::{ActivityChannel, SendActivityData}, utils::{ + check_private_messages_enabled, get_interface_language, get_url_blocklist, local_site_to_slur_regex, @@ -46,6 +47,16 @@ pub async fn create_private_message( ) .await?; + check_private_messages_enabled(&local_user_view)?; + + // Don't allow local sends to people who have private messages disabled + let recipient_local_user_opt = LocalUserView::read_person(&mut context.pool(), data.recipient_id) + .await + .ok(); + if let Some(recipient_local_user) = recipient_local_user_opt { + check_private_messages_enabled(&recipient_local_user)?; + } + let private_message_form = PrivateMessageInsertForm::new( local_user_view.person.id, data.recipient_id, @@ -78,8 +89,7 @@ pub async fn create_private_message( ActivityChannel::submit_activity( SendActivityData::CreatePrivateMessage(view.clone()), &context, - ) - .await?; + )?; Ok(Json(PrivateMessageResponse { private_message_view: view, diff --git a/crates/api_crud/src/private_message/delete.rs b/crates/api_crud/src/private_message/delete.rs index 936ff57b80..30efc020ca 100644 --- a/crates/api_crud/src/private_message/delete.rs +++ b/crates/api_crud/src/private_message/delete.rs @@ -42,8 +42,7 @@ pub async fn delete_private_message( ActivityChannel::submit_activity( SendActivityData::DeletePrivateMessage(local_user_view.person, private_message, data.deleted), &context, - ) - .await?; + )?; let view = PrivateMessageView::read(&mut context.pool(), private_message_id).await?; Ok(Json(PrivateMessageResponse { diff --git a/crates/api_crud/src/private_message/update.rs b/crates/api_crud/src/private_message/update.rs index 20eaadb36a..aa562c6262 100644 --- a/crates/api_crud/src/private_message/update.rs +++ b/crates/api_crud/src/private_message/update.rs @@ -59,8 +59,7 @@ pub async fn update_private_message( ActivityChannel::submit_activity( SendActivityData::UpdatePrivateMessage(view.clone()), &context, - ) - .await?; + )?; Ok(Json(PrivateMessageResponse { private_message_view: view, diff --git a/crates/api_crud/src/user/delete.rs b/crates/api_crud/src/user/delete.rs index d1825425c8..39598265ae 100644 --- a/crates/api_crud/src/user/delete.rs +++ b/crates/api_crud/src/user/delete.rs @@ -45,8 +45,7 @@ pub async fn delete_account( ActivityChannel::submit_activity( SendActivityData::DeleteUser(local_user_view.person, data.delete_content), &context, - ) - .await?; + )?; Ok(Json(SuccessResponse::default())) } diff --git a/crates/apub/assets/mastodon/objects/note.json b/crates/apub/assets/mastodon/objects/note_1.json similarity index 100% rename from crates/apub/assets/mastodon/objects/note.json rename to crates/apub/assets/mastodon/objects/note_1.json diff --git a/crates/apub/assets/mastodon/objects/note_2.json b/crates/apub/assets/mastodon/objects/note_2.json new file mode 100644 index 0000000000..b8c22b9765 --- /dev/null +++ b/crates/apub/assets/mastodon/objects/note_2.json @@ -0,0 +1,79 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + { + "ostatus": "http://ostatus.org#", + "atomUri": "ostatus:atomUri", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "sensitive": "as:sensitive", + "toot": "http://joinmastodon.org/ns#", + "votersCount": "toot:votersCount", + "blurhash": "toot:blurhash", + "focalPoint": { + "@container": "@list", + "@id": "toot:focalPoint" + } + } + ], + "id": "https://floss.social/users/kde/statuses/113306831140126616", + "type": "Note", + "summary": null, + "inReplyTo": "https://floss.social/users/kde/statuses/113306824627995724", + "published": "2024-10-14T16:57:15Z", + "url": "https://floss.social/@kde/113306831140126616", + "attributedTo": "https://floss.social/users/kde", + "to": ["https://www.w3.org/ns/activitystreams#Public"], + "cc": [ + "https://floss.social/users/kde/followers", + "https://lemmy.kde.social/c/kde", + "https://lemmy.kde.social/c/kde/followers" + ], + "sensitive": false, + "atomUri": "https://floss.social/users/kde/statuses/113306831140126616", + "inReplyToAtomUri": "https://floss.social/users/kde/statuses/113306824627995724", + "conversation": "tag:floss.social,2024-10-14:objectId=71424279:objectType=Conversation", + "content": "

@kde@lemmy.kde.social

We also need funding 💶 to keep the gears turning! Please support us with a donation:

https://kde.org/donate/

[3/3]

", + "contentMap": { + "en": "

@kde@lemmy.kde.social

We also need funding 💶 to keep the gears turning! Please support us with a donation:

https://kde.org/donate/

[3/3]

" + }, + "attachment": [ + { + "type": "Document", + "mediaType": "image/jpeg", + "url": "https://cdn.masto.host/floss/media_attachments/files/113/306/826/682/985/891/original/c8d906a2f2ab2334.jpg", + "name": "The KDE dragons Katie and Konqi stand on either side of a pot filling up with gold coins. Donate!", + "blurhash": "USQv:h-W-qI-^,W;RPs=^-R%NZxbo#sDobSc", + "focalPoint": [0.0, 0.0], + "width": 1500, + "height": 1095 + } + ], + "tag": [ + { + "type": "Mention", + "href": "https://lemmy.kde.social/c/kde", + "name": "@kde@lemmy.kde.social" + } + ], + "replies": { + "id": "https://floss.social/users/kde/statuses/113306831140126616/replies", + "type": "Collection", + "first": { + "type": "CollectionPage", + "next": "https://floss.social/users/kde/statuses/113306831140126616/replies?only_other_accounts=true&page=true", + "partOf": "https://floss.social/users/kde/statuses/113306831140126616/replies", + "items": [] + } + }, + "likes": { + "id": "https://floss.social/users/kde/statuses/113306831140126616/likes", + "type": "Collection", + "totalItems": 39 + }, + "shares": { + "id": "https://floss.social/users/kde/statuses/113306831140126616/shares", + "type": "Collection", + "totalItems": 24 + } +} diff --git a/crates/apub/src/activities/block/block_user.rs b/crates/apub/src/activities/block/block_user.rs index 64d5e7816a..866e1cc6c2 100644 --- a/crates/apub/src/activities/block/block_user.rs +++ b/crates/apub/src/activities/block/block_user.rs @@ -1,3 +1,4 @@ +use super::to_and_audience; use crate::{ activities::{ block::{generate_cc, SiteOrCommunity}, @@ -7,6 +8,7 @@ use crate::{ verify_is_public, verify_mod_action, verify_person_in_community, + verify_visibility, }, activity_lists::AnnouncableActivities, insert_received_activity, @@ -15,7 +17,7 @@ use crate::{ }; use activitypub_federation::{ config::Data, - kinds::{activity::BlockType, public}, + kinds::activity::BlockType, protocol::verification::verify_domains_match, traits::{ActivityHandler, Actor}, }; @@ -52,14 +54,10 @@ impl BlockUser { expires: Option>, context: &Data, ) -> LemmyResult { - let audience = if let SiteOrCommunity::Community(c) = target { - Some(c.id().into()) - } else { - None - }; + let (to, audience) = to_and_audience(target)?; Ok(BlockUser { actor: mod_.id().into(), - to: vec![public()], + to, object: user.id().into(), cc: generate_cc(target, &mut context.pool()).await?, target: target.id(), @@ -125,9 +123,9 @@ impl ActivityHandler for BlockUser { #[tracing::instrument(skip_all)] async fn verify(&self, context: &Data) -> LemmyResult<()> { - verify_is_public(&self.to, &self.cc)?; match self.target.dereference(context).await? { SiteOrCommunity::Site(site) => { + verify_is_public(&self.to, &self.cc)?; let domain = self .object .inner() @@ -143,6 +141,7 @@ impl ActivityHandler for BlockUser { verify_domains_match(&site.id(), self.object.inner())?; } SiteOrCommunity::Community(community) => { + verify_visibility(&self.to, &self.cc, &community)?; verify_person_in_community(&self.actor, &community, context).await?; verify_mod_action(&self.actor, &community, context).await?; } @@ -194,11 +193,7 @@ impl ActivityHandler for BlockUser { CommunityPersonBan::ban(&mut context.pool(), &community_user_ban_form).await?; // Also unsubscribe them from the community, if they are subscribed - let community_follower_form = CommunityFollowerForm { - community_id: community.id, - person_id: blocked_person.id, - pending: false, - }; + let community_follower_form = CommunityFollowerForm::new(community.id, blocked_person.id); CommunityFollower::unfollow(&mut context.pool(), &community_follower_form) .await .ok(); diff --git a/crates/apub/src/activities/block/mod.rs b/crates/apub/src/activities/block/mod.rs index c8323fcb49..550d981837 100644 --- a/crates/apub/src/activities/block/mod.rs +++ b/crates/apub/src/activities/block/mod.rs @@ -1,3 +1,4 @@ +use super::generate_to; use crate::{ objects::{community::ApubCommunity, instance::ApubSite}, protocol::{ @@ -8,6 +9,7 @@ use crate::{ use activitypub_federation::{ config::Data, fetch::object_id::ObjectId, + kinds::public, traits::{Actor, Object}, }; use chrono::{DateTime, Utc}; @@ -205,3 +207,13 @@ pub(crate) async fn send_ban_from_community( .await } } + +fn to_and_audience( + target: &SiteOrCommunity, +) -> LemmyResult<(Vec, Option>)> { + Ok(if let SiteOrCommunity::Community(c) = target { + (vec![generate_to(c)?], Some(c.id().into())) + } else { + (vec![public()], None) + }) +} diff --git a/crates/apub/src/activities/block/undo_block_user.rs b/crates/apub/src/activities/block/undo_block_user.rs index f9f6890b6e..29fc22f0cf 100644 --- a/crates/apub/src/activities/block/undo_block_user.rs +++ b/crates/apub/src/activities/block/undo_block_user.rs @@ -1,3 +1,4 @@ +use super::to_and_audience; use crate::{ activities::{ block::{generate_cc, SiteOrCommunity}, @@ -5,6 +6,7 @@ use crate::{ generate_activity_id, send_lemmy_activity, verify_is_public, + verify_visibility, }, activity_lists::AnnouncableActivities, insert_received_activity, @@ -13,7 +15,7 @@ use crate::{ }; use activitypub_federation::{ config::Data, - kinds::{activity::UndoType, public}, + kinds::activity::UndoType, protocol::verification::verify_domains_match, traits::{ActivityHandler, Actor}, }; @@ -44,11 +46,7 @@ impl UndoBlockUser { context: &Data, ) -> LemmyResult<()> { let block = BlockUser::new(target, user, mod_, None, reason, None, context).await?; - let audience = if let SiteOrCommunity::Community(c) = target { - Some(c.id().into()) - } else { - None - }; + let (to, audience) = to_and_audience(target)?; let id = generate_activity_id( UndoType::Undo, @@ -56,7 +54,7 @@ impl UndoBlockUser { )?; let undo = UndoBlockUser { actor: mod_.id().into(), - to: vec![public()], + to, object: block, cc: generate_cc(target, &mut context.pool()).await?, kind: UndoType::Undo, @@ -94,7 +92,6 @@ impl ActivityHandler for UndoBlockUser { #[tracing::instrument(skip_all)] async fn verify(&self, context: &Data) -> LemmyResult<()> { - verify_is_public(&self.to, &self.cc)?; verify_domains_match(self.actor.inner(), self.object.actor.inner())?; self.object.verify(context).await?; Ok(()) @@ -108,6 +105,7 @@ impl ActivityHandler for UndoBlockUser { let blocked_person = self.object.object.dereference(context).await?; match self.object.target.dereference(context).await? { SiteOrCommunity::Site(_site) => { + verify_is_public(&self.to, &self.cc)?; let blocked_person = Person::update( &mut context.pool(), blocked_person.id, @@ -135,6 +133,7 @@ impl ActivityHandler for UndoBlockUser { ModBan::create(&mut context.pool(), &form).await?; } SiteOrCommunity::Community(community) => { + verify_visibility(&self.to, &self.cc, &community)?; let community_user_ban_form = CommunityPersonBanForm { community_id: community.id, person_id: blocked_person.id, diff --git a/crates/apub/src/activities/community/announce.rs b/crates/apub/src/activities/community/announce.rs index e374d28747..d32b9d76e5 100644 --- a/crates/apub/src/activities/community/announce.rs +++ b/crates/apub/src/activities/community/announce.rs @@ -2,9 +2,10 @@ use crate::{ activities::{ generate_activity_id, generate_announce_activity_id, + generate_to, send_lemmy_activity, - verify_is_public, verify_person_in_community, + verify_visibility, }, activity_lists::AnnouncableActivities, insert_received_activity, @@ -18,7 +19,7 @@ use crate::{ }; use activitypub_federation::{ config::Data, - kinds::{activity::AnnounceType, public}, + kinds::activity::AnnounceType, traits::{ActivityHandler, Actor}, }; use lemmy_api_common::context::LemmyContext; @@ -92,7 +93,7 @@ impl AnnounceActivity { generate_announce_activity_id(inner_kind, &context.settings().get_protocol_and_hostname())?; Ok(AnnounceActivity { actor: community.id().into(), - to: vec![public()], + to: vec![generate_to(community)?], object: IdOrNestedObject::NestedObject(object), cc: community .followers_url @@ -154,7 +155,6 @@ impl ActivityHandler for AnnounceActivity { #[tracing::instrument(skip_all)] async fn verify(&self, _context: &Data) -> LemmyResult<()> { - verify_is_public(&self.to, &self.cc)?; Ok(()) } @@ -169,6 +169,7 @@ impl ActivityHandler for AnnounceActivity { } let community = object.community(context).await?; + verify_visibility(&self.to, &self.cc, &community)?; can_accept_activity_in_community(&Some(community), context).await?; // verify here in order to avoid fetching the object twice over http diff --git a/crates/apub/src/activities/community/collection_add.rs b/crates/apub/src/activities/community/collection_add.rs index 5ab754d35c..ae508c2c5b 100644 --- a/crates/apub/src/activities/community/collection_add.rs +++ b/crates/apub/src/activities/community/collection_add.rs @@ -2,9 +2,10 @@ use crate::{ activities::{ community::send_activity_in_community, generate_activity_id, - verify_is_public, + generate_to, verify_mod_action, verify_person_in_community, + verify_visibility, }, activity_lists::AnnouncableActivities, insert_received_activity, @@ -17,7 +18,7 @@ use crate::{ use activitypub_federation::{ config::Data, fetch::object_id::ObjectId, - kinds::{activity::AddType, public}, + kinds::activity::AddType, traits::{ActivityHandler, Actor}, }; use lemmy_api_common::{ @@ -53,7 +54,7 @@ impl CollectionAdd { )?; let add = CollectionAdd { actor: actor.id().into(), - to: vec![public()], + to: vec![generate_to(community)?], object: added_mod.id(), target: generate_moderators_url(&community.actor_id)?.into(), cc: vec![community.id()], @@ -79,7 +80,7 @@ impl CollectionAdd { )?; let add = CollectionAdd { actor: actor.id().into(), - to: vec![public()], + to: vec![generate_to(community)?], object: featured_post.ap_id.clone().into(), target: generate_featured_url(&community.actor_id)?.into(), cc: vec![community.id()], @@ -115,8 +116,8 @@ impl ActivityHandler for CollectionAdd { #[tracing::instrument(skip_all)] async fn verify(&self, context: &Data) -> LemmyResult<()> { - verify_is_public(&self.to, &self.cc)?; let community = self.community(context).await?; + verify_visibility(&self.to, &self.cc, &community)?; verify_person_in_community(&self.actor, &community, context).await?; verify_mod_action(&self.actor, &community, context).await?; Ok(()) diff --git a/crates/apub/src/activities/community/collection_remove.rs b/crates/apub/src/activities/community/collection_remove.rs index 90df1fd140..6c08735ed5 100644 --- a/crates/apub/src/activities/community/collection_remove.rs +++ b/crates/apub/src/activities/community/collection_remove.rs @@ -2,9 +2,10 @@ use crate::{ activities::{ community::send_activity_in_community, generate_activity_id, - verify_is_public, + generate_to, verify_mod_action, verify_person_in_community, + verify_visibility, }, activity_lists::AnnouncableActivities, insert_received_activity, @@ -14,7 +15,7 @@ use crate::{ use activitypub_federation::{ config::Data, fetch::object_id::ObjectId, - kinds::{activity::RemoveType, public}, + kinds::activity::RemoveType, traits::{ActivityHandler, Actor}, }; use lemmy_api_common::{ @@ -48,7 +49,7 @@ impl CollectionRemove { )?; let remove = CollectionRemove { actor: actor.id().into(), - to: vec![public()], + to: vec![generate_to(community)?], object: removed_mod.id(), target: generate_moderators_url(&community.actor_id)?.into(), id: id.clone(), @@ -74,7 +75,7 @@ impl CollectionRemove { )?; let remove = CollectionRemove { actor: actor.id().into(), - to: vec![public()], + to: vec![generate_to(community)?], object: featured_post.ap_id.clone().into(), target: generate_featured_url(&community.actor_id)?.into(), cc: vec![community.id()], @@ -110,8 +111,8 @@ impl ActivityHandler for CollectionRemove { #[tracing::instrument(skip_all)] async fn verify(&self, context: &Data) -> LemmyResult<()> { - verify_is_public(&self.to, &self.cc)?; let community = self.community(context).await?; + verify_visibility(&self.to, &self.cc, &community)?; verify_person_in_community(&self.actor, &community, context).await?; verify_mod_action(&self.actor, &community, context).await?; Ok(()) diff --git a/crates/apub/src/activities/community/lock_page.rs b/crates/apub/src/activities/community/lock_page.rs index 0d90b5bb0b..a9bacea8a8 100644 --- a/crates/apub/src/activities/community/lock_page.rs +++ b/crates/apub/src/activities/community/lock_page.rs @@ -3,9 +3,10 @@ use crate::{ check_community_deleted_or_removed, community::send_activity_in_community, generate_activity_id, - verify_is_public, + generate_to, verify_mod_action, verify_person_in_community, + verify_visibility, }, activity_lists::AnnouncableActivities, insert_received_activity, @@ -18,7 +19,7 @@ use crate::{ use activitypub_federation::{ config::Data, fetch::object_id::ObjectId, - kinds::{activity::UndoType, public}, + kinds::activity::UndoType, traits::ActivityHandler, }; use lemmy_api_common::context::LemmyContext; @@ -49,8 +50,8 @@ impl ActivityHandler for LockPage { } async fn verify(&self, context: &Data) -> Result<(), Self::Error> { - verify_is_public(&self.to, &self.cc)?; let community = self.community(context).await?; + verify_visibility(&self.to, &self.cc, &community)?; verify_person_in_community(&self.actor, &community, context).await?; check_community_deleted_or_removed(&community)?; verify_mod_action(&self.actor, &community, context).await?; @@ -92,8 +93,8 @@ impl ActivityHandler for UndoLockPage { } async fn verify(&self, context: &Data) -> Result<(), Self::Error> { - verify_is_public(&self.to, &self.cc)?; let community = self.community(context).await?; + verify_visibility(&self.to, &self.cc, &community)?; verify_person_in_community(&self.actor, &community, context).await?; check_community_deleted_or_removed(&community)?; verify_mod_action(&self.actor, &community, context).await?; @@ -137,7 +138,7 @@ pub(crate) async fn send_lock_post( let community_id = community.actor_id.inner().clone(); let lock = LockPage { actor: actor.actor_id.clone().into(), - to: vec![public()], + to: vec![generate_to(&community)?], object: ObjectId::from(post.ap_id), cc: vec![community_id.clone()], kind: LockType::Lock, @@ -153,7 +154,7 @@ pub(crate) async fn send_lock_post( )?; let undo = UndoLockPage { actor: lock.actor.clone(), - to: vec![public()], + to: vec![generate_to(&community)?], cc: lock.cc.clone(), kind: UndoType::Undo, id, diff --git a/crates/apub/src/activities/community/update.rs b/crates/apub/src/activities/community/update.rs index 48a64bd9d0..85be942465 100644 --- a/crates/apub/src/activities/community/update.rs +++ b/crates/apub/src/activities/community/update.rs @@ -2,9 +2,10 @@ use crate::{ activities::{ community::send_activity_in_community, generate_activity_id, - verify_is_public, + generate_to, verify_mod_action, verify_person_in_community, + verify_visibility, }, activity_lists::AnnouncableActivities, insert_received_activity, @@ -13,7 +14,7 @@ use crate::{ }; use activitypub_federation::{ config::Data, - kinds::{activity::UpdateType, public}, + kinds::activity::UpdateType, traits::{ActivityHandler, Actor, Object}, }; use lemmy_api_common::context::LemmyContext; @@ -42,7 +43,7 @@ pub(crate) async fn send_update_community( )?; let update = UpdateCommunity { actor: actor.id().into(), - to: vec![public()], + to: vec![generate_to(&community)?], object: Box::new(community.clone().into_json(&context).await?), cc: vec![community.id()], kind: UpdateType::Update, @@ -77,8 +78,8 @@ impl ActivityHandler for UpdateCommunity { #[tracing::instrument(skip_all)] async fn verify(&self, context: &Data) -> LemmyResult<()> { - verify_is_public(&self.to, &self.cc)?; let community = self.community(context).await?; + verify_visibility(&self.to, &self.cc, &community)?; verify_person_in_community(&self.actor, &community, context).await?; verify_mod_action(&self.actor, &community, context).await?; ApubCommunity::verify(&self.object, &community.actor_id.clone().into(), context).await?; diff --git a/crates/apub/src/activities/create_or_update/comment.rs b/crates/apub/src/activities/create_or_update/comment.rs index 0a07371516..90ab0153f8 100644 --- a/crates/apub/src/activities/create_or_update/comment.rs +++ b/crates/apub/src/activities/create_or_update/comment.rs @@ -3,8 +3,9 @@ use crate::{ check_community_deleted_or_removed, community::send_activity_in_community, generate_activity_id, - verify_is_public, + generate_to, verify_person_in_community, + verify_visibility, }, activity_lists::AnnouncableActivities, insert_received_activity, @@ -18,7 +19,6 @@ use crate::{ use activitypub_federation::{ config::Data, fetch::object_id::ObjectId, - kinds::public, protocol::verification::{verify_domains_match, verify_urls_match}, traits::{ActivityHandler, Actor, Object}, }; @@ -70,7 +70,7 @@ impl CreateOrUpdateNote { let create_or_update = CreateOrUpdateNote { actor: person.id().into(), - to: vec![public()], + to: vec![generate_to(&community)?], cc: note.cc.clone(), tag: note.tag.clone(), object: note, @@ -118,9 +118,9 @@ impl ActivityHandler for CreateOrUpdateNote { #[tracing::instrument(skip_all)] async fn verify(&self, context: &Data) -> LemmyResult<()> { - verify_is_public(&self.to, &self.cc)?; let post = self.object.get_parents(context).await?.0; let community = self.community(context).await?; + verify_visibility(&self.to, &self.cc, &community)?; verify_person_in_community(&self.actor, &community, context).await?; verify_domains_match(self.actor.inner(), self.object.id.inner())?; diff --git a/crates/apub/src/activities/create_or_update/post.rs b/crates/apub/src/activities/create_or_update/post.rs index fb53100f60..d0cf17a51f 100644 --- a/crates/apub/src/activities/create_or_update/post.rs +++ b/crates/apub/src/activities/create_or_update/post.rs @@ -3,8 +3,9 @@ use crate::{ check_community_deleted_or_removed, community::send_activity_in_community, generate_activity_id, - verify_is_public, + generate_to, verify_person_in_community, + verify_visibility, }, activity_lists::AnnouncableActivities, insert_received_activity, @@ -16,7 +17,6 @@ use crate::{ }; use activitypub_federation::{ config::Data, - kinds::public, protocol::verification::{verify_domains_match, verify_urls_match}, traits::{ActivityHandler, Actor, Object}, }; @@ -49,7 +49,7 @@ impl CreateOrUpdatePage { )?; Ok(CreateOrUpdatePage { actor: actor.id().into(), - to: vec![public()], + to: vec![generate_to(community)?], object: post.into_json(context).await?, cc: vec![community.id()], kind, @@ -102,8 +102,8 @@ impl ActivityHandler for CreateOrUpdatePage { #[tracing::instrument(skip_all)] async fn verify(&self, context: &Data) -> LemmyResult<()> { - verify_is_public(&self.to, &self.cc)?; let community = self.community(context).await?; + verify_visibility(&self.to, &self.cc, &community)?; verify_person_in_community(&self.actor, &community, context).await?; check_community_deleted_or_removed(&community)?; verify_domains_match(self.actor.inner(), self.object.id.inner())?; diff --git a/crates/apub/src/activities/deletion/delete.rs b/crates/apub/src/activities/deletion/delete.rs index 1ddf642b96..064f0bc826 100644 --- a/crates/apub/src/activities/deletion/delete.rs +++ b/crates/apub/src/activities/deletion/delete.rs @@ -84,7 +84,7 @@ impl Delete { pub(in crate::activities::deletion) fn new( actor: &ApubPerson, object: DeletableObjects, - to: Url, + to: Vec, community: Option<&Community>, summary: Option, context: &Data, @@ -96,7 +96,7 @@ impl Delete { let cc: Option = community.map(|c| c.actor_id.clone().into()); Ok(Delete { actor: actor.actor_id.clone().into(), - to: vec![to], + to, object: IdOrNestedObject::Id(object.id()), cc: cc.into_iter().collect(), kind: DeleteType::Delete, diff --git a/crates/apub/src/activities/deletion/mod.rs b/crates/apub/src/activities/deletion/mod.rs index b12532087e..15118a4764 100644 --- a/crates/apub/src/activities/deletion/mod.rs +++ b/crates/apub/src/activities/deletion/mod.rs @@ -1,11 +1,12 @@ +use super::{generate_to, verify_is_public}; use crate::{ activities::{ community::send_activity_in_community, send_lemmy_activity, - verify_is_public, verify_mod_action, verify_person, verify_person_in_community, + verify_visibility, }, activity_lists::AnnouncableActivities, objects::{ @@ -59,11 +60,12 @@ pub(crate) async fn send_apub_delete_in_community( ) -> LemmyResult<()> { let actor = ApubPerson::from(actor); let is_mod_action = reason.is_some(); + let to = vec![generate_to(&community)?]; let activity = if deleted { - let delete = Delete::new(&actor, object, public(), Some(&community), reason, context)?; + let delete = Delete::new(&actor, object, to, Some(&community), reason, context)?; AnnouncableActivities::Delete(delete) } else { - let undo = UndoDelete::new(&actor, object, public(), Some(&community), reason, context)?; + let undo = UndoDelete::new(&actor, object, to, Some(&community), reason, context)?; AnnouncableActivities::UndoDelete(undo) }; send_activity_in_community( @@ -92,10 +94,10 @@ pub(crate) async fn send_apub_delete_private_message( let deletable = DeletableObjects::PrivateMessage(pm.into()); let inbox = ActivitySendTargets::to_inbox(recipient.shared_inbox_or_inbox()); if deleted { - let delete: Delete = Delete::new(actor, deletable, recipient.id(), None, None, &context)?; + let delete: Delete = Delete::new(actor, deletable, vec![recipient.id()], None, None, &context)?; send_lemmy_activity(&context, delete, actor, inbox, true).await?; } else { - let undo = UndoDelete::new(actor, deletable, recipient.id(), None, None, &context)?; + let undo = UndoDelete::new(actor, deletable, vec![recipient.id()], None, None, &context)?; send_lemmy_activity(&context, undo, actor, inbox, true).await?; }; Ok(()) @@ -109,7 +111,7 @@ pub async fn send_apub_delete_user( let person: ApubPerson = person.into(); let deletable = DeletableObjects::Person(person.clone()); - let mut delete: Delete = Delete::new(&person, deletable, public(), None, None, &context)?; + let mut delete: Delete = Delete::new(&person, deletable, vec![public()], None, None, &context)?; delete.remove_data = Some(remove_data); let inboxes = ActivitySendTargets::to_all_instances(); @@ -170,7 +172,7 @@ pub(in crate::activities) async fn verify_delete_activity( let object = DeletableObjects::read_from_db(activity.object.id(), context).await?; match object { DeletableObjects::Community(community) => { - verify_is_public(&activity.to, &[])?; + verify_visibility(&activity.to, &[], &community)?; if community.local { // can only do this check for local community, in remote case it would try to fetch the // deleted community (which fails) @@ -185,22 +187,24 @@ pub(in crate::activities) async fn verify_delete_activity( verify_urls_match(person.actor_id.inner(), activity.object.id())?; } DeletableObjects::Post(p) => { - verify_is_public(&activity.to, &[])?; + let community = activity.community(context).await?; + verify_visibility(&activity.to, &[], &community)?; verify_delete_post_or_comment( &activity.actor, &p.ap_id.clone().into(), - &activity.community(context).await?, + &community, is_mod_action, context, ) .await?; } DeletableObjects::Comment(c) => { - verify_is_public(&activity.to, &[])?; + let community = activity.community(context).await?; + verify_visibility(&activity.to, &[], &community)?; verify_delete_post_or_comment( &activity.actor, &c.ap_id.clone().into(), - &activity.community(context).await?, + &community, is_mod_action, context, ) diff --git a/crates/apub/src/activities/deletion/undo_delete.rs b/crates/apub/src/activities/deletion/undo_delete.rs index 6328bb4278..f4a7bb9b95 100644 --- a/crates/apub/src/activities/deletion/undo_delete.rs +++ b/crates/apub/src/activities/deletion/undo_delete.rs @@ -68,7 +68,7 @@ impl UndoDelete { pub(in crate::activities::deletion) fn new( actor: &ApubPerson, object: DeletableObjects, - to: Url, + to: Vec, community: Option<&Community>, summary: Option, context: &Data, @@ -82,7 +82,7 @@ impl UndoDelete { let cc: Option = community.map(|c| c.actor_id.clone().into()); Ok(UndoDelete { actor: actor.actor_id.clone().into(), - to: vec![to], + to, object, cc: cc.into_iter().collect(), kind: UndoType::Undo, diff --git a/crates/apub/src/activities/following/follow.rs b/crates/apub/src/activities/following/follow.rs index 02f29a1a9e..befa2e00c1 100644 --- a/crates/apub/src/activities/following/follow.rs +++ b/crates/apub/src/activities/following/follow.rs @@ -20,7 +20,7 @@ use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ source::{ activity::ActivitySendTargets, - community::{CommunityFollower, CommunityFollowerForm}, + community::{CommunityFollower, CommunityFollowerForm, CommunityFollowerState}, person::{PersonFollower, PersonFollowerForm}, }, traits::Followable, @@ -102,21 +102,25 @@ impl ActivityHandler for Follow { pending: false, }; PersonFollower::follow(&mut context.pool(), &form).await?; + AcceptFollow::send(self, context).await?; } UserOrCommunity::Community(c) => { - // Dont allow following local-only community via federation. - if c.visibility != CommunityVisibility::Public { - return Err(LemmyErrorType::NotFound.into()); - } + let state = Some(match c.visibility { + CommunityVisibility::Public => CommunityFollowerState::Accepted, + CommunityVisibility::Private => CommunityFollowerState::ApprovalRequired, + // Dont allow following local-only community via federation. + CommunityVisibility::LocalOnly => return Err(LemmyErrorType::NotFound.into()), + }); let form = CommunityFollowerForm { - community_id: c.id, - person_id: actor.id, - pending: false, + state, + ..CommunityFollowerForm::new(c.id, actor.id) }; CommunityFollower::follow(&mut context.pool(), &form).await?; + if c.visibility == CommunityVisibility::Public { + AcceptFollow::send(self, context).await?; + } } } - - AcceptFollow::send(self, context).await + Ok(()) } } diff --git a/crates/apub/src/activities/following/mod.rs b/crates/apub/src/activities/following/mod.rs index 7c7163f120..83cdc841c4 100644 --- a/crates/apub/src/activities/following/mod.rs +++ b/crates/apub/src/activities/following/mod.rs @@ -1,15 +1,26 @@ +use super::generate_activity_id; use crate::{ objects::{community::ApubCommunity, person::ApubPerson}, - protocol::activities::following::{follow::Follow, undo_follow::UndoFollow}, + protocol::activities::following::{ + accept::AcceptFollow, + follow::Follow, + reject::RejectFollow, + undo_follow::UndoFollow, + }, }; -use activitypub_federation::config::Data; +use activitypub_federation::{config::Data, kinds::activity::FollowType}; use lemmy_api_common::context::LemmyContext; -use lemmy_db_schema::source::{community::Community, person::Person}; +use lemmy_db_schema::{ + newtypes::{CommunityId, PersonId}, + source::{community::Community, person::Person}, + traits::Crud, +}; use lemmy_utils::error::LemmyResult; -pub mod accept; -pub mod follow; -pub mod undo_follow; +pub(crate) mod accept; +pub(crate) mod follow; +pub(crate) mod reject; +pub(crate) mod undo_follow; pub async fn send_follow_community( community: Community, @@ -25,3 +36,29 @@ pub async fn send_follow_community( UndoFollow::send(&actor, &community, context).await } } + +pub async fn send_accept_or_reject_follow( + community_id: CommunityId, + person_id: PersonId, + accepted: bool, + context: &Data, +) -> LemmyResult<()> { + let community = Community::read(&mut context.pool(), community_id).await?; + let person = Person::read(&mut context.pool(), person_id).await?; + + let follow = Follow { + actor: person.actor_id.into(), + to: Some([community.actor_id.clone().into()]), + object: community.actor_id.into(), + kind: FollowType::Follow, + id: generate_activity_id( + FollowType::Follow, + &context.settings().get_protocol_and_hostname(), + )?, + }; + if accepted { + AcceptFollow::send(follow, context).await + } else { + RejectFollow::send(follow, context).await + } +} diff --git a/crates/apub/src/activities/following/reject.rs b/crates/apub/src/activities/following/reject.rs new file mode 100644 index 0000000000..8f1623d20b --- /dev/null +++ b/crates/apub/src/activities/following/reject.rs @@ -0,0 +1,79 @@ +use crate::{ + activities::{generate_activity_id, send_lemmy_activity}, + insert_received_activity, + protocol::activities::following::{follow::Follow, reject::RejectFollow}, +}; +use activitypub_federation::{ + config::Data, + kinds::activity::RejectType, + protocol::verification::verify_urls_match, + traits::{ActivityHandler, Actor}, +}; +use lemmy_api_common::context::LemmyContext; +use lemmy_db_schema::{ + source::{ + activity::ActivitySendTargets, + community::{CommunityFollower, CommunityFollowerForm}, + }, + traits::Followable, +}; +use lemmy_utils::error::{LemmyError, LemmyResult}; +use url::Url; + +impl RejectFollow { + #[tracing::instrument(skip_all)] + pub async fn send(follow: Follow, context: &Data) -> LemmyResult<()> { + let user_or_community = follow.object.dereference_local(context).await?; + let person = follow.actor.clone().dereference(context).await?; + let reject = RejectFollow { + actor: user_or_community.id().into(), + to: Some([person.id().into()]), + object: follow, + kind: RejectType::Reject, + id: generate_activity_id( + RejectType::Reject, + &context.settings().get_protocol_and_hostname(), + )?, + }; + let inbox = ActivitySendTargets::to_inbox(person.shared_inbox_or_inbox()); + send_lemmy_activity(context, reject, &user_or_community, inbox, true).await + } +} + +/// Handle rejected follows +#[async_trait::async_trait] +impl ActivityHandler for RejectFollow { + type DataType = LemmyContext; + type Error = LemmyError; + + fn id(&self) -> &Url { + &self.id + } + + fn actor(&self) -> &Url { + self.actor.inner() + } + + #[tracing::instrument(skip_all)] + async fn verify(&self, context: &Data) -> LemmyResult<()> { + verify_urls_match(self.actor.inner(), self.object.object.inner())?; + self.object.verify(context).await?; + if let Some(to) = &self.to { + verify_urls_match(to[0].inner(), self.object.actor.inner())?; + } + Ok(()) + } + + #[tracing::instrument(skip_all)] + async fn receive(self, context: &Data) -> LemmyResult<()> { + insert_received_activity(&self.id, context).await?; + let community = self.actor.dereference(context).await?; + let person = self.object.actor.dereference(context).await?; + + // remove the follow + let form = CommunityFollowerForm::new(community.id, person.id); + CommunityFollower::unfollow(&mut context.pool(), &form).await?; + + Ok(()) + } +} diff --git a/crates/apub/src/activities/following/undo_follow.rs b/crates/apub/src/activities/following/undo_follow.rs index ba6253946f..1aa6bb7fca 100644 --- a/crates/apub/src/activities/following/undo_follow.rs +++ b/crates/apub/src/activities/following/undo_follow.rs @@ -90,11 +90,7 @@ impl ActivityHandler for UndoFollow { PersonFollower::unfollow(&mut context.pool(), &form).await?; } UserOrCommunity::Community(c) => { - let form = CommunityFollowerForm { - community_id: c.id, - person_id: person.id, - pending: false, - }; + let form = CommunityFollowerForm::new(c.id, person.id); CommunityFollower::unfollow(&mut context.pool(), &form).await?; } } diff --git a/crates/apub/src/activities/mod.rs b/crates/apub/src/activities/mod.rs index 21723c3909..ffb6a662e7 100644 --- a/crates/apub/src/activities/mod.rs +++ b/crates/apub/src/activities/mod.rs @@ -30,6 +30,7 @@ use activitypub_federation::{ traits::{ActivityHandler, Actor}, }; use anyhow::anyhow; +use following::send_accept_or_reject_follow; use lemmy_api_common::{ context::LemmyContext, send_activity::{ActivityChannel, SendActivityData}, @@ -40,6 +41,7 @@ use lemmy_db_schema::{ community::Community, }, traits::Crud, + CommunityVisibility, }; use lemmy_db_views_actor::structs::{CommunityPersonBanView, CommunityView}; use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}; @@ -120,6 +122,28 @@ pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> LemmyResult<()> { } } +/// Returns an error if object visibility doesnt match community visibility +/// (ie content in private community must also be private). +pub(crate) fn verify_visibility(to: &[Url], cc: &[Url], community: &Community) -> LemmyResult<()> { + use CommunityVisibility::*; + let object_is_public = [to, cc].iter().any(|set| set.contains(&public())); + match community.visibility { + Public if !object_is_public => Err(FederationError::ObjectIsNotPublic)?, + Private if object_is_public => Err(FederationError::ObjectIsNotPrivate)?, + LocalOnly => Err(LemmyErrorType::NotFound.into()), + _ => Ok(()), + } +} + +/// Marks object as public only if the community is public +pub(crate) fn generate_to(community: &Community) -> LemmyResult { + if community.visibility == CommunityVisibility::Public { + Ok(public()) + } else { + Ok(Url::parse(&format!("{}/followers", community.actor_id))?) + } +} + pub(crate) fn verify_community_matches(a: &ObjectId, b: T) -> LemmyResult<()> where T: Into>, @@ -367,6 +391,12 @@ pub async fn match_outgoing_activities( community, reason, } => Report::send(ObjectId::from(object_id), actor, community, reason, context).await, + AcceptFollower(community_id, person_id) => { + send_accept_or_reject_follow(community_id, person_id, true, &context).await + } + RejectFollower(community_id, person_id) => { + send_accept_or_reject_follow(community_id, person_id, false, &context).await + } } }; fed_task.await?; diff --git a/crates/apub/src/activity_lists.rs b/crates/apub/src/activity_lists.rs index 9262236d8a..7ed1d8baf4 100644 --- a/crates/apub/src/activity_lists.rs +++ b/crates/apub/src/activity_lists.rs @@ -17,7 +17,12 @@ use crate::{ page::CreateOrUpdatePage, }, deletion::{delete::Delete, undo_delete::UndoDelete}, - following::{accept::AcceptFollow, follow::Follow, undo_follow::UndoFollow}, + following::{ + accept::AcceptFollow, + follow::Follow, + reject::RejectFollow, + undo_follow::UndoFollow, + }, voting::{undo_vote::UndoVote, vote::Vote}, }, objects::page::Page, @@ -26,7 +31,7 @@ use crate::{ }; use activitypub_federation::{config::Data, traits::ActivityHandler}; use lemmy_api_common::context::LemmyContext; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use serde::{Deserialize, Serialize}; use url::Url; @@ -41,6 +46,7 @@ use url::Url; pub enum SharedInboxActivities { Follow(Follow), AcceptFollow(AcceptFollow), + RejectFollow(RejectFollow), UndoFollow(UndoFollow), CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage), Report(Report), @@ -68,6 +74,7 @@ pub enum GroupInboxActivities { pub enum PersonInboxActivities { Follow(Follow), AcceptFollow(AcceptFollow), + RejectFollow(RejectFollow), UndoFollow(UndoFollow), CreateOrUpdatePrivateMessage(CreateOrUpdateChatMessage), Delete(Delete), diff --git a/crates/apub/src/api/list_posts.rs b/crates/apub/src/api/list_posts.rs index d75a82d3bc..cdf24dbaa0 100644 --- a/crates/apub/src/api/list_posts.rs +++ b/crates/apub/src/api/list_posts.rs @@ -42,6 +42,7 @@ pub async fn list_posts( let show_hidden = data.show_hidden; let show_read = data.show_read; let show_nsfw = data.show_nsfw; + let no_comments_only = data.no_comments_only; let liked_only = data.liked_only; let disliked_only = data.disliked_only; @@ -82,6 +83,7 @@ pub async fn list_posts( show_hidden, show_read, show_nsfw, + no_comments_only, ..Default::default() } .list(&local_site.site, &mut context.pool()) diff --git a/crates/apub/src/api/resolve_object.rs b/crates/apub/src/api/resolve_object.rs index d9d50e69e8..04d4895920 100644 --- a/crates/apub/src/api/resolve_object.rs +++ b/crates/apub/src/api/resolve_object.rs @@ -86,7 +86,7 @@ mod tests { traits::Crud, }; use lemmy_db_views::structs::LocalUserView; - use lemmy_utils::{error::LemmyResult, LemmyErrorType}; + use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use serial_test::serial; #[tokio::test] diff --git a/crates/apub/src/api/user_settings_backup.rs b/crates/apub/src/api/user_settings_backup.rs index 2e075c2027..601ba8664a 100644 --- a/crates/apub/src/api/user_settings_backup.rs +++ b/crates/apub/src/api/user_settings_backup.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::{ newtypes::DbUrl, source::{ comment::{CommentSaved, CommentSavedForm}, - community::{CommunityFollower, CommunityFollowerForm}, + community::{CommunityFollower, CommunityFollowerForm, CommunityFollowerState}, community_block::{CommunityBlock, CommunityBlockForm}, instance::Instance, instance_block::{InstanceBlock, InstanceBlockForm}, @@ -186,9 +186,8 @@ pub async fn import_settings( |(followed, context)| async move { let community = followed.dereference(&context).await?; let form = CommunityFollowerForm { - person_id, - community_id: community.id, - pending: true, + state: Some(CommunityFollowerState::Pending), + ..CommunityFollowerForm::new(community.id, person_id) }; CommunityFollower::follow(&mut context.pool(), &form).await?; LemmyResult::Ok(()) @@ -319,7 +318,13 @@ pub(crate) mod tests { use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ source::{ - community::{Community, CommunityFollower, CommunityFollowerForm, CommunityInsertForm}, + community::{ + Community, + CommunityFollower, + CommunityFollowerForm, + CommunityFollowerState, + CommunityInsertForm, + }, local_user::LocalUser, }, traits::{Crud, Followable}, @@ -327,7 +332,6 @@ pub(crate) mod tests { use lemmy_db_views::structs::LocalUserView; use lemmy_db_views_actor::structs::CommunityFollowerView; use lemmy_utils::error::{LemmyErrorType, LemmyResult}; - use pretty_assertions::assert_eq; use serial_test::serial; use std::time::Duration; use tokio::time::sleep; @@ -348,9 +352,8 @@ pub(crate) mod tests { ); let community = Community::create(pool, &community_form).await?; let follower_form = CommunityFollowerForm { - community_id: community.id, - person_id: export_user.person.id, - pending: false, + state: Some(CommunityFollowerState::Accepted), + ..CommunityFollowerForm::new(community.id, export_user.person.id) }; CommunityFollower::follow(pool, &follower_form).await?; diff --git a/crates/apub/src/fetcher/site_or_community_or_user.rs b/crates/apub/src/fetcher/site_or_community_or_user.rs index c6a1bb17ef..79d7978ae0 100644 --- a/crates/apub/src/fetcher/site_or_community_or_user.rs +++ b/crates/apub/src/fetcher/site_or_community_or_user.rs @@ -9,6 +9,7 @@ use activitypub_federation::{ }; use chrono::{DateTime, Utc}; use lemmy_api_common::context::LemmyContext; +use lemmy_db_schema::newtypes::InstanceId; use lemmy_utils::error::{LemmyError, LemmyResult}; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -127,3 +128,13 @@ impl Actor for SiteOrCommunityOrUser { } } } + +impl SiteOrCommunityOrUser { + pub fn instance_id(&self) -> InstanceId { + match self { + SiteOrCommunityOrUser::Site(s) => s.instance_id, + SiteOrCommunityOrUser::UserOrCommunity(UserOrCommunity::User(u)) => u.instance_id, + SiteOrCommunityOrUser::UserOrCommunity(UserOrCommunity::Community(c)) => c.instance_id, + } + } +} diff --git a/crates/apub/src/http/comment.rs b/crates/apub/src/http/comment.rs index d6b3c818da..41160234f8 100644 --- a/crates/apub/src/http/comment.rs +++ b/crates/apub/src/http/comment.rs @@ -1,14 +1,10 @@ +use super::check_community_content_fetchable; use crate::{ - http::{ - check_community_public, - create_apub_response, - create_apub_tombstone_response, - redirect_remote_object, - }, + http::{create_apub_response, create_apub_tombstone_response, redirect_remote_object}, objects::comment::ApubComment, }; use activitypub_federation::{config::Data, traits::Object}; -use actix_web::{web::Path, HttpResponse}; +use actix_web::{web::Path, HttpRequest, HttpResponse}; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ newtypes::CommentId, @@ -28,13 +24,14 @@ pub(crate) struct CommentQuery { pub(crate) async fn get_apub_comment( info: Path, context: Data, + request: HttpRequest, ) -> LemmyResult { let id = CommentId(info.comment_id.parse::()?); // Can't use CommentView here because it excludes deleted/removed/local-only items let comment: ApubComment = Comment::read(&mut context.pool(), id).await?.into(); let post = Post::read(&mut context.pool(), comment.post_id).await?; let community = Community::read(&mut context.pool(), post.community_id).await?; - check_community_public(&community)?; + check_community_content_fetchable(&community, &request, &context).await?; if !comment.local { Ok(redirect_remote_object(&comment.ap_id)) diff --git a/crates/apub/src/http/community.rs b/crates/apub/src/http/community.rs index 37482aedbd..96a917d91b 100644 --- a/crates/apub/src/http/community.rs +++ b/crates/apub/src/http/community.rs @@ -1,3 +1,4 @@ +use super::check_community_content_fetchable; use crate::{ collections::{ community_featured::ApubCommunityFeatured, @@ -5,17 +6,17 @@ use crate::{ community_moderators::ApubCommunityModerators, community_outbox::ApubCommunityOutbox, }, - http::{check_community_public, create_apub_response, create_apub_tombstone_response}, + http::{check_community_fetchable, create_apub_response, create_apub_tombstone_response}, objects::community::ApubCommunity, }; use activitypub_federation::{ config::Data, traits::{Collection, Object}, }; -use actix_web::{web, HttpResponse}; +use actix_web::{web, HttpRequest, HttpResponse}; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{source::community::Community, traits::ApubActor}; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use serde::Deserialize; #[derive(Deserialize, Clone)] @@ -38,7 +39,7 @@ pub(crate) async fn get_apub_community_http( if community.deleted || community.removed { return create_apub_tombstone_response(community.actor_id.clone()); } - check_community_public(&community)?; + check_community_fetchable(&community)?; let apub = community.into_json(&context).await?; create_apub_response(&apub) @@ -52,7 +53,7 @@ pub(crate) async fn get_apub_community_followers( let community = Community::read_from_name(&mut context.pool(), &info.community_name, false) .await? .ok_or(LemmyErrorType::NotFound)?; - check_community_public(&community)?; + check_community_fetchable(&community)?; let followers = ApubCommunityFollower::read_local(&community.into(), &context).await?; create_apub_response(&followers) } @@ -62,13 +63,14 @@ pub(crate) async fn get_apub_community_followers( pub(crate) async fn get_apub_community_outbox( info: web::Path, context: Data, + request: HttpRequest, ) -> LemmyResult { let community: ApubCommunity = Community::read_from_name(&mut context.pool(), &info.community_name, false) .await? .ok_or(LemmyErrorType::NotFound)? .into(); - check_community_public(&community)?; + check_community_content_fetchable(&community, &request, &context).await?; let outbox = ApubCommunityOutbox::read_local(&community, &context).await?; create_apub_response(&outbox) } @@ -83,7 +85,7 @@ pub(crate) async fn get_apub_community_moderators( .await? .ok_or(LemmyErrorType::NotFound)? .into(); - check_community_public(&community)?; + check_community_fetchable(&community)?; let moderators = ApubCommunityModerators::read_local(&community, &context).await?; create_apub_response(&moderators) } @@ -92,13 +94,14 @@ pub(crate) async fn get_apub_community_moderators( pub(crate) async fn get_apub_community_featured( info: web::Path, context: Data, + request: HttpRequest, ) -> LemmyResult { let community: ApubCommunity = Community::read_from_name(&mut context.pool(), &info.community_name, false) .await? .ok_or(LemmyErrorType::NotFound)? .into(); - check_community_public(&community)?; + check_community_content_fetchable(&community, &request, &context).await?; let featured = ApubCommunityFeatured::read_local(&community, &context).await?; create_apub_response(&featured) } @@ -108,7 +111,7 @@ pub(crate) mod tests { use super::*; use crate::protocol::objects::{group::Group, tombstone::Tombstone}; - use actix_web::body::to_bytes; + use actix_web::{body::to_bytes, test::TestRequest}; use lemmy_db_schema::{ newtypes::InstanceId, source::{ @@ -175,6 +178,7 @@ pub(crate) mod tests { async fn test_get_community() -> LemmyResult<()> { let context = LemmyContext::init_test_context().await; let (instance, community) = init(false, CommunityVisibility::Public, &context).await?; + let request = TestRequest::default().to_http_request(); // fetch invalid community let query = CommunityQuery { @@ -194,8 +198,12 @@ pub(crate) mod tests { let group = community.clone().into_json(&context).await?; assert_eq!(group, res_group); - let res = - get_apub_community_featured(query.clone().into(), context.reset_request_count()).await?; + let res = get_apub_community_featured( + query.clone().into(), + context.reset_request_count(), + request.clone(), + ) + .await?; assert_eq!(200, res.status()); let res = get_apub_community_followers(query.clone().into(), context.reset_request_count()).await?; @@ -203,7 +211,8 @@ pub(crate) mod tests { let res = get_apub_community_moderators(query.clone().into(), context.reset_request_count()).await?; assert_eq!(200, res.status()); - let res = get_apub_community_outbox(query.into(), context.reset_request_count()).await?; + let res = + get_apub_community_outbox(query.into(), context.reset_request_count(), request).await?; assert_eq!(200, res.status()); Instance::delete(&mut context.pool(), instance.id).await?; @@ -215,6 +224,7 @@ pub(crate) mod tests { async fn test_get_deleted_community() -> LemmyResult<()> { let context = LemmyContext::init_test_context().await; let (instance, community) = init(true, CommunityVisibility::LocalOnly, &context).await?; + let request = TestRequest::default().to_http_request(); // should return tombstone let query = CommunityQuery { @@ -225,8 +235,12 @@ pub(crate) mod tests { let res_tombstone = decode_response::(res).await; assert!(res_tombstone.is_ok()); - let res = - get_apub_community_featured(query.clone().into(), context.reset_request_count()).await; + let res = get_apub_community_featured( + query.clone().into(), + context.reset_request_count(), + request.clone(), + ) + .await; assert!(res.is_err()); let res = get_apub_community_followers(query.clone().into(), context.reset_request_count()).await; @@ -234,7 +248,7 @@ pub(crate) mod tests { let res = get_apub_community_moderators(query.clone().into(), context.reset_request_count()).await; assert!(res.is_err()); - let res = get_apub_community_outbox(query.into(), context.reset_request_count()).await; + let res = get_apub_community_outbox(query.into(), context.reset_request_count(), request).await; assert!(res.is_err()); //Community::delete(&mut context.pool(), community.id).await?; @@ -247,14 +261,19 @@ pub(crate) mod tests { async fn test_get_local_only_community() -> LemmyResult<()> { let context = LemmyContext::init_test_context().await; let (instance, community) = init(false, CommunityVisibility::LocalOnly, &context).await?; + let request = TestRequest::default().to_http_request(); let query = CommunityQuery { community_name: community.name.clone(), }; let res = get_apub_community_http(query.clone().into(), context.reset_request_count()).await; assert!(res.is_err()); - let res = - get_apub_community_featured(query.clone().into(), context.reset_request_count()).await; + let res = get_apub_community_featured( + query.clone().into(), + context.reset_request_count(), + request.clone(), + ) + .await; assert!(res.is_err()); let res = get_apub_community_followers(query.clone().into(), context.reset_request_count()).await; @@ -262,7 +281,7 @@ pub(crate) mod tests { let res = get_apub_community_moderators(query.clone().into(), context.reset_request_count()).await; assert!(res.is_err()); - let res = get_apub_community_outbox(query.into(), context.reset_request_count()).await; + let res = get_apub_community_outbox(query.into(), context.reset_request_count(), request).await; assert!(res.is_err()); Instance::delete(&mut context.pool(), instance.id).await?; diff --git a/crates/apub/src/http/mod.rs b/crates/apub/src/http/mod.rs index bc148eb9c7..d79cd3d555 100644 --- a/crates/apub/src/http/mod.rs +++ b/crates/apub/src/http/mod.rs @@ -1,11 +1,11 @@ use crate::{ activity_lists::SharedInboxActivities, - fetcher::user_or_community::UserOrCommunity, + fetcher::{site_or_community_or_user::SiteOrCommunityOrUser, user_or_community::UserOrCommunity}, protocol::objects::tombstone::Tombstone, FEDERATION_CONTEXT, }; use activitypub_federation::{ - actix_web::inbox::receive_activity, + actix_web::{inbox::receive_activity, signing_actor}, config::Data, protocol::context::WithContext, FEDERATION_CONTENT_TYPE, @@ -17,6 +17,7 @@ use lemmy_db_schema::{ source::{activity::SentActivity, community::Community}, CommunityVisibility, }; +use lemmy_db_views_actor::structs::CommunityFollowerView; use lemmy_utils::error::{FederationError, LemmyErrorType, LemmyResult}; use serde::{Deserialize, Serialize}; use std::{ops::Deref, time::Duration}; @@ -119,12 +120,46 @@ pub(crate) async fn get_activity( } /// Ensure that the community is public and not removed/deleted. -fn check_community_public(community: &Community) -> LemmyResult<()> { +fn check_community_fetchable(community: &Community) -> LemmyResult<()> { + check_community_removed_or_deleted(community)?; + if community.visibility == CommunityVisibility::LocalOnly { + return Err(LemmyErrorType::NotFound.into()); + } + Ok(()) +} + +/// Check if posts or comments in the community are allowed to be fetched +async fn check_community_content_fetchable( + community: &Community, + request: &HttpRequest, + context: &Data, +) -> LemmyResult<()> { + use CommunityVisibility::*; + check_community_removed_or_deleted(community)?; + match community.visibility { + // content in public community can always be fetched + Public => Ok(()), + // no federation for local only community + LocalOnly => Err(LemmyErrorType::NotFound.into()), + // for private community check http signature of request, if there is any approved follower + // from the fetching instance then fetching is allowed + Private => { + let signing_actor = signing_actor::(request, None, context).await?; + Ok( + CommunityFollowerView::check_has_followers_from_instance( + community.id, + signing_actor.instance_id(), + &mut context.pool(), + ) + .await?, + ) + } + } +} + +fn check_community_removed_or_deleted(community: &Community) -> LemmyResult<()> { if community.deleted || community.removed { Err(LemmyErrorType::Deleted)? } - if community.visibility != CommunityVisibility::Public { - return Err(LemmyErrorType::NotFound.into()); - } Ok(()) } diff --git a/crates/apub/src/http/person.rs b/crates/apub/src/http/person.rs index 0f628c4972..f8afceb94b 100644 --- a/crates/apub/src/http/person.rs +++ b/crates/apub/src/http/person.rs @@ -7,7 +7,7 @@ use activitypub_federation::{config::Data, traits::Object}; use actix_web::{web, HttpResponse}; use lemmy_api_common::{context::LemmyContext, utils::generate_outbox_url}; use lemmy_db_schema::{source::person::Person, traits::ApubActor}; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use serde::Deserialize; #[derive(Deserialize)] diff --git a/crates/apub/src/http/post.rs b/crates/apub/src/http/post.rs index ce66128262..6afb9fc3e0 100644 --- a/crates/apub/src/http/post.rs +++ b/crates/apub/src/http/post.rs @@ -1,14 +1,10 @@ +use super::check_community_content_fetchable; use crate::{ - http::{ - check_community_public, - create_apub_response, - create_apub_tombstone_response, - redirect_remote_object, - }, + http::{create_apub_response, create_apub_tombstone_response, redirect_remote_object}, objects::post::ApubPost, }; use activitypub_federation::{config::Data, traits::Object}; -use actix_web::{web, HttpResponse}; +use actix_web::{web, HttpRequest, HttpResponse}; use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{ newtypes::PostId, @@ -28,12 +24,14 @@ pub(crate) struct PostQuery { pub(crate) async fn get_apub_post( info: web::Path, context: Data, + request: HttpRequest, ) -> LemmyResult { let id = PostId(info.post_id.parse::()?); // Can't use PostView here because it excludes deleted/removed/local-only items let post: ApubPost = Post::read(&mut context.pool(), id).await?.into(); let community = Community::read(&mut context.pool(), post.community_id).await?; - check_community_public(&community)?; + + check_community_content_fetchable(&community, &request, &context).await?; if !post.local { Ok(redirect_remote_object(&post.ap_id)) diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs index a04aec6552..e11475d6ce 100644 --- a/crates/apub/src/lib.rs +++ b/crates/apub/src/lib.rs @@ -54,15 +54,24 @@ impl UrlVerifier for VerifyUrlData { use FederationError::*; check_apub_id_valid(url, &local_site_data).map_err(|err| match err { LemmyError { - error_type: LemmyErrorType::FederationError(Some(FederationDisabled)), + error_type: + LemmyErrorType::FederationError { + error: Some(FederationDisabled), + }, .. } => ActivityPubError::Other("Federation disabled".into()), LemmyError { - error_type: LemmyErrorType::FederationError(Some(DomainBlocked(domain))), + error_type: + LemmyErrorType::FederationError { + error: Some(DomainBlocked(domain)), + }, .. } => ActivityPubError::Other(format!("Domain {domain:?} is blocked")), LemmyError { - error_type: LemmyErrorType::FederationError(Some(DomainNotInAllowList(domain))), + error_type: + LemmyErrorType::FederationError { + error: Some(DomainNotInAllowList(domain)), + }, .. } => ActivityPubError::Other(format!("Domain {domain:?} is not in allowlist")), _ => ActivityPubError::Other("Failed validating apub id".into()), diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index 403ecbf949..b7c6a5f51e 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -1,9 +1,9 @@ use crate::{ - activities::{verify_is_public, verify_person_in_community}, + activities::{generate_to, verify_person_in_community, verify_visibility}, check_apub_id_valid_with_strictness, fetcher::markdown_links::markdown_rewrite_remote_links, mentions::collect_non_local_mentions, - objects::{read_from_string_or_source, verify_is_remote_object}, + objects::{append_attachments_to_comment, read_from_string_or_source, verify_is_remote_object}, protocol::{ objects::{note::Note, LanguageTag}, InCommunity, @@ -12,7 +12,7 @@ use crate::{ }; use activitypub_federation::{ config::Data, - kinds::{object::NoteType, public}, + kinds::object::NoteType, protocol::{values::MediaTypeMarkdownOrHtml, verification::verify_domains_match}, traits::Object, }; @@ -112,7 +112,7 @@ impl Object for ApubComment { r#type: NoteType::Note, id: self.ap_id.clone().into(), attributed_to: creator.actor_id.into(), - to: vec![public()], + to: vec![generate_to(&community)?], cc: maa.ccs, content: markdown_to_html(&self.content), media_type: Some(MediaTypeMarkdownOrHtml::Html), @@ -124,6 +124,7 @@ impl Object for ApubComment { distinguished: Some(self.distinguished), language, audience: Some(community.actor_id.into()), + attachment: vec![], }; Ok(note) @@ -139,8 +140,8 @@ impl Object for ApubComment { ) -> LemmyResult<()> { verify_domains_match(note.id.inner(), expected_domain)?; verify_domains_match(note.attributed_to.inner(), note.id.inner())?; - verify_is_public(¬e.to, ¬e.cc)?; let community = Box::pin(note.community(context)).await?; + verify_visibility(¬e.to, ¬e.cc, &community)?; Box::pin(check_apub_id_valid_with_strictness( note.id.inner(), @@ -181,6 +182,7 @@ impl Object for ApubComment { let local_site = LocalSite::read(&mut context.pool()).await.ok(); let slur_regex = &local_site_opt_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(context).await?; + let content = append_attachments_to_comment(content, ¬e.attachment, context).await?; let content = process_markdown(&content, slur_regex, &url_blocklist, context).await?; let content = markdown_rewrite_remote_links(content, context).await; let language_id = Some( @@ -247,13 +249,13 @@ pub(crate) mod tests { } async fn cleanup( - data: (ApubPerson, ApubCommunity, ApubPost, ApubSite), + (person, community, post, site): (ApubPerson, ApubCommunity, ApubPost, ApubSite), context: &LemmyContext, ) -> LemmyResult<()> { - Post::delete(&mut context.pool(), data.2.id).await?; - Community::delete(&mut context.pool(), data.1.id).await?; - Person::delete(&mut context.pool(), data.0.id).await?; - Site::delete(&mut context.pool(), data.3.id).await?; + Post::delete(&mut context.pool(), post.id).await?; + Community::delete(&mut context.pool(), community.id).await?; + Person::delete(&mut context.pool(), person.id).await?; + Site::delete(&mut context.pool(), site.id).await?; LocalSite::delete(&mut context.pool()).await?; Ok(()) } diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 7ee204ac93..efa2c52472 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -39,6 +39,7 @@ use lemmy_db_schema::{ }, traits::{ApubActor, Crud}, utils::naive_now, + CommunityVisibility, }; use lemmy_db_views_actor::structs::CommunityFollowerView; use lemmy_utils::{ @@ -126,6 +127,7 @@ impl Object for ApubCommunity { updated: self.updated, posting_restricted_to_mods: Some(self.posting_restricted_to_mods), attributed_to: Some(generate_moderators_url(&self.actor_id)?.into()), + manually_approves_followers: Some(self.visibility == CommunityVisibility::Private), }; Ok(group) } @@ -152,7 +154,11 @@ impl Object for ApubCommunity { let sidebar = markdown_rewrite_remote_links_opt(sidebar, context).await; let icon = proxy_image_link_opt_apub(group.icon.map(|i| i.url), context).await?; let banner = proxy_image_link_opt_apub(group.image.map(|i| i.url), context).await?; - + let visibility = Some(if group.manually_approves_followers.unwrap_or_default() { + CommunityVisibility::Private + } else { + CommunityVisibility::Public + }); let form = CommunityInsertForm { published: group.published, updated: group.updated, @@ -176,6 +182,7 @@ impl Object for ApubCommunity { moderators_url: group.attributed_to.clone().map(Into::into), posting_restricted_to_mods: group.posting_restricted_to_mods, featured_url: group.featured.clone().map(Into::into), + visibility, ..CommunityInsertForm::new( instance_id, group.preferred_username.clone(), diff --git a/crates/apub/src/objects/mod.rs b/crates/apub/src/objects/mod.rs index e199ebfada..f837f7ad35 100644 --- a/crates/apub/src/objects/mod.rs +++ b/crates/apub/src/objects/mod.rs @@ -1,4 +1,4 @@ -use crate::protocol::Source; +use crate::protocol::{objects::page::Attachment, Source}; use activitypub_federation::{ config::Data, fetch::object_id::ObjectId, @@ -46,6 +46,23 @@ pub(crate) fn read_from_string_or_source_opt( .map(|content| read_from_string_or_source(content, media_type, source)) } +pub(crate) async fn append_attachments_to_comment( + content: String, + attachments: &[Attachment], + context: &Data, +) -> LemmyResult { + let mut content = content; + // Don't modify comments with no attachments + if !attachments.is_empty() { + content += "\n"; + for attachment in attachments { + content = content + "\n" + &attachment.as_markdown(context).await?; + } + } + + Ok(content) +} + /// When for example a Post is made in a remote community, the community will send it back, /// wrapped in Announce. If we simply receive this like any other federated object, overwrite the /// existing, local Post. In particular, it will set the field local = false, so that the object diff --git a/crates/apub/src/objects/person.rs b/crates/apub/src/objects/person.rs index 4e8519f784..737579662c 100644 --- a/crates/apub/src/objects/person.rs +++ b/crates/apub/src/objects/person.rs @@ -285,9 +285,12 @@ pub(crate) mod tests { Ok(()) } - async fn cleanup(data: (ApubPerson, ApubSite), context: &LemmyContext) -> LemmyResult<()> { - DbPerson::delete(&mut context.pool(), data.0.id).await?; - Site::delete(&mut context.pool(), data.1.id).await?; + async fn cleanup( + (person, site): (ApubPerson, ApubSite), + context: &LemmyContext, + ) -> LemmyResult<()> { + DbPerson::delete(&mut context.pool(), person.id).await?; + Site::delete(&mut context.pool(), site.id).await?; Ok(()) } } diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index ee88cf3ec7..b72fa17289 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -1,5 +1,5 @@ use crate::{ - activities::{verify_is_public, verify_person_in_community}, + activities::{generate_to, verify_person_in_community, verify_visibility}, check_apub_id_valid_with_strictness, fetcher::markdown_links::{markdown_rewrite_remote_links_opt, to_local_url}, local_site_data_cached, @@ -16,7 +16,6 @@ use crate::{ }; use activitypub_federation::{ config::Data, - kinds::public, protocol::{values::MediaTypeMarkdownOrHtml, verification::verify_domains_match}, traits::Object, }; @@ -135,7 +134,7 @@ impl Object for ApubPost { kind: PageType::Page, id: self.ap_id.clone().into(), attributed_to: AttributedTo::Lemmy(creator.actor_id.into()), - to: vec![community.actor_id.clone().into(), public()], + to: vec![generate_to(&community)?], cc: vec![], name: Some(self.name.clone()), content: self.body.as_ref().map(|b| markdown_to_html(b)), @@ -172,7 +171,7 @@ impl Object for ApubPost { check_slurs_opt(&page.name, slur_regex)?; verify_domains_match(page.creator()?.inner(), page.id.inner())?; - verify_is_public(&page.to, &page.cc)?; + verify_visibility(&page.to, &page.cc, &community)?; Ok(()) } diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 5a191cc662..f3a9f140c7 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -16,7 +16,12 @@ use activitypub_federation::{ use chrono::{DateTime, Utc}; use lemmy_api_common::{ context::LemmyContext, - utils::{get_url_blocklist, local_site_opt_to_slur_regex, process_markdown}, + utils::{ + check_private_messages_enabled, + get_url_blocklist, + local_site_opt_to_slur_regex, + process_markdown, + }, }; use lemmy_db_schema::{ source::{ @@ -28,6 +33,7 @@ use lemmy_db_schema::{ traits::Crud, utils::naive_now, }; +use lemmy_db_views::structs::LocalUserView; use lemmy_utils::{ error::{FederationError, LemmyError, LemmyErrorType, LemmyResult}, utils::markdown::markdown_to_html, @@ -130,9 +136,16 @@ impl Object for ApubPrivateMessage { let recipient = note.to[0].dereference(context).await?; PersonBlock::read(&mut context.pool(), recipient.id, creator.id).await?; + // Check that they can receive private messages + if let Ok(recipient_local_user) = + LocalUserView::read_person(&mut context.pool(), recipient.id).await + { + check_private_messages_enabled(&recipient_local_user)?; + } let local_site = LocalSite::read(&mut context.pool()).await.ok(); let slur_regex = &local_site_opt_to_slur_regex(&local_site); let url_blocklist = get_url_blocklist(context).await?; + let content = read_from_string_or_source(¬e.content, &None, ¬e.source); let content = process_markdown(&content, slur_regex, &url_blocklist, context).await?; let content = markdown_rewrite_remote_links(content, context).await; @@ -186,12 +199,12 @@ mod tests { } async fn cleanup( - data: (ApubPerson, ApubPerson, ApubSite), + (person1, person2, site): (ApubPerson, ApubPerson, ApubSite), context: &Data, ) -> LemmyResult<()> { - Person::delete(&mut context.pool(), data.0.id).await?; - Person::delete(&mut context.pool(), data.1.id).await?; - Site::delete(&mut context.pool(), data.2.id).await?; + Person::delete(&mut context.pool(), person1.id).await?; + Person::delete(&mut context.pool(), person2.id).await?; + Site::delete(&mut context.pool(), site.id).await?; Ok(()) } diff --git a/crates/apub/src/protocol/activities/following/mod.rs b/crates/apub/src/protocol/activities/following/mod.rs index ec263adae2..1bb805608a 100644 --- a/crates/apub/src/protocol/activities/following/mod.rs +++ b/crates/apub/src/protocol/activities/following/mod.rs @@ -1,5 +1,6 @@ pub(crate) mod accept; pub mod follow; +pub(crate) mod reject; pub mod undo_follow; #[cfg(test)] diff --git a/crates/apub/src/protocol/activities/following/reject.rs b/crates/apub/src/protocol/activities/following/reject.rs new file mode 100644 index 0000000000..1584dfb11b --- /dev/null +++ b/crates/apub/src/protocol/activities/following/reject.rs @@ -0,0 +1,24 @@ +use crate::{ + objects::{community::ApubCommunity, person::ApubPerson}, + protocol::activities::following::follow::Follow, +}; +use activitypub_federation::{ + fetch::object_id::ObjectId, + kinds::activity::RejectType, + protocol::helpers::deserialize_skip_error, +}; +use serde::{Deserialize, Serialize}; +use url::Url; + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RejectFollow { + pub(crate) actor: ObjectId, + /// Optional, for compatibility with platforms that always expect recipient field + #[serde(deserialize_with = "deserialize_skip_error", default)] + pub(crate) to: Option<[ObjectId; 1]>, + pub(crate) object: Follow, + #[serde(rename = "type")] + pub(crate) kind: RejectType, + pub(crate) id: Url, +} diff --git a/crates/apub/src/protocol/objects/group.rs b/crates/apub/src/protocol/objects/group.rs index affafe2690..dbf4af8921 100644 --- a/crates/apub/src/protocol/objects/group.rs +++ b/crates/apub/src/protocol/objects/group.rs @@ -73,6 +73,8 @@ pub struct Group { pub(crate) featured: Option>, #[serde(default)] pub(crate) language: Vec, + /// True if this is a private community + pub(crate) manually_approves_followers: Option, pub(crate) published: Option>, pub(crate) updated: Option>, } diff --git a/crates/apub/src/protocol/objects/mod.rs b/crates/apub/src/protocol/objects/mod.rs index dbba1bb8af..00fe26d2b7 100644 --- a/crates/apub/src/protocol/objects/mod.rs +++ b/crates/apub/src/protocol/objects/mod.rs @@ -145,7 +145,8 @@ mod tests { #[test] fn test_parse_objects_mastodon() -> LemmyResult<()> { test_json::("assets/mastodon/objects/person.json")?; - test_json::("assets/mastodon/objects/note.json")?; + test_json::("assets/mastodon/objects/note_1.json")?; + test_json::("assets/mastodon/objects/note_2.json")?; test_json::("assets/mastodon/objects/page.json")?; Ok(()) } diff --git a/crates/apub/src/protocol/objects/note.rs b/crates/apub/src/protocol/objects/note.rs index e3e2042540..fc38b9b5ed 100644 --- a/crates/apub/src/protocol/objects/note.rs +++ b/crates/apub/src/protocol/objects/note.rs @@ -3,7 +3,11 @@ use crate::{ fetcher::post_or_comment::PostOrComment, mentions::MentionOrValue, objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost}, - protocol::{objects::LanguageTag, InCommunity, Source}, + protocol::{ + objects::{page::Attachment, LanguageTag}, + InCommunity, + Source, + }, }; use activitypub_federation::{ config::Data, @@ -20,7 +24,10 @@ use lemmy_db_schema::{ source::{community::Community, post::Post}, traits::Crud, }; -use lemmy_utils::{error::LemmyResult, LemmyErrorType, MAX_COMMENT_DEPTH_LIMIT}; +use lemmy_utils::{ + error::{LemmyErrorType, LemmyResult}, + MAX_COMMENT_DEPTH_LIMIT, +}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; use url::Url; @@ -50,6 +57,8 @@ pub struct Note { pub(crate) distinguished: Option, pub(crate) language: Option, pub(crate) audience: Option>, + #[serde(default)] + pub(crate) attachment: Vec, } impl Note { diff --git a/crates/apub/src/protocol/objects/page.rs b/crates/apub/src/protocol/objects/page.rs index 97f767573a..3ce720bc0b 100644 --- a/crates/apub/src/protocol/objects/page.rs +++ b/crates/apub/src/protocol/objects/page.rs @@ -19,7 +19,7 @@ use activitypub_federation::{ }; use chrono::{DateTime, Utc}; use itertools::Itertools; -use lemmy_api_common::context::LemmyContext; +use lemmy_api_common::{context::LemmyContext, utils::proxy_image_link}; use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorType, LemmyResult}; use serde::{de::Error, Deserialize, Deserializer, Serialize}; use serde_with::skip_serializing_none; @@ -93,6 +93,7 @@ pub(crate) struct Document { #[serde(rename = "type")] kind: DocumentType, url: Url, + media_type: Option, /// Used for alt_text name: Option, } @@ -124,6 +125,24 @@ impl Attachment { _ => None, } } + + pub(crate) async fn as_markdown(&self, context: &Data) -> LemmyResult { + let (url, name, media_type) = match self { + Attachment::Image(i) => (i.url.clone(), i.name.clone(), Some(String::from("image"))), + Attachment::Document(d) => (d.url.clone(), d.name.clone(), d.media_type.clone()), + Attachment::Link(l) => (l.href.clone(), None, l.media_type.clone()), + }; + + let is_image = + media_type.is_some_and(|media| media.starts_with("video") || media.starts_with("image")); + + if is_image { + let url = proxy_image_link(url, context).await?; + Ok(format!("![{}]({url})", name.unwrap_or_default())) + } else { + Ok(format!("[{url}]({url})")) + } + } } #[derive(Clone, Debug, Deserialize, Serialize)] diff --git a/crates/db_perf/src/main.rs b/crates/db_perf/src/main.rs index 0fa5c05497..02796a906c 100644 --- a/crates/db_perf/src/main.rs +++ b/crates/db_perf/src/main.rs @@ -54,7 +54,7 @@ async fn main() -> anyhow::Result<()> { async fn try_main() -> LemmyResult<()> { let args = CmdArgs::parse(); - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let conn = &mut get_conn(pool).await?; diff --git a/crates/db_schema/src/aggregates/comment_aggregates.rs b/crates/db_schema/src/aggregates/comment_aggregates.rs index a97bb565b8..b26d277363 100644 --- a/crates/db_schema/src/aggregates/comment_aggregates.rs +++ b/crates/db_schema/src/aggregates/comment_aggregates.rs @@ -51,7 +51,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_schema/src/aggregates/community_aggregates.rs b/crates/db_schema/src/aggregates/community_aggregates.rs index 0359d8632f..3ec56d73d6 100644 --- a/crates/db_schema/src/aggregates/community_aggregates.rs +++ b/crates/db_schema/src/aggregates/community_aggregates.rs @@ -37,7 +37,13 @@ mod tests { aggregates::community_aggregates::CommunityAggregates, source::{ comment::{Comment, CommentInsertForm}, - community::{Community, CommunityFollower, CommunityFollowerForm, CommunityInsertForm}, + community::{ + Community, + CommunityFollower, + CommunityFollowerForm, + CommunityFollowerState, + CommunityInsertForm, + }, instance::Instance, person::{Person, PersonInsertForm}, post::{Post, PostInsertForm}, @@ -52,7 +58,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; @@ -84,7 +90,8 @@ mod tests { let first_person_follow = CommunityFollowerForm { community_id: inserted_community.id, person_id: inserted_person.id, - pending: false, + state: Some(CommunityFollowerState::Accepted), + approver_id: None, }; CommunityFollower::follow(pool, &first_person_follow).await?; @@ -92,7 +99,8 @@ mod tests { let second_person_follow = CommunityFollowerForm { community_id: inserted_community.id, person_id: another_inserted_person.id, - pending: false, + state: Some(CommunityFollowerState::Accepted), + approver_id: None, }; CommunityFollower::follow(pool, &second_person_follow).await?; @@ -100,7 +108,8 @@ mod tests { let another_community_follow = CommunityFollowerForm { community_id: another_inserted_community.id, person_id: inserted_person.id, - pending: false, + state: Some(CommunityFollowerState::Accepted), + approver_id: None, }; CommunityFollower::follow(pool, &another_community_follow).await?; diff --git a/crates/db_schema/src/aggregates/person_aggregates.rs b/crates/db_schema/src/aggregates/person_aggregates.rs index 6e0eacc078..62aa9b609c 100644 --- a/crates/db_schema/src/aggregates/person_aggregates.rs +++ b/crates/db_schema/src/aggregates/person_aggregates.rs @@ -36,7 +36,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_schema/src/aggregates/post_aggregates.rs b/crates/db_schema/src/aggregates/post_aggregates.rs index b63017317b..46747b0769 100644 --- a/crates/db_schema/src/aggregates/post_aggregates.rs +++ b/crates/db_schema/src/aggregates/post_aggregates.rs @@ -70,7 +70,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; @@ -182,7 +182,7 @@ mod tests { #[tokio::test] #[serial] async fn test_soft_delete() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_schema/src/aggregates/site_aggregates.rs b/crates/db_schema/src/aggregates/site_aggregates.rs index 379ddd2d90..2df5662907 100644 --- a/crates/db_schema/src/aggregates/site_aggregates.rs +++ b/crates/db_schema/src/aggregates/site_aggregates.rs @@ -65,7 +65,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let (inserted_instance, inserted_person, inserted_site, inserted_community) = @@ -136,7 +136,7 @@ mod tests { #[tokio::test] #[serial] async fn test_soft_delete() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let (inserted_instance, inserted_person, inserted_site, inserted_community) = diff --git a/crates/db_schema/src/impls/activity.rs b/crates/db_schema/src/impls/activity.rs index fff0c2f0c1..d2cc6dcef8 100644 --- a/crates/db_schema/src/impls/activity.rs +++ b/crates/db_schema/src/impls/activity.rs @@ -71,7 +71,7 @@ mod tests { #[tokio::test] #[serial] async fn receive_activity_duplicate() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let ap_id: DbUrl = Url::parse("http://example.com/activity/531")?.into(); @@ -86,7 +86,7 @@ mod tests { #[tokio::test] #[serial] async fn sent_activity_write_read() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let ap_id: DbUrl = Url::parse("http://example.com/activity/412")?.into(); let data = json!({ diff --git a/crates/db_schema/src/impls/actor_language.rs b/crates/db_schema/src/impls/actor_language.rs index bff729f41c..b4ad0d347a 100644 --- a/crates/db_schema/src/impls/actor_language.rs +++ b/crates/db_schema/src/impls/actor_language.rs @@ -438,7 +438,7 @@ mod tests { #[tokio::test] #[serial] async fn test_convert_update_languages() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); // call with empty vec, returns all languages @@ -457,7 +457,7 @@ mod tests { #[serial] async fn test_convert_read_languages() -> Result<(), Error> { use crate::schema::language::dsl::{id, language}; - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); // call with all languages, returns empty vec @@ -477,7 +477,7 @@ mod tests { #[tokio::test] #[serial] async fn test_site_languages() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let (site, instance) = create_test_site(pool).await?; @@ -502,7 +502,7 @@ mod tests { #[tokio::test] #[serial] async fn test_user_languages() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let (site, instance) = create_test_site(pool).await?; @@ -535,7 +535,7 @@ mod tests { #[tokio::test] #[serial] async fn test_community_languages() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let (site, instance) = create_test_site(pool).await?; let test_langs = test_langs1(pool).await?; @@ -591,7 +591,7 @@ mod tests { #[tokio::test] #[serial] async fn test_default_post_language() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let (site, instance) = create_test_site(pool).await?; let test_langs = test_langs1(pool).await?; diff --git a/crates/db_schema/src/impls/captcha_answer.rs b/crates/db_schema/src/impls/captcha_answer.rs index d7183e4fbd..8be8fc5deb 100644 --- a/crates/db_schema/src/impls/captcha_answer.rs +++ b/crates/db_schema/src/impls/captcha_answer.rs @@ -13,7 +13,7 @@ use diesel::{ QueryDsl, }; use diesel_async::RunQueryDsl; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; impl CaptchaAnswer { pub async fn insert(pool: &mut DbPool<'_>, captcha: &CaptchaAnswerForm) -> Result { @@ -62,7 +62,7 @@ mod tests { #[tokio::test] #[serial] async fn test_captcha_happy_path() { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted = CaptchaAnswer::insert( @@ -89,7 +89,7 @@ mod tests { #[tokio::test] #[serial] async fn test_captcha_repeat_answer_fails() { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted = CaptchaAnswer::insert( diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index 11792e4283..96ec70fa29 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -251,7 +251,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 218d82eccb..03cc12558a 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -8,6 +8,7 @@ use crate::{ Community, CommunityFollower, CommunityFollowerForm, + CommunityFollowerState, CommunityInsertForm, CommunityModerator, CommunityModeratorForm, @@ -342,22 +343,8 @@ impl Bannable for CommunityPersonBan { } impl CommunityFollower { - pub fn to_subscribed_type(follower: &Option) -> SubscribedType { - match follower { - Some(f) => { - if f.pending { - SubscribedType::Pending - } else { - SubscribedType::Subscribed - } - } - // If the row doesn't exist, the person isn't a follower. - None => SubscribedType::NotSubscribed, - } - } - - pub fn select_subscribed_type() -> dsl::Nullable { - community_actions::follow_pending.nullable() + pub fn select_subscribed_type() -> dsl::Nullable { + community_actions::follow_state.nullable() } /// Check if a remote instance has any followers on local instance. For this it is enough to check @@ -376,14 +363,37 @@ impl CommunityFollower { .then_some(()) .ok_or(LemmyErrorType::CommunityHasNoFollowers.into()) } + + pub async fn approve( + pool: &mut DbPool<'_>, + community_id: CommunityId, + follower_id: PersonId, + approver_id: PersonId, + ) -> LemmyResult<()> { + let conn = &mut get_conn(pool).await?; + diesel::update(find_action( + community_actions::followed, + (follower_id, community_id), + )) + .set(( + community_actions::follow_state.eq(CommunityFollowerState::Accepted), + community_actions::follow_approver_id.eq(approver_id), + )) + .execute(conn) + .await?; + Ok(()) + } } -impl Queryable, Pg> for SubscribedType { - type Row = Option; +impl Queryable, Pg> + for SubscribedType +{ + type Row = Option; fn build(row: Self::Row) -> deserialize::Result { Ok(match row { - Some(true) => SubscribedType::Pending, - Some(false) => SubscribedType::Subscribed, + Some(CommunityFollowerState::Pending) => SubscribedType::Pending, + Some(CommunityFollowerState::Accepted) => SubscribedType::Subscribed, + Some(CommunityFollowerState::ApprovalRequired) => SubscribedType::ApprovalRequired, None => SubscribedType::NotSubscribed, }) } @@ -414,10 +424,10 @@ impl Followable for CommunityFollower { ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(find_action( - community_actions::follow_pending, + community_actions::follow_state, (person_id, community_id), )) - .set(community_actions::follow_pending.eq(Some(false))) + .set(community_actions::follow_state.eq(Some(CommunityFollowerState::Accepted))) .returning(Self::as_select()) .get_result::(conn) .await @@ -429,7 +439,8 @@ impl Followable for CommunityFollower { let conn = &mut get_conn(pool).await?; uplete::new(community_actions::table.find((form.person_id, form.community_id))) .set_null(community_actions::followed) - .set_null(community_actions::follow_pending) + .set_null(community_actions::follow_state) + .set_null(community_actions::follow_approver_id) .get_result(conn) .await } @@ -492,6 +503,7 @@ mod tests { Community, CommunityFollower, CommunityFollowerForm, + CommunityFollowerState, CommunityInsertForm, CommunityModerator, CommunityModeratorForm, @@ -514,7 +526,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; @@ -564,7 +576,8 @@ mod tests { let community_follower_form = CommunityFollowerForm { community_id: inserted_community.id, person_id: inserted_bobby.id, - pending: false, + state: Some(CommunityFollowerState::Accepted), + approver_id: None, }; let inserted_community_follower = @@ -573,8 +586,9 @@ mod tests { let expected_community_follower = CommunityFollower { community_id: inserted_community.id, person_id: inserted_bobby.id, - pending: false, + state: CommunityFollowerState::Accepted, published: inserted_community_follower.published, + approver_id: None, }; let bobby_moderator_form = CommunityModeratorForm { diff --git a/crates/db_schema/src/impls/community_block.rs b/crates/db_schema/src/impls/community_block.rs index 4f4c426bcb..c520e43e80 100644 --- a/crates/db_schema/src/impls/community_block.rs +++ b/crates/db_schema/src/impls/community_block.rs @@ -18,7 +18,7 @@ use diesel::{ QueryDsl, }; use diesel_async::RunQueryDsl; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; impl CommunityBlock { pub async fn read( diff --git a/crates/db_schema/src/impls/federation_allowlist.rs b/crates/db_schema/src/impls/federation_allowlist.rs index cbfd14b033..099e0b2317 100644 --- a/crates/db_schema/src/impls/federation_allowlist.rs +++ b/crates/db_schema/src/impls/federation_allowlist.rs @@ -61,7 +61,7 @@ mod tests { #[tokio::test] #[serial] async fn test_allowlist_insert_and_clear() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let domains = vec![ "tld1.xyz".to_string(), diff --git a/crates/db_schema/src/impls/instance_block.rs b/crates/db_schema/src/impls/instance_block.rs index 8602a3c639..1722e8318a 100644 --- a/crates/db_schema/src/impls/instance_block.rs +++ b/crates/db_schema/src/impls/instance_block.rs @@ -18,7 +18,7 @@ use diesel::{ QueryDsl, }; use diesel_async::RunQueryDsl; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; impl InstanceBlock { pub async fn read( diff --git a/crates/db_schema/src/impls/language.rs b/crates/db_schema/src/impls/language.rs index 57420fcd4b..3b8bc1d20c 100644 --- a/crates/db_schema/src/impls/language.rs +++ b/crates/db_schema/src/impls/language.rs @@ -46,7 +46,7 @@ mod tests { #[tokio::test] #[serial] async fn test_languages() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let all = Language::read_all(pool).await?; diff --git a/crates/db_schema/src/impls/local_user.rs b/crates/db_schema/src/impls/local_user.rs index d5e6d05973..3b695a97ed 100644 --- a/crates/db_schema/src/impls/local_user.rs +++ b/crates/db_schema/src/impls/local_user.rs @@ -331,6 +331,7 @@ impl LocalUserOptionHelper for Option<&LocalUser> { .unwrap_or(site.content_warning.is_some()) } + // TODO: use this function for private community checks, but the generics get extremely confusing fn visible_communities_only(&self, query: Q) -> Q where Q: diesel::query_dsl::methods::FilterDsl< @@ -385,7 +386,7 @@ mod tests { #[tokio::test] #[serial] async fn test_admin_higher_check() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; @@ -424,7 +425,7 @@ mod tests { #[tokio::test] #[serial] async fn test_email_taken() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let darwin_email = "charles.darwin@gmail.com"; diff --git a/crates/db_schema/src/impls/login_token.rs b/crates/db_schema/src/impls/login_token.rs index c8c44c506e..f4f7a7aae2 100644 --- a/crates/db_schema/src/impls/login_token.rs +++ b/crates/db_schema/src/impls/login_token.rs @@ -7,7 +7,7 @@ use crate::{ }; use diesel::{delete, dsl::exists, insert_into, result::Error, select}; use diesel_async::RunQueryDsl; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; impl LoginToken { pub async fn create(pool: &mut DbPool<'_>, form: LoginTokenCreateForm) -> Result { diff --git a/crates/db_schema/src/impls/moderator.rs b/crates/db_schema/src/impls/moderator.rs index b2ef26e694..8deb562585 100644 --- a/crates/db_schema/src/impls/moderator.rs +++ b/crates/db_schema/src/impls/moderator.rs @@ -533,7 +533,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_schema/src/impls/password_reset_request.rs b/crates/db_schema/src/impls/password_reset_request.rs index 015db55814..a9ac3a9c28 100644 --- a/crates/db_schema/src/impls/password_reset_request.rs +++ b/crates/db_schema/src/impls/password_reset_request.rs @@ -61,7 +61,7 @@ mod tests { #[tokio::test] #[serial] async fn test_password_reset() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); // Setup diff --git a/crates/db_schema/src/impls/person.rs b/crates/db_schema/src/impls/person.rs index 2cc8701461..3ae355b87d 100644 --- a/crates/db_schema/src/impls/person.rs +++ b/crates/db_schema/src/impls/person.rs @@ -23,7 +23,7 @@ use diesel::{ QueryDsl, }; use diesel_async::RunQueryDsl; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; #[async_trait] impl Crud for Person { @@ -261,7 +261,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; @@ -315,7 +315,7 @@ mod tests { #[tokio::test] #[serial] async fn follow() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_schema/src/impls/person_block.rs b/crates/db_schema/src/impls/person_block.rs index 0813714417..363a2d3d1a 100644 --- a/crates/db_schema/src/impls/person_block.rs +++ b/crates/db_schema/src/impls/person_block.rs @@ -19,7 +19,7 @@ use diesel::{ QueryDsl, }; use diesel_async::RunQueryDsl; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; impl PersonBlock { pub async fn read( diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 9f65f59770..5be9d7aae4 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -452,7 +452,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_schema/src/impls/post_report.rs b/crates/db_schema/src/impls/post_report.rs index 5507423e14..e7d27aee9b 100644 --- a/crates/db_schema/src/impls/post_report.rs +++ b/crates/db_schema/src/impls/post_report.rs @@ -126,7 +126,7 @@ mod tests { #[tokio::test] #[serial] async fn test_resolve_post_report() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let (person, report) = init(pool).await?; @@ -146,7 +146,7 @@ mod tests { #[tokio::test] #[serial] async fn test_resolve_all_post_reports() -> Result<(), Error> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let (person, report) = init(pool).await?; diff --git a/crates/db_schema/src/impls/private_message.rs b/crates/db_schema/src/impls/private_message.rs index 264175fe28..e08b4cf7ff 100644 --- a/crates/db_schema/src/impls/private_message.rs +++ b/crates/db_schema/src/impls/private_message.rs @@ -104,7 +104,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_schema/src/impls/site.rs b/crates/db_schema/src/impls/site.rs index 8f57647a31..e993639fad 100644 --- a/crates/db_schema/src/impls/site.rs +++ b/crates/db_schema/src/impls/site.rs @@ -10,7 +10,7 @@ use crate::{ }; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, OptionalExtension, QueryDsl}; use diesel_async::RunQueryDsl; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; use url::Url; #[async_trait] diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index 6d4c073fd5..7ee60cc1e0 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -191,6 +191,7 @@ pub enum SubscribedType { Subscribed, NotSubscribed, Pending, + ApprovalRequired, } #[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] @@ -241,14 +242,14 @@ pub enum PostFeatureType { #[cfg_attr(feature = "full", DbValueStyle = "verbatim")] #[cfg_attr(feature = "full", ts(export))] /// Defines who can browse and interact with content in a community. -/// -/// TODO: Also use this to define private communities pub enum CommunityVisibility { /// Public community, any local or federated user can interact. #[default] Public, /// Unfederated community, only local users can interact. LocalOnly, + /// Users need to be approved by mods before they are able to browse or post. + Private, } #[derive( diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs index fe1febef59..c28be8222d 100644 --- a/crates/db_schema/src/newtypes.rs +++ b/crates/db_schema/src/newtypes.rs @@ -174,8 +174,9 @@ pub struct LtreeDef(pub String); #[repr(transparent)] #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug, Hash)] -#[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow))] +#[cfg_attr(feature = "full", derive(AsExpression, FromSqlRow, TS))] #[cfg_attr(feature = "full", diesel(sql_type = diesel::sql_types::Text))] +#[cfg_attr(feature = "full", ts(export))] pub struct DbUrl(pub(crate) Box); impl DbUrl { @@ -248,19 +249,6 @@ impl Deref for DbUrl { } } -#[cfg(feature = "full")] -impl TS for DbUrl { - fn name() -> String { - "string".to_string() - } - fn dependencies() -> Vec { - Vec::new() - } - fn transparent() -> bool { - true - } -} - #[cfg(feature = "full")] impl ToSql for DbUrl { fn to_sql(&self, out: &mut Output) -> diesel::serialize::Result { diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 27fbf08232..1d6f3f1d06 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -1,39 +1,43 @@ // @generated automatically by Diesel CLI. pub mod sql_types { - #[derive(diesel::sql_types::SqlType)] + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "actor_type_enum"))] pub struct ActorTypeEnum; - #[derive(diesel::sql_types::SqlType)] + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "comment_sort_type_enum"))] pub struct CommentSortTypeEnum; - #[derive(diesel::sql_types::SqlType)] + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "community_follower_state"))] + pub struct CommunityFollowerState; + + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "community_visibility"))] pub struct CommunityVisibility; - #[derive(diesel::sql_types::SqlType)] + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "federation_mode_enum"))] pub struct FederationModeEnum; - #[derive(diesel::sql_types::SqlType)] + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "listing_type_enum"))] pub struct ListingTypeEnum; - #[derive(diesel::sql_types::SqlType)] + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "ltree"))] pub struct Ltree; - #[derive(diesel::sql_types::SqlType)] + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "post_listing_mode_enum"))] pub struct PostListingModeEnum; - #[derive(diesel::sql_types::SqlType)] + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "post_sort_type_enum"))] pub struct PostSortTypeEnum; - #[derive(diesel::sql_types::SqlType)] + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "registration_mode_enum"))] pub struct RegistrationModeEnum; } @@ -195,11 +199,15 @@ diesel::table! { } diesel::table! { + use diesel::sql_types::*; + use super::sql_types::CommunityFollowerState; + community_actions (person_id, community_id) { community_id -> Int4, person_id -> Int4, followed -> Nullable, - follow_pending -> Nullable, + follow_state -> Nullable, + follow_approver_id -> Nullable, blocked -> Nullable, became_moderator -> Nullable, received_ban -> Nullable, @@ -444,6 +452,7 @@ diesel::table! { totp_2fa_enabled -> Bool, enable_keyboard_navigation -> Bool, enable_animated_images -> Bool, + enable_private_messages -> Bool, collapse_bot_comments -> Bool, default_comment_sort_type -> CommentSortTypeEnum, } @@ -938,7 +947,6 @@ diesel::joinable!(comment_reply -> person (recipient_id)); diesel::joinable!(comment_report -> comment (comment_id)); diesel::joinable!(community -> instance (instance_id)); diesel::joinable!(community_actions -> community (community_id)); -diesel::joinable!(community_actions -> person (person_id)); diesel::joinable!(community_aggregates -> community (community_id)); diesel::joinable!(community_language -> community (community_id)); diesel::joinable!(community_language -> language (language_id)); diff --git a/crates/db_schema/src/sensitive.rs b/crates/db_schema/src/sensitive.rs index 340679e2fb..5d1d449fb3 100644 --- a/crates/db_schema/src/sensitive.rs +++ b/crates/db_schema/src/sensitive.rs @@ -4,8 +4,9 @@ use std::{fmt::Debug, ops::Deref}; use ts_rs::TS; #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize, Default)] -#[cfg_attr(feature = "full", derive(DieselNewType))] +#[cfg_attr(feature = "full", derive(DieselNewType, TS))] #[serde(transparent)] +#[cfg_attr(feature = "full", ts(export))] pub struct SensitiveString(String); impl SensitiveString { @@ -39,19 +40,3 @@ impl From for SensitiveString { SensitiveString(t) } } - -#[cfg(feature = "full")] -impl TS for SensitiveString { - fn name() -> String { - "string".to_string() - } - fn name_with_type_args(_args: Vec) -> String { - "string".to_string() - } - fn dependencies() -> Vec { - Vec::new() - } - fn transparent() -> bool { - true - } -} diff --git a/crates/db_schema/src/source/comment.rs b/crates/db_schema/src/source/comment.rs index 5b7ceecd44..be9aa7873d 100644 --- a/crates/db_schema/src/source/comment.rs +++ b/crates/db_schema/src/source/comment.rs @@ -32,6 +32,7 @@ pub struct Comment { /// Whether the comment has been removed. pub removed: bool, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, /// Whether the comment has been deleted by its creator. pub deleted: bool, diff --git a/crates/db_schema/src/source/comment_report.rs b/crates/db_schema/src/source/comment_report.rs index 73dadc945d..a19b6925a8 100644 --- a/crates/db_schema/src/source/comment_report.rs +++ b/crates/db_schema/src/source/comment_report.rs @@ -25,8 +25,10 @@ pub struct CommentReport { pub original_comment_text: String, pub reason: String, pub resolved: bool, + #[cfg_attr(feature = "full", ts(optional))] pub resolver_id: Option, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, } diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index 0495f31b78..f65ef06f94 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -11,6 +11,7 @@ use chrono::{DateTime, Utc}; use diesel::{dsl, expression_methods::NullableExpressionMethods}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use strum::{Display, EnumString}; #[cfg(feature = "full")] use ts_rs::TS; @@ -27,10 +28,12 @@ pub struct Community { /// A longer title, that can contain other characters, and doesn't have to be unique. pub title: String, /// A sidebar for the community in markdown. + #[cfg_attr(feature = "full", ts(optional))] pub sidebar: Option, /// Whether the community is removed by a mod. pub removed: bool, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, /// Whether the community has been deleted by its creator. pub deleted: bool, @@ -47,8 +50,10 @@ pub struct Community { #[serde(skip)] pub last_refreshed_at: DateTime, /// A URL for an icon. + #[cfg_attr(feature = "full", ts(optional))] pub icon: Option, /// A URL for a banner. + #[cfg_attr(feature = "full", ts(optional))] pub banner: Option, #[cfg_attr(feature = "full", ts(skip))] #[serde(skip)] @@ -69,6 +74,7 @@ pub struct Community { pub featured_url: Option, pub visibility: CommunityVisibility, /// A shorter, one-line description of the site. + #[cfg_attr(feature = "full", ts(optional))] pub description: Option, } @@ -210,6 +216,20 @@ pub struct CommunityPersonBanForm { pub expires: Option>>, } +#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(DbEnum, TS))] +#[cfg_attr( + feature = "full", + ExistingTypePath = "crate::schema::sql_types::CommunityFollowerState" +)] +#[cfg_attr(feature = "full", DbValueStyle = "verbatim")] +#[cfg_attr(feature = "full", ts(export))] +pub enum CommunityFollowerState { + Accepted, + Pending, + ApprovalRequired, +} + #[derive(PartialEq, Eq, Debug)] #[cfg_attr( feature = "full", @@ -228,17 +248,23 @@ pub struct CommunityFollower { #[cfg_attr(feature = "full", diesel(select_expression = community_actions::followed.assume_not_null()))] #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] pub published: DateTime, - #[cfg_attr(feature = "full", diesel(select_expression = community_actions::follow_pending.assume_not_null()))] - #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] - pub pending: bool, + #[cfg_attr(feature = "full", diesel(select_expression = community_actions::follow_state.assume_not_null()))] + #[cfg_attr(feature = "full", diesel(select_expression_type = dsl::AssumeNotNull))] + pub state: CommunityFollowerState, + #[cfg_attr(feature = "full", diesel(column_name = follow_approver_id))] + pub approver_id: Option, } -#[derive(Clone)] +#[derive(Clone, derive_new::new)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = community_actions))] pub struct CommunityFollowerForm { pub community_id: CommunityId, pub person_id: PersonId, - #[cfg_attr(feature = "full", diesel(column_name = follow_pending))] - pub pending: bool, + #[new(default)] + #[cfg_attr(feature = "full", diesel(column_name = follow_state))] + pub state: Option, + #[new(default)] + #[cfg_attr(feature = "full", diesel(column_name = follow_approver_id))] + pub approver_id: Option, } diff --git a/crates/db_schema/src/source/custom_emoji.rs b/crates/db_schema/src/source/custom_emoji.rs index f5a92ea46e..bb95cb7c87 100644 --- a/crates/db_schema/src/source/custom_emoji.rs +++ b/crates/db_schema/src/source/custom_emoji.rs @@ -21,6 +21,7 @@ pub struct CustomEmoji { pub alt_text: String, pub category: String, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, } diff --git a/crates/db_schema/src/source/federation_queue_state.rs b/crates/db_schema/src/source/federation_queue_state.rs index 134dfe452d..27e464d1f5 100644 --- a/crates/db_schema/src/source/federation_queue_state.rs +++ b/crates/db_schema/src/source/federation_queue_state.rs @@ -19,10 +19,13 @@ use ts_rs::TS; pub struct FederationQueueState { pub instance_id: InstanceId, /// the last successfully sent activity id + #[cfg_attr(feature = "full", ts(optional))] pub last_successful_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub last_successful_published_time: Option>, /// how many failed attempts have been made to send the next activity pub fail_count: i32, /// timestamp of the last retry attempt (when the last failing activity was resent) + #[cfg_attr(feature = "full", ts(optional))] pub last_retry: Option>, } diff --git a/crates/db_schema/src/source/images.rs b/crates/db_schema/src/source/images.rs index 22f5e6eb4b..acd339d8ea 100644 --- a/crates/db_schema/src/source/images.rs +++ b/crates/db_schema/src/source/images.rs @@ -23,6 +23,7 @@ use ts_rs::TS; #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(primary_key(pictrs_alias)))] pub struct LocalImage { + #[cfg_attr(feature = "full", ts(optional))] pub local_user_id: Option, pub pictrs_alias: String, pub pictrs_delete_token: String, diff --git a/crates/db_schema/src/source/instance.rs b/crates/db_schema/src/source/instance.rs index 8c27a2cb69..f622751cc8 100644 --- a/crates/db_schema/src/source/instance.rs +++ b/crates/db_schema/src/source/instance.rs @@ -19,8 +19,11 @@ pub struct Instance { pub id: InstanceId, pub domain: String, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, + #[cfg_attr(feature = "full", ts(optional))] pub software: Option, + #[cfg_attr(feature = "full", ts(optional))] pub version: Option, } diff --git a/crates/db_schema/src/source/local_site.rs b/crates/db_schema/src/source/local_site.rs index 5fa57fe3b6..b5bcebc587 100644 --- a/crates/db_schema/src/source/local_site.rs +++ b/crates/db_schema/src/source/local_site.rs @@ -33,6 +33,7 @@ pub struct LocalSite { /// Whether emails are required. pub require_email_verification: bool, /// An optional registration application questionnaire in markdown. + #[cfg_attr(feature = "full", ts(optional))] pub application_question: Option, /// Whether the instance is private or public. pub private_instance: bool, @@ -40,12 +41,14 @@ pub struct LocalSite { pub default_theme: String, pub default_post_listing_type: ListingType, /// An optional legal disclaimer page. + #[cfg_attr(feature = "full", ts(optional))] pub legal_information: Option, /// Whether to hide mod names on the modlog. pub hide_modlog_mod_names: bool, /// Whether new applications email admins. pub application_email_admins: bool, /// An optional regex to filter words. + #[cfg_attr(feature = "full", ts(optional))] pub slur_filter_regex: Option, /// The max actor name length. pub actor_name_max_length: i32, @@ -56,6 +59,7 @@ pub struct LocalSite { /// The captcha difficulty. pub captcha_difficulty: String, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, pub registration_mode: RegistrationMode, /// Whether to email admins on new reports. diff --git a/crates/db_schema/src/source/local_site_rate_limit.rs b/crates/db_schema/src/source/local_site_rate_limit.rs index f7f25f5c1a..af424a248f 100644 --- a/crates/db_schema/src/source/local_site_rate_limit.rs +++ b/crates/db_schema/src/source/local_site_rate_limit.rs @@ -34,6 +34,7 @@ pub struct LocalSiteRateLimit { pub search: i32, pub search_per_second: i32, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, pub import_user_settings: i32, pub import_user_settings_per_second: i32, diff --git a/crates/db_schema/src/source/local_site_url_blocklist.rs b/crates/db_schema/src/source/local_site_url_blocklist.rs index 4ac0893ecc..d6127a78a3 100644 --- a/crates/db_schema/src/source/local_site_url_blocklist.rs +++ b/crates/db_schema/src/source/local_site_url_blocklist.rs @@ -16,6 +16,7 @@ pub struct LocalSiteUrlBlocklist { pub id: i32, pub url: String, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, } diff --git a/crates/db_schema/src/source/local_user.rs b/crates/db_schema/src/source/local_user.rs index 37da709085..fd15253cc9 100644 --- a/crates/db_schema/src/source/local_user.rs +++ b/crates/db_schema/src/source/local_user.rs @@ -27,6 +27,7 @@ pub struct LocalUser { pub person_id: PersonId, #[serde(skip)] pub password_encrypted: Option, + #[cfg_attr(feature = "full", ts(optional))] pub email: Option, /// Whether to show NSFW content. pub show_nsfw: bool, @@ -62,6 +63,8 @@ pub struct LocalUser { /// Whether user avatars and inline images in the UI that are gifs should be allowed to play or /// should be paused pub enable_animated_images: bool, + /// Whether a user can send / receive private messages + pub enable_private_messages: bool, /// Whether to auto-collapse bot comments. pub collapse_bot_comments: bool, pub default_comment_sort_type: CommentSortType, @@ -116,6 +119,8 @@ pub struct LocalUserInsertForm { #[new(default)] pub enable_animated_images: Option, #[new(default)] + pub enable_private_messages: Option, + #[new(default)] pub collapse_bot_comments: Option, #[new(default)] pub default_comment_sort_type: Option, @@ -147,6 +152,7 @@ pub struct LocalUserUpdateForm { pub totp_2fa_enabled: Option, pub enable_keyboard_navigation: Option, pub enable_animated_images: Option, + pub enable_private_messages: Option, pub collapse_bot_comments: Option, pub default_comment_sort_type: Option, } diff --git a/crates/db_schema/src/source/login_token.rs b/crates/db_schema/src/source/login_token.rs index 38aac33efb..20d81afb09 100644 --- a/crates/db_schema/src/source/login_token.rs +++ b/crates/db_schema/src/source/login_token.rs @@ -24,7 +24,9 @@ pub struct LoginToken { pub published: DateTime, /// IP address where login was made from, allows invalidating logins by IP address. /// Could be stored in truncated format, or store derived information for better privacy. + #[cfg_attr(feature = "full", ts(optional))] pub ip: Option, + #[cfg_attr(feature = "full", ts(optional))] pub user_agent: Option, } diff --git a/crates/db_schema/src/source/moderator.rs b/crates/db_schema/src/source/moderator.rs index c1f58ebc84..b4fdcc6767 100644 --- a/crates/db_schema/src/source/moderator.rs +++ b/crates/db_schema/src/source/moderator.rs @@ -34,6 +34,7 @@ pub struct ModRemovePost { pub id: i32, pub mod_person_id: PersonId, pub post_id: PostId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, pub removed: bool, pub when_: DateTime, @@ -105,6 +106,7 @@ pub struct ModRemoveComment { pub id: i32, pub mod_person_id: PersonId, pub comment_id: CommentId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, pub removed: bool, pub when_: DateTime, @@ -130,6 +132,7 @@ pub struct ModRemoveCommunity { pub id: i32, pub mod_person_id: PersonId, pub community_id: CommunityId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, pub removed: bool, pub when_: DateTime, @@ -156,8 +159,10 @@ pub struct ModBanFromCommunity { pub mod_person_id: PersonId, pub other_person_id: PersonId, pub community_id: CommunityId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, pub banned: bool, + #[cfg_attr(feature = "full", ts(optional))] pub expires: Option>, pub when_: DateTime, } @@ -184,8 +189,10 @@ pub struct ModBan { pub id: i32, pub mod_person_id: PersonId, pub other_person_id: PersonId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, pub banned: bool, + #[cfg_attr(feature = "full", ts(optional))] pub expires: Option>, pub when_: DateTime, } @@ -211,6 +218,7 @@ pub struct ModHideCommunity { pub community_id: CommunityId, pub mod_person_id: PersonId, pub when_: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, pub hidden: bool, } @@ -303,6 +311,7 @@ pub struct ModAddForm { pub struct AdminPurgePerson { pub id: i32, pub admin_person_id: PersonId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, pub when_: DateTime, } @@ -324,6 +333,7 @@ pub struct AdminPurgePersonForm { pub struct AdminPurgeCommunity { pub id: i32, pub admin_person_id: PersonId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, pub when_: DateTime, } @@ -346,6 +356,7 @@ pub struct AdminPurgePost { pub id: i32, pub admin_person_id: PersonId, pub community_id: CommunityId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, pub when_: DateTime, } @@ -369,6 +380,7 @@ pub struct AdminPurgeComment { pub id: i32, pub admin_person_id: PersonId, pub post_id: PostId, + #[cfg_attr(feature = "full", ts(optional))] pub reason: Option, pub when_: DateTime, } diff --git a/crates/db_schema/src/source/oauth_account.rs b/crates/db_schema/src/source/oauth_account.rs index 83b578e229..b7d190c35f 100644 --- a/crates/db_schema/src/source/oauth_account.rs +++ b/crates/db_schema/src/source/oauth_account.rs @@ -19,6 +19,7 @@ pub struct OAuthAccount { pub oauth_provider_id: OAuthProviderId, pub oauth_user_id: String, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, } diff --git a/crates/db_schema/src/source/oauth_provider.rs b/crates/db_schema/src/source/oauth_provider.rs index 75b9898058..a70405a5e2 100644 --- a/crates/db_schema/src/source/oauth_provider.rs +++ b/crates/db_schema/src/source/oauth_provider.rs @@ -60,6 +60,7 @@ pub struct OAuthProvider { /// switch to enable or disable an oauth provider pub enabled: bool, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, } diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index b515574ac9..9c2a2d426a 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -24,16 +24,20 @@ pub struct Person { pub id: PersonId, pub name: String, /// A shorter display name. + #[cfg_attr(feature = "full", ts(optional))] pub display_name: Option, /// A URL for an avatar. + #[cfg_attr(feature = "full", ts(optional))] pub avatar: Option, /// Whether the person is banned. pub banned: bool, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, /// The federated actor_id. pub actor_id: DbUrl, /// An optional bio, in markdown. + #[cfg_attr(feature = "full", ts(optional))] pub bio: Option, /// Whether the person is local to our site. pub local: bool, @@ -44,6 +48,7 @@ pub struct Person { #[serde(skip)] pub last_refreshed_at: DateTime, /// A URL for a banner. + #[cfg_attr(feature = "full", ts(optional))] pub banner: Option, /// Whether the person is deleted. pub deleted: bool, @@ -51,10 +56,12 @@ pub struct Person { #[serde(skip, default = "placeholder_apub_url")] pub inbox_url: DbUrl, /// A matrix id, usually given an @person:matrix.org + #[cfg_attr(feature = "full", ts(optional))] pub matrix_user_id: Option, /// Whether the person is a bot account. pub bot_account: bool, /// When their ban, if it exists, expires, if at all. + #[cfg_attr(feature = "full", ts(optional))] pub ban_expires: Option>, pub instance_id: InstanceId, } diff --git a/crates/db_schema/src/source/post.rs b/crates/db_schema/src/source/post.rs index 870d5682a1..bed659a107 100644 --- a/crates/db_schema/src/source/post.rs +++ b/crates/db_schema/src/source/post.rs @@ -19,10 +19,11 @@ use ts_rs::TS; pub struct Post { pub id: PostId, pub name: String, - #[cfg_attr(feature = "full", ts(type = "string"))] /// An optional link / url for the post. + #[cfg_attr(feature = "full", ts(optional))] pub url: Option, /// An optional post body, in markdown. + #[cfg_attr(feature = "full", ts(optional))] pub body: Option, pub creator_id: PersonId, pub community_id: CommunityId, @@ -31,35 +32,40 @@ pub struct Post { /// Whether the post is locked. pub locked: bool, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, /// Whether the post is deleted. pub deleted: bool, /// Whether the post is NSFW. pub nsfw: bool, /// A title for the link. + #[cfg_attr(feature = "full", ts(optional))] pub embed_title: Option, /// A description for the link. + #[cfg_attr(feature = "full", ts(optional))] pub embed_description: Option, - #[cfg_attr(feature = "full", ts(type = "string"))] /// A thumbnail picture url. + #[cfg_attr(feature = "full", ts(optional))] pub thumbnail_url: Option, - #[cfg_attr(feature = "full", ts(type = "string"))] /// The federated activity id / ap_id. pub ap_id: DbUrl, /// Whether the post is local. pub local: bool, - #[cfg_attr(feature = "full", ts(type = "string"))] /// A video url for the link. + #[cfg_attr(feature = "full", ts(optional))] pub embed_video_url: Option, pub language_id: LanguageId, /// Whether the post is featured to its community. pub featured_community: bool, /// Whether the post is featured to its site. pub featured_local: bool, + #[cfg_attr(feature = "full", ts(optional))] pub url_content_type: Option, /// An optional alt_text, usable for image posts. + #[cfg_attr(feature = "full", ts(optional))] pub alt_text: Option, /// Time at which the post will be published. None means publish immediately. + #[cfg_attr(feature = "full", ts(optional))] pub scheduled_publish_time: Option>, } diff --git a/crates/db_schema/src/source/post_report.rs b/crates/db_schema/src/source/post_report.rs index 9aee9ed97f..610e495ae9 100644 --- a/crates/db_schema/src/source/post_report.rs +++ b/crates/db_schema/src/source/post_report.rs @@ -25,13 +25,17 @@ pub struct PostReport { /// The original post title. pub original_post_name: String, /// The original post url. + #[cfg_attr(feature = "full", ts(optional))] pub original_post_url: Option, /// The original post body. + #[cfg_attr(feature = "full", ts(optional))] pub original_post_body: Option, pub reason: String, pub resolved: bool, + #[cfg_attr(feature = "full", ts(optional))] pub resolver_id: Option, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, } diff --git a/crates/db_schema/src/source/private_message.rs b/crates/db_schema/src/source/private_message.rs index 8afaa14f14..f153739075 100644 --- a/crates/db_schema/src/source/private_message.rs +++ b/crates/db_schema/src/source/private_message.rs @@ -29,6 +29,7 @@ pub struct PrivateMessage { pub deleted: bool, pub read: bool, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, pub ap_id: DbUrl, pub local: bool, diff --git a/crates/db_schema/src/source/private_message_report.rs b/crates/db_schema/src/source/private_message_report.rs index 7b4c8c6371..570f555849 100644 --- a/crates/db_schema/src/source/private_message_report.rs +++ b/crates/db_schema/src/source/private_message_report.rs @@ -29,8 +29,10 @@ pub struct PrivateMessageReport { pub original_pm_text: String, pub reason: String, pub resolved: bool, + #[cfg_attr(feature = "full", ts(optional))] pub resolver_id: Option, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, } diff --git a/crates/db_schema/src/source/registration_application.rs b/crates/db_schema/src/source/registration_application.rs index 2ac973f34d..f01c042d98 100644 --- a/crates/db_schema/src/source/registration_application.rs +++ b/crates/db_schema/src/source/registration_application.rs @@ -18,7 +18,9 @@ pub struct RegistrationApplication { pub id: RegistrationApplicationId, pub local_user_id: LocalUserId, pub answer: String, + #[cfg_attr(feature = "full", ts(optional))] pub admin_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub deny_reason: Option, pub published: DateTime, } diff --git a/crates/db_schema/src/source/site.rs b/crates/db_schema/src/source/site.rs index 0ec4043e47..0fe33de01b 100644 --- a/crates/db_schema/src/source/site.rs +++ b/crates/db_schema/src/source/site.rs @@ -21,14 +21,19 @@ pub struct Site { pub id: SiteId, pub name: String, /// A sidebar for the site in markdown. + #[cfg_attr(feature = "full", ts(optional))] pub sidebar: Option, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, /// An icon URL. + #[cfg_attr(feature = "full", ts(optional))] pub icon: Option, /// A banner url. + #[cfg_attr(feature = "full", ts(optional))] pub banner: Option, /// A shorter, one-line description of the site. + #[cfg_attr(feature = "full", ts(optional))] pub description: Option, /// The federated actor_id. pub actor_id: DbUrl, @@ -43,6 +48,7 @@ pub struct Site { pub instance_id: InstanceId, /// If present, nsfw content is visible by default. Should be displayed by frontends/clients /// when the site is first opened by a user. + #[cfg_attr(feature = "full", ts(optional))] pub content_warning: Option, } diff --git a/crates/db_schema/src/source/tagline.rs b/crates/db_schema/src/source/tagline.rs index 05f7e0520b..80c045a0ae 100644 --- a/crates/db_schema/src/source/tagline.rs +++ b/crates/db_schema/src/source/tagline.rs @@ -17,6 +17,7 @@ pub struct Tagline { pub id: i32, pub content: String, pub published: DateTime, + #[cfg_attr(feature = "full", ts(optional))] pub updated: Option>, } diff --git a/crates/db_schema/src/utils.rs b/crates/db_schema/src/utils.rs index 38508f3780..bb7edb13fa 100644 --- a/crates/db_schema/src/utils.rs +++ b/crates/db_schema/src/utils.rs @@ -34,7 +34,6 @@ use diesel_async::{ ManagerConfig, }, AsyncConnection, - RunQueryDsl, }; use diesel_bind_if_some::BindIfSome; use futures_util::{future::BoxFuture, Future, FutureExt}; @@ -60,7 +59,7 @@ use rustls::{ }; use std::{ ops::{Deref, DerefMut}, - sync::{Arc, LazyLock}, + sync::{Arc, LazyLock, OnceLock}, time::Duration, }; use tracing::error; @@ -72,6 +71,8 @@ pub const SITEMAP_LIMIT: i64 = 50000; pub const SITEMAP_DAYS: Option = TimeDelta::try_days(31); pub const RANK_DEFAULT: f64 = 0.0001; +/// Some connection options to speed up queries +const CONNECTION_OPTIONS: [&str; 1] = ["geqo_threshold=12"]; pub type ActualDbPool = Pool; /// References a pool or connection. Functions must take `&mut DbPool<'_>` to allow implicit @@ -358,10 +359,37 @@ pub fn diesel_url_create(opt: Option<&str>) -> LemmyResult> { } } +/// Sets a few additional config options necessary for starting lemmy +fn build_config_options_uri_segment(config: &str) -> String { + let mut url = Url::parse(config).expect("Couldn't parse postgres connection URI"); + + // Set `lemmy.protocol_and_hostname` so triggers can use it + let lemmy_protocol_and_hostname_option = + "lemmy.protocol_and_hostname=".to_owned() + &SETTINGS.get_protocol_and_hostname(); + let mut options = CONNECTION_OPTIONS.to_vec(); + options.push(&lemmy_protocol_and_hostname_option); + + // Create the connection uri portion + let options_segments = options + .iter() + .map(|o| "-c ".to_owned() + o) + .collect::>() + .join(" "); + + url.set_query(Some(&format!("options={options_segments}"))); + url.into() +} + fn establish_connection(config: &str) -> BoxFuture> { let fut = async { + /// Use a once_lock to create the postgres connection config, since this config never changes + static POSTGRES_CONFIG_WITH_OPTIONS: OnceLock = OnceLock::new(); + + let config = + POSTGRES_CONFIG_WITH_OPTIONS.get_or_init(|| build_config_options_uri_segment(config)); + // We only support TLS with sslmode=require currently - let mut conn = if config.contains("sslmode=require") { + let conn = if config.contains("sslmode=require") { let rustls_config = DangerousClientConfigBuilder { cfg: ClientConfig::builder(), } @@ -382,24 +410,6 @@ fn establish_connection(config: &str) -> BoxFuture LemmyResult { +pub fn build_db_pool() -> LemmyResult { let db_url = SETTINGS.get_database_url(); // diesel-async does not support any TLS connections out of the box, so we need to manually // provide a setup function which handles creating the connection @@ -485,8 +495,8 @@ pub async fn build_db_pool() -> LemmyResult { Ok(pool) } -pub async fn build_db_pool_for_tests() -> ActualDbPool { - build_db_pool().await.expect("db pool missing") +pub fn build_db_pool_for_tests() -> ActualDbPool { + build_db_pool().expect("db pool missing") } pub fn naive_now() -> DateTime { @@ -511,7 +521,7 @@ static EMAIL_REGEX: LazyLock = LazyLock::new(|| { }); pub mod functions { - use diesel::sql_types::{BigInt, Bool, Text, Timestamptz}; + use diesel::sql_types::{BigInt, Text, Timestamptz}; sql_function! { #[sql_name = "r.hot_rank"] @@ -534,8 +544,6 @@ pub mod functions { // really this function is variadic, this just adds the two-argument version sql_function!(fn coalesce(x: diesel::sql_types::Nullable, y: T) -> T); - - sql_function!(fn set_config(setting_name: Text, new_value: Text, is_local: Bool) -> Text); } pub const DELETED_REPLACEMENT_TEXT: &str = "*Permanently Deleted*"; diff --git a/crates/db_schema/src/utils/uplete.rs b/crates/db_schema/src/utils/uplete.rs index 857ef66a3f..8c5262b907 100644 --- a/crates/db_schema/src/utils/uplete.rs +++ b/crates/db_schema/src/utils/uplete.rs @@ -291,7 +291,7 @@ mod tests { #[tokio::test] #[serial] async fn test_count() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let mut conn = get_conn(pool).await?; @@ -400,7 +400,7 @@ mod tests { #[tokio::test] #[serial] async fn test_all_null() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let mut conn = get_conn(pool).await?; diff --git a/crates/db_views/src/comment_report_view.rs b/crates/db_views/src/comment_report_view.rs index 0ba51230fa..278dc5c226 100644 --- a/crates/db_views/src/comment_report_view.rs +++ b/crates/db_views/src/comment_report_view.rs @@ -272,7 +272,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_views/src/comment_view.rs b/crates/db_views/src/comment_view.rs index d58ec24eab..22b7b3de43 100644 --- a/crates/db_views/src/comment_view.rs +++ b/crates/db_views/src/comment_view.rs @@ -29,7 +29,11 @@ use lemmy_db_schema::{ person_actions, post, }, - source::{community::CommunityFollower, local_user::LocalUser, site::Site}, + source::{ + community::{CommunityFollower, CommunityFollowerState}, + local_user::LocalUser, + site::Site, + }, utils::{ actions, actions_alias, @@ -42,6 +46,7 @@ use lemmy_db_schema::{ ReadFn, }, CommentSortType, + CommunityVisibility, ListingType, }; @@ -118,6 +123,18 @@ fn queries<'a>() -> Queries< my_local_user.person_id(), ); query = my_local_user.visible_communities_only(query); + + // Check permissions to view private community content. + // Specifically, if the community is private then only accepted followers may view its + // content, otherwise it is filtered out. Admins can view private community content + // without restriction. + if !my_local_user.is_admin() { + query = query.filter( + community::visibility + .ne(CommunityVisibility::Private) + .or(community_actions::follow_state.eq(CommunityFollowerState::Accepted)), + ); + } query.first(&mut conn).await }; @@ -220,6 +237,14 @@ fn queries<'a>() -> Queries< query = options.local_user.visible_communities_only(query); + if !options.local_user.is_admin() { + query = query.filter( + community::visibility + .ne(CommunityVisibility::Private) + .or(community_actions::follow_state.eq(CommunityFollowerState::Accepted)), + ); + } + // A Max depth given means its a tree fetch let (limit, offset) = if let Some(max_depth) = options.max_depth { let depth_limit = if let Some(parent_path) = options.parent_path.as_ref() { @@ -364,6 +389,9 @@ mod tests { }, community::{ Community, + CommunityFollower, + CommunityFollowerForm, + CommunityFollowerState, CommunityInsertForm, CommunityModerator, CommunityModeratorForm, @@ -380,7 +408,7 @@ mod tests { post::{Post, PostInsertForm, PostUpdateForm}, site::{Site, SiteInsertForm}, }, - traits::{Bannable, Blockable, Crud, Joinable, Likeable, Saveable}, + traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable, Saveable}, utils::{build_db_pool_for_tests, RANK_DEFAULT}, CommunityVisibility, SubscribedType, @@ -550,7 +578,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -605,7 +633,7 @@ mod tests { #[tokio::test] #[serial] async fn test_liked_only() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -656,7 +684,7 @@ mod tests { #[tokio::test] #[serial] async fn test_comment_tree() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -729,7 +757,7 @@ mod tests { #[tokio::test] #[serial] async fn test_languages() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -788,7 +816,7 @@ mod tests { #[tokio::test] #[serial] async fn test_distinguished_first() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -813,7 +841,7 @@ mod tests { #[tokio::test] #[serial] async fn test_creator_is_moderator() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -844,7 +872,7 @@ mod tests { #[tokio::test] #[serial] async fn test_creator_is_admin() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -869,7 +897,7 @@ mod tests { #[tokio::test] #[serial] async fn test_saved_order() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1044,7 +1072,7 @@ mod tests { #[tokio::test] #[serial] async fn local_only_instance() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1090,7 +1118,7 @@ mod tests { #[tokio::test] #[serial] async fn comment_listing_local_user_banned_from_community() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1132,7 +1160,7 @@ mod tests { #[tokio::test] #[serial] async fn comment_listing_local_user_not_banned_from_community() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1151,7 +1179,7 @@ mod tests { #[tokio::test] #[serial] async fn comment_listings_hide_nsfw() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1176,4 +1204,96 @@ mod tests { cleanup(data, pool).await } + + #[tokio::test] + #[serial] + async fn comment_listing_private_community() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests(); + let pool = &mut pool.into(); + let mut data = init_data(pool).await?; + + // Mark community as private + Community::update( + pool, + data.inserted_community.id, + &CommunityUpdateForm { + visibility: Some(CommunityVisibility::Private), + ..Default::default() + }, + ) + .await?; + + // No comments returned without auth + let read_comment_listing = CommentQuery::default().list(&data.site, pool).await?; + assert_eq!(0, read_comment_listing.len()); + let comment_view = CommentView::read(pool, data.inserted_comment_0.id, None).await; + assert!(comment_view.is_err()); + + // No comments returned for non-follower who is not admin + data.timmy_local_user_view.local_user.admin = false; + let read_comment_listing = CommentQuery { + community_id: Some(data.inserted_community.id), + local_user: Some(&data.timmy_local_user_view.local_user), + ..Default::default() + } + .list(&data.site, pool) + .await?; + assert_eq!(0, read_comment_listing.len()); + let comment_view = CommentView::read( + pool, + data.inserted_comment_0.id, + Some(&data.timmy_local_user_view.local_user), + ) + .await; + assert!(comment_view.is_err()); + + // Admin can view content without following + data.timmy_local_user_view.local_user.admin = true; + let read_comment_listing = CommentQuery { + community_id: Some(data.inserted_community.id), + local_user: Some(&data.timmy_local_user_view.local_user), + ..Default::default() + } + .list(&data.site, pool) + .await?; + assert_eq!(5, read_comment_listing.len()); + let comment_view = CommentView::read( + pool, + data.inserted_comment_0.id, + Some(&data.timmy_local_user_view.local_user), + ) + .await; + assert!(comment_view.is_ok()); + data.timmy_local_user_view.local_user.admin = false; + + // User can view after following + CommunityFollower::follow( + pool, + &CommunityFollowerForm { + state: Some(CommunityFollowerState::Accepted), + ..CommunityFollowerForm::new( + data.inserted_community.id, + data.timmy_local_user_view.person.id, + ) + }, + ) + .await?; + let read_comment_listing = CommentQuery { + community_id: Some(data.inserted_community.id), + local_user: Some(&data.timmy_local_user_view.local_user), + ..Default::default() + } + .list(&data.site, pool) + .await?; + assert_eq!(5, read_comment_listing.len()); + let comment_view = CommentView::read( + pool, + data.inserted_comment_0.id, + Some(&data.timmy_local_user_view.local_user), + ) + .await; + assert!(comment_view.is_ok()); + + cleanup(data, pool).await + } } diff --git a/crates/db_views/src/custom_emoji_view.rs b/crates/db_views/src/custom_emoji_view.rs index a346c086dd..606e807e9b 100644 --- a/crates/db_views/src/custom_emoji_view.rs +++ b/crates/db_views/src/custom_emoji_view.rs @@ -76,16 +76,16 @@ impl CustomEmojiView { fn from_tuple_to_vec(items: Vec) -> Vec { let mut result = Vec::new(); let mut hash: HashMap> = HashMap::new(); - for item in &items { - let emoji_id: CustomEmojiId = item.0.id; + for (emoji, keyword) in &items { + let emoji_id: CustomEmojiId = emoji.id; if let std::collections::hash_map::Entry::Vacant(e) = hash.entry(emoji_id) { e.insert(Vec::new()); result.push(CustomEmojiView { - custom_emoji: item.0.clone(), + custom_emoji: emoji.clone(), keywords: Vec::new(), }) } - if let Some(item_keyword) = &item.1 { + if let Some(item_keyword) = &keyword { if let Some(keywords) = hash.get_mut(&emoji_id) { keywords.push(item_keyword.clone()) } diff --git a/crates/db_views/src/post_report_view.rs b/crates/db_views/src/post_report_view.rs index 74c00ea863..c6c19bf6f2 100644 --- a/crates/db_views/src/post_report_view.rs +++ b/crates/db_views/src/post_report_view.rs @@ -252,7 +252,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_views/src/post_view.rs b/crates/db_views/src/post_view.rs index dcdae7ec4d..2469422c2f 100644 --- a/crates/db_views/src/post_view.rs +++ b/crates/db_views/src/post_view.rs @@ -33,7 +33,11 @@ use lemmy_db_schema::{ post_actions, post_aggregates, }, - source::{community::CommunityFollower, local_user::LocalUser, site::Site}, + source::{ + community::{CommunityFollower, CommunityFollowerState}, + local_user::LocalUser, + site::Site, + }, utils::{ action_query, actions, @@ -51,6 +55,7 @@ use lemmy_db_schema::{ ReadFn, ReverseTimestampKey, }, + CommunityVisibility, ListingType, PostSortType, }; @@ -171,6 +176,12 @@ fn queries<'a>() -> Queries< post::deleted .eq(false) .or(post::creator_id.eq(person_id_join)), + ) + // private communities can only by browsed by accepted followers + .filter( + community::visibility + .ne(CommunityVisibility::Private) + .or(community_actions::follow_state.eq(CommunityFollowerState::Accepted)), ); } @@ -268,6 +279,11 @@ fn queries<'a>() -> Queries< query = query.filter(person::bot_account.eq(false)); }; + // Filter to show only posts with no comments + if options.no_comments_only.unwrap_or_default() { + query = query.filter(post_aggregates::comments.eq(0)); + }; + // If its saved only, then filter, and order by the saved time, not the comment creation time. if options.saved_only.unwrap_or_default() { query = query @@ -307,6 +323,14 @@ fn queries<'a>() -> Queries< query = options.local_user.visible_communities_only(query); + if !options.local_user.is_admin() { + query = query.filter( + community::visibility + .ne(CommunityVisibility::Private) + .or(community_actions::follow_state.eq(CommunityFollowerState::Accepted)), + ); + } + // Dont filter blocks or missing languages for moderator view type if options.listing_type.unwrap_or_default() != ListingType::ModeratorView { // Filter out the rows with missing languages if user is logged in @@ -473,6 +497,7 @@ pub struct PostQuery<'a> { pub show_hidden: Option, pub show_read: Option, pub show_nsfw: Option, + pub no_comments_only: Option, } impl<'a> PostQuery<'a> { @@ -581,6 +606,7 @@ mod tests { structs::LocalUserView, }; use chrono::Utc; + use diesel_async::SimpleAsyncConnection; use lemmy_db_schema::{ aggregates::structs::PostAggregates, impls::actor_language::UNDETERMINED_ID, @@ -592,6 +618,7 @@ mod tests { Community, CommunityFollower, CommunityFollowerForm, + CommunityFollowerState, CommunityInsertForm, CommunityModerator, CommunityModeratorForm, @@ -621,7 +648,7 @@ mod tests { site::Site, }, traits::{Bannable, Blockable, Crud, Followable, Joinable, Likeable, Saveable}, - utils::{build_db_pool, build_db_pool_for_tests, uplete, DbPool, RANK_DEFAULT}, + utils::{build_db_pool, build_db_pool_for_tests, get_conn, uplete, DbPool, RANK_DEFAULT}, CommunityVisibility, PostSortType, SubscribedType, @@ -629,7 +656,10 @@ mod tests { use lemmy_utils::error::LemmyResult; use pretty_assertions::assert_eq; use serial_test::serial; - use std::{collections::HashSet, time::Duration}; + use std::{ + collections::HashSet, + time::{Duration, Instant}, + }; use url::Url; const POST_WITH_ANOTHER_TITLE: &str = "Another title"; @@ -780,7 +810,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listing_with_person() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let mut data = init_data(pool).await?; @@ -840,7 +870,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listing_no_person() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -878,7 +908,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listing_title_only() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -937,7 +967,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listing_block_community() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -963,7 +993,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listing_like() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let mut data = init_data(pool).await?; @@ -1021,7 +1051,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listing_liked_only() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1070,7 +1100,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listing_saved_only() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1100,7 +1130,7 @@ mod tests { #[tokio::test] #[serial] async fn creator_info() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1138,7 +1168,7 @@ mod tests { async fn post_listing_person_language() -> LemmyResult<()> { const EL_POSTO: &str = "el posto"; - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1199,7 +1229,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listings_removed() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let mut data = init_data(pool).await?; @@ -1234,7 +1264,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listings_deleted() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1273,7 +1303,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listings_hidden_community() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1295,9 +1325,8 @@ mod tests { // Follow the community let form = CommunityFollowerForm { - community_id: data.inserted_community.id, - person_id: data.local_user_view.person.id, - pending: false, + state: Some(CommunityFollowerState::Accepted), + ..CommunityFollowerForm::new(data.inserted_community.id, data.local_user_view.person.id) }; CommunityFollower::follow(pool, &form).await?; @@ -1312,7 +1341,7 @@ mod tests { async fn post_listing_instance_block() -> LemmyResult<()> { const POST_FROM_BLOCKED_INSTANCE: &str = "post on blocked instance"; - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1372,7 +1401,7 @@ mod tests { #[tokio::test] #[serial] async fn pagination_includes_each_post_once() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1482,7 +1511,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listings_hide_read() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let mut data = init_data(pool).await?; @@ -1532,7 +1561,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listings_hide_hidden() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1568,7 +1597,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listings_hide_nsfw() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1734,7 +1763,7 @@ mod tests { #[tokio::test] #[serial] async fn local_only_instance() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1782,7 +1811,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listing_local_user_banned_from_community() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1825,7 +1854,7 @@ mod tests { #[tokio::test] #[serial] async fn post_listing_local_user_not_banned_from_community() -> LemmyResult<()> { - let pool = &build_db_pool().await?; + let pool = &build_db_pool()?; let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -1841,4 +1870,187 @@ mod tests { cleanup(data, pool).await } + + #[tokio::test] + #[serial] + async fn speed_check() -> LemmyResult<()> { + let pool = &build_db_pool()?; + let pool = &mut pool.into(); + let data = init_data(pool).await?; + + // Make sure the post_view query is less than this time + let duration_max = Duration::from_millis(80); + + // Create some dummy posts + let num_posts = 1000; + for x in 1..num_posts { + let name = format!("post_{x}"); + let url = Some(Url::parse(&format!("https://google.com/{name}"))?.into()); + + let post_form = PostInsertForm { + url, + ..PostInsertForm::new( + name, + data.local_user_view.person.id, + data.inserted_community.id, + ) + }; + Post::create(pool, &post_form).await?; + } + + // Manually trigger and wait for a statistics update to ensure consistent and high amount of + // accuracy in the statistics used for query planning + println!("🧮 updating database statistics"); + let conn = &mut get_conn(pool).await?; + conn.batch_execute("ANALYZE;").await?; + + // Time how fast the query took + let now = Instant::now(); + PostQuery { + sort: Some(PostSortType::Active), + local_user: Some(&data.local_user_view.local_user), + ..Default::default() + } + .list(&data.site, pool) + .await?; + + let elapsed = now.elapsed(); + println!("Elapsed: {:.0?}", elapsed); + + assert!( + elapsed.lt(&duration_max), + "Query took {:.0?}, longer than the max of {:.0?}", + elapsed, + duration_max + ); + + cleanup(data, pool).await + } + + #[tokio::test] + #[serial] + async fn post_listings_no_comments_only() -> LemmyResult<()> { + let pool = &build_db_pool()?; + let pool = &mut pool.into(); + let data = init_data(pool).await?; + + // Create a comment for a post + let comment_form = CommentInsertForm::new( + data.local_user_view.person.id, + data.inserted_post.id, + "a comment".to_owned(), + ); + Comment::create(pool, &comment_form, None).await?; + + // Make sure it doesnt come back with the no_comments option + let post_listings_no_comments = PostQuery { + sort: Some(PostSortType::New), + no_comments_only: Some(true), + local_user: Some(&data.local_user_view.local_user), + ..Default::default() + } + .list(&data.site, pool) + .await?; + + assert_eq!(vec![POST_BY_BOT], names(&post_listings_no_comments)); + + cleanup(data, pool).await + } + + #[tokio::test] + #[serial] + async fn post_listing_private_community() -> LemmyResult<()> { + let pool = &build_db_pool()?; + let pool = &mut pool.into(); + let mut data = init_data(pool).await?; + + // Mark community as private + Community::update( + pool, + data.inserted_community.id, + &CommunityUpdateForm { + visibility: Some(CommunityVisibility::Private), + ..Default::default() + }, + ) + .await?; + + // No posts returned without auth + let read_post_listing = PostQuery { + community_id: Some(data.inserted_community.id), + ..Default::default() + } + .list(&data.site, pool) + .await?; + assert_eq!(0, read_post_listing.len()); + let post_view = PostView::read(pool, data.inserted_post.id, None, false).await; + assert!(post_view.is_err()); + + // No posts returned for non-follower who is not admin + data.local_user_view.local_user.admin = false; + let read_post_listing = PostQuery { + community_id: Some(data.inserted_community.id), + local_user: Some(&data.local_user_view.local_user), + ..Default::default() + } + .list(&data.site, pool) + .await?; + assert_eq!(0, read_post_listing.len()); + let post_view = PostView::read( + pool, + data.inserted_post.id, + Some(&data.local_user_view.local_user), + false, + ) + .await; + assert!(post_view.is_err()); + + // Admin can view content without following + data.local_user_view.local_user.admin = true; + let read_post_listing = PostQuery { + community_id: Some(data.inserted_community.id), + local_user: Some(&data.local_user_view.local_user), + ..Default::default() + } + .list(&data.site, pool) + .await?; + assert_eq!(2, read_post_listing.len()); + let post_view = PostView::read( + pool, + data.inserted_post.id, + Some(&data.local_user_view.local_user), + true, + ) + .await; + assert!(post_view.is_ok()); + data.local_user_view.local_user.admin = false; + + // User can view after following + CommunityFollower::follow( + pool, + &CommunityFollowerForm { + state: Some(CommunityFollowerState::Accepted), + ..CommunityFollowerForm::new(data.inserted_community.id, data.local_user_view.person.id) + }, + ) + .await?; + let read_post_listing = PostQuery { + community_id: Some(data.inserted_community.id), + local_user: Some(&data.local_user_view.local_user), + ..Default::default() + } + .list(&data.site, pool) + .await?; + assert_eq!(2, read_post_listing.len()); + let post_view = PostView::read( + pool, + data.inserted_post.id, + Some(&data.local_user_view.local_user), + true, + ) + .await; + assert!(post_view.is_ok()); + + cleanup(data, pool).await + } } diff --git a/crates/db_views/src/private_message_report_view.rs b/crates/db_views/src/private_message_report_view.rs index 56d0d6e7b6..e59d996089 100644 --- a/crates/db_views/src/private_message_report_view.rs +++ b/crates/db_views/src/private_message_report_view.rs @@ -133,7 +133,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_views/src/private_message_view.rs b/crates/db_views/src/private_message_view.rs index a55871fb9d..2286b7dc67 100644 --- a/crates/db_views/src/private_message_view.rs +++ b/crates/db_views/src/private_message_view.rs @@ -243,7 +243,7 @@ mod tests { #[tokio::test] #[serial] async fn read_private_messages() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let Data { timmy, @@ -314,7 +314,7 @@ mod tests { #[tokio::test] #[serial] async fn ensure_person_block() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let Data { timmy, @@ -357,7 +357,7 @@ mod tests { #[tokio::test] #[serial] async fn ensure_instance_block() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let Data { timmy, diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index a0a40789bf..b5821ef26f 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -162,7 +162,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; @@ -240,6 +240,7 @@ mod tests { totp_2fa_enabled: inserted_sara_local_user.totp_2fa_enabled, enable_keyboard_navigation: inserted_sara_local_user.enable_keyboard_navigation, enable_animated_images: inserted_sara_local_user.enable_animated_images, + enable_private_messages: inserted_sara_local_user.enable_private_messages, collapse_bot_comments: inserted_sara_local_user.collapse_bot_comments, }, creator: Person { diff --git a/crates/db_views/src/site_view.rs b/crates/db_views/src/site_view.rs index 6014ad9645..ed9aeb498c 100644 --- a/crates/db_views/src/site_view.rs +++ b/crates/db_views/src/site_view.rs @@ -5,7 +5,7 @@ use lemmy_db_schema::{ schema::{local_site, local_site_rate_limit, site, site_aggregates}, utils::{get_conn, DbPool}, }; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; impl SiteView { pub async fn read_local(pool: &mut DbPool<'_>) -> LemmyResult { diff --git a/crates/db_views/src/structs.rs b/crates/db_views/src/structs.rs index 3c219d63fb..4586fbcacd 100644 --- a/crates/db_views/src/structs.rs +++ b/crates/db_views/src/structs.rs @@ -48,7 +48,9 @@ pub struct CommentReportView { pub creator_blocked: bool, pub subscribed: SubscribedType, pub saved: bool, + #[cfg_attr(feature = "full", ts(optional))] pub my_vote: Option, + #[cfg_attr(feature = "full", ts(optional))] pub resolver: Option, } @@ -71,6 +73,7 @@ pub struct CommentView { pub subscribed: SubscribedType, pub saved: bool, pub creator_blocked: bool, + #[cfg_attr(feature = "full", ts(optional))] pub my_vote: Option, } @@ -106,9 +109,11 @@ pub struct PostReportView { pub read: bool, pub hidden: bool, pub creator_blocked: bool, + #[cfg_attr(feature = "full", ts(optional))] pub my_vote: Option, pub unread_comments: i64, pub counts: PostAggregates, + #[cfg_attr(feature = "full", ts(optional))] pub resolver: Option, } @@ -131,6 +136,7 @@ pub struct PostView { pub post: Post, pub creator: Person, pub community: Community, + #[cfg_attr(feature = "full", ts(optional))] pub image_details: Option, pub creator_banned_from_community: bool, pub banned_from_community: bool, @@ -142,6 +148,7 @@ pub struct PostView { pub read: bool, pub hidden: bool, pub creator_blocked: bool, + #[cfg_attr(feature = "full", ts(optional))] pub my_vote: Option, pub unread_comments: i64, } @@ -168,6 +175,7 @@ pub struct PrivateMessageReportView { pub private_message: PrivateMessage, pub private_message_creator: Person, pub creator: Person, + #[cfg_attr(feature = "full", ts(optional))] pub resolver: Option, } @@ -181,6 +189,7 @@ pub struct RegistrationApplicationView { pub registration_application: RegistrationApplication, pub creator_local_user: LocalUser, pub creator: Person, + #[cfg_attr(feature = "full", ts(optional))] pub admin: Option, } diff --git a/crates/db_views/src/vote_view.rs b/crates/db_views/src/vote_view.rs index 40f24704b0..9af0bd7560 100644 --- a/crates/db_views/src/vote_view.rs +++ b/crates/db_views/src/vote_view.rs @@ -98,7 +98,7 @@ mod tests { #[tokio::test] #[serial] async fn post_and_comment_vote_views() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_views_actor/Cargo.toml b/crates/db_views_actor/Cargo.toml index d623959d5f..18a79826bf 100644 --- a/crates/db_views_actor/Cargo.toml +++ b/crates/db_views_actor/Cargo.toml @@ -46,5 +46,4 @@ serial_test = { workspace = true } tokio = { workspace = true } pretty_assertions = { workspace = true } url.workspace = true -lemmy_db_views.workspace = true -lemmy_utils.workspace = true +lemmy_db_views = { workspace = true, features = ["full"] } diff --git a/crates/db_views_actor/src/comment_reply_view.rs b/crates/db_views_actor/src/comment_reply_view.rs index c74d8ce181..6c5442e6af 100644 --- a/crates/db_views_actor/src/comment_reply_view.rs +++ b/crates/db_views_actor/src/comment_reply_view.rs @@ -244,7 +244,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_views_actor/src/community_follower_view.rs b/crates/db_views_actor/src/community_follower_view.rs index 40261dacdc..d3015c182e 100644 --- a/crates/db_views_actor/src/community_follower_view.rs +++ b/crates/db_views_actor/src/community_follower_view.rs @@ -1,17 +1,27 @@ -use crate::structs::CommunityFollowerView; +use crate::structs::{CommunityFollowerView, PendingFollow}; use chrono::Utc; use diesel::{ - dsl::{count_star, not}, + dsl::{count, count_star, exists, not}, result::Error, + select, + BoolExpressionMethods, ExpressionMethods, + JoinOnDsl, QueryDsl, }; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, schema::{community, community_actions, person}, - utils::{action_query, get_conn, DbPool}, + source::{ + community::{Community, CommunityFollower, CommunityFollowerState}, + person::Person, + }, + utils::{action_query, get_conn, limit_and_offset, DbPool}, + CommunityVisibility, + SubscribedType, }; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; impl CommunityFollowerView { /// return a list of local community ids and remote inboxes that at least one user of the given @@ -31,7 +41,7 @@ impl CommunityFollowerView { community_actions::table .inner_join(community::table) - .inner_join(person::table) + .inner_join(person::table.on(community_actions::person_id.eq(person::id))) .filter(person::instance_id.eq(instance_id)) .filter(community::local) // this should be a no-op since community_followers table only has // local-person+remote-community or remote-person+local-community @@ -50,7 +60,7 @@ impl CommunityFollowerView { let res = action_query(community_actions::followed) .filter(community_actions::community_id.eq(community_id)) .filter(not(person::local)) - .inner_join(person::table) + .inner_join(person::table.on(community_actions::person_id.eq(person::id))) .select(person::inbox_url) .distinct() .load::(conn) @@ -76,7 +86,7 @@ impl CommunityFollowerView { let conn = &mut get_conn(pool).await?; action_query(community_actions::followed) .inner_join(community::table) - .inner_join(person::table) + .inner_join(person::table.on(community_actions::person_id.eq(person::id))) .select((community::all_columns, person::all_columns)) .filter(community_actions::person_id.eq(person_id)) .filter(community::deleted.eq(false)) @@ -85,4 +95,220 @@ impl CommunityFollowerView { .load::(conn) .await } + + pub async fn list_approval_required( + pool: &mut DbPool<'_>, + person_id: PersonId, + // TODO: if this is true dont check for community mod, but only check for local community + // also need to check is_admin() + all_communities: bool, + pending_only: bool, + page: Option, + limit: Option, + ) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + let (limit, offset) = limit_and_offset(page, limit)?; + let (person_alias, community_follower_alias) = diesel::alias!( + person as person_alias, + community_actions as community_follower_alias + ); + + // check if the community already has an accepted follower from the same instance + let is_new_instance = not(exists( + person_alias + .inner_join( + community_follower_alias.on( + person_alias + .field(person::id) + .eq(community_follower_alias.field(community_actions::person_id)), + ), + ) + .filter( + person::instance_id + .eq(person_alias.field(person::instance_id)) + .and( + community_follower_alias + .field(community_actions::community_id) + .eq(community_actions::community_id), + ) + .and( + community_follower_alias + .field(community_actions::follow_state) + .eq(CommunityFollowerState::Accepted), + ), + ), + )); + + let mut query = action_query(community_actions::followed) + .inner_join(person::table.on(community_actions::person_id.eq(person::id))) + .inner_join(community::table) + .into_boxed(); + if all_communities { + // if param is false, only return items for communities where user is a mod + query = query + .filter(community_actions::became_moderator.is_not_null()) + .filter(community_actions::person_id.eq(person_id)); + } + if pending_only { + query = + query.filter(community_actions::follow_state.eq(CommunityFollowerState::ApprovalRequired)); + } + let res = query + .order_by(community_actions::followed.asc()) + .limit(limit) + .offset(offset) + .select(( + person::all_columns, + community::all_columns, + is_new_instance, + CommunityFollower::select_subscribed_type(), + )) + .load::<(Person, Community, bool, SubscribedType)>(conn) + .await?; + Ok( + res + .into_iter() + .map( + |(person, community, is_new_instance, subscribed)| PendingFollow { + person, + community, + is_new_instance, + subscribed, + }, + ) + .collect(), + ) + } + + pub async fn count_approval_required( + pool: &mut DbPool<'_>, + community_id: CommunityId, + ) -> Result { + let conn = &mut get_conn(pool).await?; + action_query(community_actions::followed) + .inner_join(person::table.on(community_actions::person_id.eq(person::id))) + .filter(community_actions::community_id.eq(community_id)) + .filter(community_actions::follow_state.eq(CommunityFollowerState::ApprovalRequired)) + .select(count(community_actions::community_id)) + .first::(conn) + .await + } + pub async fn check_private_community_action( + pool: &mut DbPool<'_>, + from_person_id: PersonId, + community: &Community, + ) -> LemmyResult<()> { + if community.visibility != CommunityVisibility::Private { + return Ok(()); + } + let conn = &mut get_conn(pool).await?; + select(exists( + action_query(community_actions::followed) + .filter(community_actions::community_id.eq(community.id)) + .filter(community_actions::person_id.eq(from_person_id)) + .filter(community_actions::follow_state.eq(CommunityFollowerState::Accepted)), + )) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(LemmyErrorType::NotFound.into()) + } + pub async fn check_has_followers_from_instance( + community_id: CommunityId, + instance_id: InstanceId, + pool: &mut DbPool<'_>, + ) -> Result<(), Error> { + let conn = &mut get_conn(pool).await?; + select(exists( + action_query(community_actions::followed) + .inner_join(person::table.on(community_actions::person_id.eq(person::id))) + .filter(community_actions::community_id.eq(community_id)) + .filter(person::instance_id.eq(instance_id)) + .filter(community_actions::follow_state.eq(CommunityFollowerState::Accepted)), + )) + .get_result::(conn) + .await? + .then_some(()) + .ok_or(diesel::NotFound) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use lemmy_db_schema::{ + source::{ + community::{CommunityFollower, CommunityFollowerForm, CommunityInsertForm}, + instance::Instance, + person::PersonInsertForm, + }, + traits::{Crud, Followable}, + utils::build_db_pool_for_tests, + }; + use serial_test::serial; + + #[tokio::test] + #[serial] + async fn test_has_followers_from_instance() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests(); + let pool = &mut pool.into(); + + // insert local community + let local_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; + let community_form = CommunityInsertForm::new( + local_instance.id, + "test_community_3".to_string(), + "nada".to_owned(), + "pubkey".to_string(), + ); + let community = Community::create(pool, &community_form).await?; + + // insert remote user + let remote_instance = Instance::read_or_create(pool, "other_domain.tld".to_string()).await?; + let person_form = + PersonInsertForm::new("name".to_string(), "pubkey".to_string(), remote_instance.id); + let person = Person::create(pool, &person_form).await?; + + // community has no follower from remote instance, returns error + let has_followers = CommunityFollowerView::check_has_followers_from_instance( + community.id, + remote_instance.id, + pool, + ) + .await; + assert!(has_followers.is_err()); + + // insert unapproved follower + let mut follower_form = CommunityFollowerForm { + state: Some(CommunityFollowerState::ApprovalRequired), + ..CommunityFollowerForm::new(community.id, person.id) + }; + CommunityFollower::follow(pool, &follower_form).await?; + + // still returns error + let has_followers = CommunityFollowerView::check_has_followers_from_instance( + community.id, + remote_instance.id, + pool, + ) + .await; + assert!(has_followers.is_err()); + + // mark follower as accepted + follower_form.state = Some(CommunityFollowerState::Accepted); + CommunityFollower::follow(pool, &follower_form).await?; + + // now returns ok + let has_followers = CommunityFollowerView::check_has_followers_from_instance( + community.id, + remote_instance.id, + pool, + ) + .await; + assert!(has_followers.is_ok()); + + Instance::delete(pool, local_instance.id).await?; + Instance::delete(pool, remote_instance.id).await?; + Ok(()) + } } diff --git a/crates/db_views_actor/src/community_moderator_view.rs b/crates/db_views_actor/src/community_moderator_view.rs index 85533434b2..a9ada92e14 100644 --- a/crates/db_views_actor/src/community_moderator_view.rs +++ b/crates/db_views_actor/src/community_moderator_view.rs @@ -1,5 +1,5 @@ use crate::structs::CommunityModeratorView; -use diesel::{dsl::exists, result::Error, select, ExpressionMethods, QueryDsl}; +use diesel::{dsl::exists, result::Error, select, ExpressionMethods, JoinOnDsl, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ impls::local_user::LocalUserOptionHelper, @@ -8,7 +8,7 @@ use lemmy_db_schema::{ source::local_user::LocalUser, utils::{action_query, find_action, get_conn, DbPool}, }; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; impl CommunityModeratorView { pub async fn check_is_community_moderator( @@ -49,7 +49,7 @@ impl CommunityModeratorView { let conn = &mut get_conn(pool).await?; action_query(community_actions::became_moderator) .inner_join(community::table) - .inner_join(person::table) + .inner_join(person::table.on(person::id.eq(community_actions::person_id))) .filter(community_actions::community_id.eq(community_id)) .select((community::all_columns, person::all_columns)) .order_by(community_actions::became_moderator) @@ -65,7 +65,7 @@ impl CommunityModeratorView { let conn = &mut get_conn(pool).await?; let mut query = action_query(community_actions::became_moderator) .inner_join(community::table) - .inner_join(person::table) + .inner_join(person::table.on(person::id.eq(community_actions::person_id))) .filter(community_actions::person_id.eq(person_id)) .select((community::all_columns, person::all_columns)) .into_boxed(); @@ -91,7 +91,7 @@ impl CommunityModeratorView { let conn = &mut get_conn(pool).await?; action_query(community_actions::became_moderator) .inner_join(community::table) - .inner_join(person::table) + .inner_join(person::table.on(person::id.eq(community_actions::person_id))) .select((community::all_columns, person::all_columns)) // A hacky workaround instead of group_bys // https://stackoverflow.com/questions/24042359/how-to-join-only-one-row-in-joined-table-with-postgres diff --git a/crates/db_views_actor/src/community_person_ban_view.rs b/crates/db_views_actor/src/community_person_ban_view.rs index f058b2061e..224ea8d53f 100644 --- a/crates/db_views_actor/src/community_person_ban_view.rs +++ b/crates/db_views_actor/src/community_person_ban_view.rs @@ -9,7 +9,7 @@ use lemmy_db_schema::{ schema::community_actions, utils::{find_action, get_conn, DbPool}, }; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; impl CommunityPersonBanView { pub async fn check( diff --git a/crates/db_views_actor/src/community_view.rs b/crates/db_views_actor/src/community_view.rs index 8057e729b7..f42340bdb6 100644 --- a/crates/db_views_actor/src/community_view.rs +++ b/crates/db_views_actor/src/community_view.rs @@ -13,7 +13,11 @@ use lemmy_db_schema::{ impls::local_user::LocalUserOptionHelper, newtypes::{CommunityId, PersonId}, schema::{community, community_actions, community_aggregates, instance_actions}, - source::{community::CommunityFollower, local_user::LocalUser, site::Site}, + source::{ + community::{CommunityFollower, CommunityFollowerState}, + local_user::LocalUser, + site::Site, + }, utils::{ actions, functions::lower, @@ -28,7 +32,7 @@ use lemmy_db_schema::{ ListingType, PostSortType, }; -use lemmy_utils::{error::LemmyResult, LemmyErrorType}; +use lemmy_utils::error::{LemmyErrorType, LemmyResult}; fn queries<'a>() -> Queries< impl ReadFn<'a, CommunityView, (CommunityId, Option<&'a LocalUser>, bool)>, @@ -105,7 +109,7 @@ fn queries<'a>() -> Queries< query = query.filter(not_removed_or_deleted).filter( community::hidden .eq(false) - .or(community_actions::follow_pending.is_not_null()), + .or(community_actions::follow_state.is_not_null()), ); } @@ -132,7 +136,9 @@ fn queries<'a>() -> Queries< if let Some(listing_type) = options.listing_type { query = match listing_type { - ListingType::Subscribed => query.filter(community_actions::follow_pending.is_not_null()), + ListingType::Subscribed => { + query.filter(community_actions::follow_state.eq(Some(CommunityFollowerState::Accepted))) + } ListingType::Local => query.filter(community::local.eq(true)), _ => query, }; @@ -262,44 +268,52 @@ mod tests { }; use lemmy_db_schema::{ source::{ - community::{Community, CommunityInsertForm, CommunityUpdateForm}, + community::{ + Community, + CommunityFollower, + CommunityFollowerForm, + CommunityFollowerState, + CommunityInsertForm, + CommunityUpdateForm, + }, instance::Instance, local_user::{LocalUser, LocalUserInsertForm}, person::{Person, PersonInsertForm}, site::Site, }, - traits::Crud, + traits::{Crud, Followable}, utils::{build_db_pool_for_tests, DbPool}, CommunityVisibility, + SubscribedType, }; use lemmy_utils::error::LemmyResult; use serial_test::serial; use url::Url; struct Data { - inserted_instance: Instance, + instance: Instance, local_user: LocalUser, - inserted_communities: [Community; 3], + communities: [Community; 3], site: Site, } async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult { - let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; + let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; let person_name = "tegan".to_string(); - let new_person = PersonInsertForm::test_form(inserted_instance.id, &person_name); + let new_person = PersonInsertForm::test_form(instance.id, &person_name); let inserted_person = Person::create(pool, &new_person).await?; let local_user_form = LocalUserInsertForm::test_form(inserted_person.id); let local_user = LocalUser::create(pool, &local_user_form, vec![]).await?; - let inserted_communities = [ + let communities = [ Community::create( pool, &CommunityInsertForm::new( - inserted_instance.id, + instance.id, "test_community_1".to_string(), "nada1".to_owned(), "pubkey".to_string(), @@ -309,7 +323,7 @@ mod tests { Community::create( pool, &CommunityInsertForm::new( - inserted_instance.id, + instance.id, "test_community_2".to_string(), "nada2".to_owned(), "pubkey".to_string(), @@ -319,7 +333,7 @@ mod tests { Community::create( pool, &CommunityInsertForm::new( - inserted_instance.id, + instance.id, "test_community_3".to_string(), "nada3".to_owned(), "pubkey".to_string(), @@ -348,33 +362,93 @@ mod tests { }; Ok(Data { - inserted_instance, + instance, local_user, - inserted_communities, + communities, site, }) } async fn cleanup(data: Data, pool: &mut DbPool<'_>) -> LemmyResult<()> { - for Community { id, .. } in data.inserted_communities { + for Community { id, .. } in data.communities { Community::delete(pool, id).await?; } Person::delete(pool, data.local_user.person_id).await?; - Instance::delete(pool, data.inserted_instance.id).await?; + Instance::delete(pool, data.instance.id).await?; Ok(()) } + #[tokio::test] + #[serial] + async fn subscribe_state() -> LemmyResult<()> { + let pool = &build_db_pool_for_tests(); + let pool = &mut pool.into(); + let data = init_data(pool).await?; + let community = &data.communities[0]; + + let unauthenticated = CommunityView::read(pool, community.id, None, false).await?; + assert_eq!(SubscribedType::NotSubscribed, unauthenticated.subscribed); + + let authenticated = + CommunityView::read(pool, community.id, Some(&data.local_user), false).await?; + assert_eq!(SubscribedType::NotSubscribed, authenticated.subscribed); + + let form = CommunityFollowerForm { + state: Some(CommunityFollowerState::Pending), + ..CommunityFollowerForm::new(community.id, data.local_user.person_id) + }; + CommunityFollower::follow(pool, &form).await?; + + let with_pending_follow = + CommunityView::read(pool, community.id, Some(&data.local_user), false).await?; + assert_eq!(SubscribedType::Pending, with_pending_follow.subscribed); + + // mark community private and set follow as approval required + Community::update( + pool, + community.id, + &CommunityUpdateForm { + visibility: Some(CommunityVisibility::Private), + ..Default::default() + }, + ) + .await?; + let form = CommunityFollowerForm { + state: Some(CommunityFollowerState::ApprovalRequired), + ..CommunityFollowerForm::new(community.id, data.local_user.person_id) + }; + CommunityFollower::follow(pool, &form).await?; + + let with_approval_required_follow = + CommunityView::read(pool, community.id, Some(&data.local_user), false).await?; + assert_eq!( + SubscribedType::ApprovalRequired, + with_approval_required_follow.subscribed + ); + + let form = CommunityFollowerForm { + state: Some(CommunityFollowerState::Accepted), + ..CommunityFollowerForm::new(community.id, data.local_user.person_id) + }; + CommunityFollower::follow(pool, &form).await?; + let with_accepted_follow = + CommunityView::read(pool, community.id, Some(&data.local_user), false).await?; + assert_eq!(SubscribedType::Subscribed, with_accepted_follow.subscribed); + + cleanup(data, pool).await + } + #[tokio::test] #[serial] async fn local_only_community() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; Community::update( pool, - data.inserted_communities[0].id, + data.communities[0].id, &CommunityUpdateForm { visibility: Some(CommunityVisibility::LocalOnly), ..Default::default() @@ -387,10 +461,7 @@ mod tests { } .list(&data.site, pool) .await?; - assert_eq!( - data.inserted_communities.len() - 1, - unauthenticated_query.len() - ); + assert_eq!(data.communities.len() - 1, unauthenticated_query.len()); let authenticated_query = CommunityQuery { local_user: Some(&data.local_user), @@ -398,19 +469,14 @@ mod tests { } .list(&data.site, pool) .await?; - assert_eq!(data.inserted_communities.len(), authenticated_query.len()); + assert_eq!(data.communities.len(), authenticated_query.len()); let unauthenticated_community = - CommunityView::read(pool, data.inserted_communities[0].id, None, false).await; + CommunityView::read(pool, data.communities[0].id, None, false).await; assert!(unauthenticated_community.is_err()); - let authenticated_community = CommunityView::read( - pool, - data.inserted_communities[0].id, - Some(&data.local_user), - false, - ) - .await; + let authenticated_community = + CommunityView::read(pool, data.communities[0].id, Some(&data.local_user), false).await; assert!(authenticated_community.is_ok()); cleanup(data, pool).await @@ -419,7 +485,7 @@ mod tests { #[tokio::test] #[serial] async fn community_sort_name() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; diff --git a/crates/db_views_actor/src/person_mention_view.rs b/crates/db_views_actor/src/person_mention_view.rs index 0a026aab81..08be67a826 100644 --- a/crates/db_views_actor/src/person_mention_view.rs +++ b/crates/db_views_actor/src/person_mention_view.rs @@ -245,7 +245,7 @@ mod tests { #[tokio::test] #[serial] async fn test_crud() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; diff --git a/crates/db_views_actor/src/person_view.rs b/crates/db_views_actor/src/person_view.rs index 724a700ad0..39d1ac27c6 100644 --- a/crates/db_views_actor/src/person_view.rs +++ b/crates/db_views_actor/src/person_view.rs @@ -229,7 +229,7 @@ mod tests { #[tokio::test] #[serial] async fn exclude_deleted() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -261,7 +261,7 @@ mod tests { #[tokio::test] #[serial] async fn list_banned() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -285,7 +285,7 @@ mod tests { #[tokio::test] #[serial] async fn list_admins() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; @@ -315,7 +315,7 @@ mod tests { #[tokio::test] #[serial] async fn listing_type() -> LemmyResult<()> { - let pool = &build_db_pool_for_tests().await; + let pool = &build_db_pool_for_tests(); let pool = &mut pool.into(); let data = init_data(pool).await?; diff --git a/crates/db_views_actor/src/structs.rs b/crates/db_views_actor/src/structs.rs index ecf9ba11d1..6b609a7537 100644 --- a/crates/db_views_actor/src/structs.rs +++ b/crates/db_views_actor/src/structs.rs @@ -109,6 +109,7 @@ pub struct PersonMentionView { pub subscribed: SubscribedType, pub saved: bool, pub creator_blocked: bool, + #[cfg_attr(feature = "full", ts(optional))] pub my_vote: Option, } @@ -133,6 +134,7 @@ pub struct CommentReplyView { pub subscribed: SubscribedType, pub saved: bool, pub creator_blocked: bool, + #[cfg_attr(feature = "full", ts(optional))] pub my_vote: Option, } @@ -146,3 +148,14 @@ pub struct PersonView { pub counts: PersonAggregates, pub is_admin: bool, } + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS, Queryable))] +#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] +#[cfg_attr(feature = "full", ts(export))] +pub struct PendingFollow { + pub person: Person, + pub community: Community, + pub is_new_instance: bool, + pub subscribed: SubscribedType, +} diff --git a/crates/db_views_moderator/src/structs.rs b/crates/db_views_moderator/src/structs.rs index 10ad78942a..27ee825224 100644 --- a/crates/db_views_moderator/src/structs.rs +++ b/crates/db_views_moderator/src/structs.rs @@ -39,6 +39,7 @@ use ts_rs::TS; /// When someone is added as a community moderator. pub struct ModAddCommunityView { pub mod_add_community: ModAddCommunity, + #[cfg_attr(feature = "full", ts(optional))] pub moderator: Option, pub community: Community, pub modded_person: Person, @@ -52,6 +53,7 @@ pub struct ModAddCommunityView { /// When someone is added as a site moderator. pub struct ModAddView { pub mod_add: ModAdd, + #[cfg_attr(feature = "full", ts(optional))] pub moderator: Option, pub modded_person: Person, } @@ -64,6 +66,7 @@ pub struct ModAddView { /// When someone is banned from a community. pub struct ModBanFromCommunityView { pub mod_ban_from_community: ModBanFromCommunity, + #[cfg_attr(feature = "full", ts(optional))] pub moderator: Option, pub community: Community, pub banned_person: Person, @@ -77,6 +80,7 @@ pub struct ModBanFromCommunityView { /// When someone is banned from the site. pub struct ModBanView { pub mod_ban: ModBan, + #[cfg_attr(feature = "full", ts(optional))] pub moderator: Option, pub banned_person: Person, } @@ -89,6 +93,7 @@ pub struct ModBanView { /// When a community is hidden from public view. pub struct ModHideCommunityView { pub mod_hide_community: ModHideCommunity, + #[cfg_attr(feature = "full", ts(optional))] pub admin: Option, pub community: Community, } @@ -101,6 +106,7 @@ pub struct ModHideCommunityView { /// When a moderator locks a post (prevents new comments being made). pub struct ModLockPostView { pub mod_lock_post: ModLockPost, + #[cfg_attr(feature = "full", ts(optional))] pub moderator: Option, pub post: Post, pub community: Community, @@ -114,6 +120,7 @@ pub struct ModLockPostView { /// When a moderator removes a comment. pub struct ModRemoveCommentView { pub mod_remove_comment: ModRemoveComment, + #[cfg_attr(feature = "full", ts(optional))] pub moderator: Option, pub comment: Comment, pub commenter: Person, @@ -129,6 +136,7 @@ pub struct ModRemoveCommentView { /// When a moderator removes a community. pub struct ModRemoveCommunityView { pub mod_remove_community: ModRemoveCommunity, + #[cfg_attr(feature = "full", ts(optional))] pub moderator: Option, pub community: Community, } @@ -141,6 +149,7 @@ pub struct ModRemoveCommunityView { /// When a moderator removes a post. pub struct ModRemovePostView { pub mod_remove_post: ModRemovePost, + #[cfg_attr(feature = "full", ts(optional))] pub moderator: Option, pub post: Post, pub community: Community, @@ -154,6 +163,7 @@ pub struct ModRemovePostView { /// When a moderator features a post on a community (pins it to the top). pub struct ModFeaturePostView { pub mod_feature_post: ModFeaturePost, + #[cfg_attr(feature = "full", ts(optional))] pub moderator: Option, pub post: Post, pub community: Community, @@ -167,6 +177,7 @@ pub struct ModFeaturePostView { /// When a moderator transfers a community to a new owner. pub struct ModTransferCommunityView { pub mod_transfer_community: ModTransferCommunity, + #[cfg_attr(feature = "full", ts(optional))] pub moderator: Option, pub community: Community, pub modded_person: Person, @@ -180,6 +191,7 @@ pub struct ModTransferCommunityView { /// When an admin purges a comment. pub struct AdminPurgeCommentView { pub admin_purge_comment: AdminPurgeComment, + #[cfg_attr(feature = "full", ts(optional))] pub admin: Option, pub post: Post, } @@ -192,6 +204,7 @@ pub struct AdminPurgeCommentView { /// When an admin purges a community. pub struct AdminPurgeCommunityView { pub admin_purge_community: AdminPurgeCommunity, + #[cfg_attr(feature = "full", ts(optional))] pub admin: Option, } @@ -203,6 +216,7 @@ pub struct AdminPurgeCommunityView { /// When an admin purges a person. pub struct AdminPurgePersonView { pub admin_purge_person: AdminPurgePerson, + #[cfg_attr(feature = "full", ts(optional))] pub admin: Option, } @@ -214,6 +228,7 @@ pub struct AdminPurgePersonView { /// When an admin purges a post. pub struct AdminPurgePostView { pub admin_purge_post: AdminPurgePost, + #[cfg_attr(feature = "full", ts(optional))] pub admin: Option, pub community: Community, } @@ -225,12 +240,19 @@ pub struct AdminPurgePostView { #[cfg_attr(feature = "full", ts(export))] /// Querying / filtering the modlog. pub struct ModlogListParams { + #[cfg_attr(feature = "full", ts(optional))] pub community_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub mod_person_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub other_person_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub post_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub comment_id: Option, + #[cfg_attr(feature = "full", ts(optional))] pub page: Option, + #[cfg_attr(feature = "full", ts(optional))] pub limit: Option, pub hide_modlog_names: bool, } diff --git a/crates/utils/src/error.rs b/crates/utils/src/error.rs index c95af03e2d..d52df2f722 100644 --- a/crates/utils/src/error.rs +++ b/crates/utils/src/error.rs @@ -119,7 +119,10 @@ pub enum LemmyErrorType { InvalidUrl, EmailSendFailed, Slurs, - RegistrationDenied(Option), + RegistrationDenied { + #[cfg_attr(feature = "full", ts(optional))] + reason: Option, + }, SiteNameRequired, SiteNameLengthOverflow, PermissiveRegex, @@ -147,7 +150,10 @@ pub enum LemmyErrorType { CommunityHasNoFollowers, PostScheduleTimeMustBeInFuture, TooManyScheduledPosts, - FederationError(Option), + FederationError { + #[cfg_attr(feature = "full", ts(optional))] + error: Option, + }, } /// Federation related errors, these dont need to be translated. @@ -178,6 +184,7 @@ pub enum FederationError { InboxTimeout, CantDeleteSite, ObjectIsNotPublic, + ObjectIsNotPrivate, } cfg_if! { @@ -262,7 +269,7 @@ cfg_if! { fn from(error_type: FederationError) -> Self { let inner = anyhow::anyhow!("{}", error_type); LemmyError { - error_type: LemmyErrorType::FederationError(Some(error_type)), + error_type: LemmyErrorType::FederationError { error: Some(error_type) }, inner, context: Backtrace::capture(), } diff --git a/crates/utils/src/lib.rs b/crates/utils/src/lib.rs index 7f06914960..1e0cbefbf0 100644 --- a/crates/utils/src/lib.rs +++ b/crates/utils/src/lib.rs @@ -13,7 +13,6 @@ cfg_if! { } pub mod error; -pub use error::LemmyErrorType; use std::time::Duration; pub type ConnectionId = usize; diff --git a/crates/utils/src/settings/structs.rs b/crates/utils/src/settings/structs.rs index 8c28d908a2..c95f66644e 100644 --- a/crates/utils/src/settings/structs.rs +++ b/crates/utils/src/settings/structs.rs @@ -92,7 +92,7 @@ pub struct PictrsConfig { pub upload_timeout: u64, /// Resize post thumbnails to this maximum width/height. - #[default(256)] + #[default(512)] pub max_thumbnail_size: u32, } diff --git a/crates/utils/src/utils/markdown/image_links.rs b/crates/utils/src/utils/markdown/image_links.rs index a21bb6f416..7456190e42 100644 --- a/crates/utils/src/utils/markdown/image_links.rs +++ b/crates/utils/src/utils/markdown/image_links.rs @@ -42,13 +42,10 @@ pub fn markdown_rewrite_image_links(mut src: String) -> (String, Vec) { pub fn markdown_handle_title(src: &str, start: usize, end: usize) -> (&str, Option<&str>) { let content = src.get(start..end).unwrap_or_default(); // necessary for custom emojis which look like `![name](url "title")` - let (url, extra) = if content.contains(' ') { - let split = content.split_once(' ').expect("split is valid"); - (split.0, Some(split.1)) - } else { - (content, None) - }; - (url, extra) + match content.split_once(' ') { + Some((a, b)) => (a, Some(b)), + _ => (content, None), + } } pub fn markdown_find_links(src: &str) -> Vec<(usize, usize)> { @@ -61,9 +58,9 @@ fn find_urls(src: &str) -> Vec<(usize, usize)> { let mut links_offsets = vec![]; ast.walk(|node, _depth| { if let Some(image) = node.cast::() { - let node_offsets = node.srcmap.expect("srcmap is none").get_byte_offsets(); - let start_offset = node_offsets.1 - image.url_len() - 1 - image.title_len(); - let end_offset = node_offsets.1 - 1; + let (_, node_offset) = node.srcmap.expect("srcmap is none").get_byte_offsets(); + let start_offset = node_offset - image.url_len() - 1 - image.title_len(); + let end_offset = node_offset - 1; links_offsets.push((start_offset, end_offset)); } diff --git a/crates/utils/src/utils/markdown/mod.rs b/crates/utils/src/utils/markdown/mod.rs index a51b507ced..9d34e8a698 100644 --- a/crates/utils/src/utils/markdown/mod.rs +++ b/crates/utils/src/utils/markdown/mod.rs @@ -1,4 +1,4 @@ -use crate::{error::LemmyResult, LemmyErrorType}; +use crate::error::{LemmyErrorType, LemmyResult}; use markdown_it::MarkdownIt; use regex::RegexSet; use std::sync::LazyLock; diff --git a/crates/utils/tests/test_errors_used.rs b/crates/utils/tests/test_errors_used.rs index d0dec489af..bfd70bdade 100644 --- a/crates/utils/tests/test_errors_used.rs +++ b/crates/utils/tests/test_errors_used.rs @@ -1,4 +1,4 @@ -use lemmy_utils::LemmyErrorType; +use lemmy_utils::error::LemmyErrorType; use std::{env::current_dir, process::Command}; use strum::IntoEnumIterator; diff --git a/crates/utils/translations b/crates/utils/translations index 7adddded58..dbb09b0784 160000 --- a/crates/utils/translations +++ b/crates/utils/translations @@ -1 +1 @@ -Subproject commit 7adddded581fcd965ab33b91c5fe10e0d7247208 +Subproject commit dbb09b0784982827d5d9b7dcf39f1703c1212b83 diff --git a/diesel.toml b/diesel.toml index ce23d470e7..0da2434d4e 100644 --- a/diesel.toml +++ b/diesel.toml @@ -1,3 +1,5 @@ [print_schema] file = "crates/db_schema/src/schema.rs" patch_file = "crates/db_schema/src/diesel_ltree.patch" +# Required for https://github.com/adwhit/diesel-derive-enum +custom_type_derives = ["diesel::query_builder::QueryId"] diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index c1d8d359b9..cb438af3ac 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -23,7 +23,7 @@ services: lemmy: # use "image" to pull down an already compiled lemmy. make sure to comment out "build". - # image: dessalines/lemmy:0.19.5 + # image: dessalines/lemmy:0.19.6 # platform: linux/x86_64 # no arm64 support. uncomment platform if using m1. # use "build" to build your local lemmy server image for development. make sure to comment out "image". # run: docker compose up --build @@ -31,9 +31,9 @@ services: build: context: ../ dockerfile: docker/Dockerfile - # args: - # RUST_RELEASE_MODE: release - # CARGO_BUILD_FEATURES: default + # args: + # RUST_RELEASE_MODE: release + # CARGO_BUILD_FEATURES: default # this hostname is used in nginx reverse proxy and also for lemmy ui to connect to the backend, do not change hostname: lemmy restart: unless-stopped @@ -53,7 +53,7 @@ services: lemmy-ui: # use "image" to pull down an already compiled lemmy-ui. make sure to comment out "build". - image: dessalines/lemmy-ui:0.19.5 + image: dessalines/lemmy-ui:0.19.6 # platform: linux/x86_64 # no arm64 support. uncomment platform if using m1. # use "build" to build your local lemmy ui image for development. make sure to comment out "image". # run: docker compose up --build diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml index 090ad71a85..bc4b5ea7f1 100644 --- a/docker/federation/docker-compose.yml +++ b/docker/federation/docker-compose.yml @@ -2,7 +2,7 @@ version: "3.7" x-ui-default: &ui-default init: true - image: dessalines/lemmy-ui:0.19.5 + image: dessalines/lemmy-ui:0.19.6 # assuming lemmy-ui is cloned besides lemmy directory # build: # context: ../../../lemmy-ui diff --git a/migrations/2023-10-24-140438_enable_private_messages/down.sql b/migrations/2023-10-24-140438_enable_private_messages/down.sql new file mode 100644 index 0000000000..0b4846679e --- /dev/null +++ b/migrations/2023-10-24-140438_enable_private_messages/down.sql @@ -0,0 +1,3 @@ +ALTER TABLE local_user + DROP COLUMN enable_private_messages; + diff --git a/migrations/2023-10-24-140438_enable_private_messages/up.sql b/migrations/2023-10-24-140438_enable_private_messages/up.sql new file mode 100644 index 0000000000..a0c2919deb --- /dev/null +++ b/migrations/2023-10-24-140438_enable_private_messages/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE local_user + ADD COLUMN enable_private_messages boolean DEFAULT TRUE NOT NULL; + diff --git a/migrations/2024-08-03-155932_increase_post_url_max_length/down.sql b/migrations/2024-08-03-155932_increase_post_url_max_length/down.sql index d25918578e..7e4feef3d4 100644 --- a/migrations/2024-08-03-155932_increase_post_url_max_length/down.sql +++ b/migrations/2024-08-03-155932_increase_post_url_max_length/down.sql @@ -1,3 +1,5 @@ ALTER TABLE post ALTER COLUMN url TYPE varchar(512); +ANALYZE post (url); + diff --git a/migrations/2024-08-03-155932_increase_post_url_max_length/up.sql b/migrations/2024-08-03-155932_increase_post_url_max_length/up.sql index 7c6818d223..8e5a3a9ce1 100644 --- a/migrations/2024-08-03-155932_increase_post_url_max_length/up.sql +++ b/migrations/2024-08-03-155932_increase_post_url_max_length/up.sql @@ -3,3 +3,5 @@ ALTER TABLE post ALTER COLUMN url TYPE varchar(2000); +ANALYZE post (url); + diff --git a/migrations/2024-10-29-090055_private-community/down.sql b/migrations/2024-10-29-090055_private-community/down.sql new file mode 100644 index 0000000000..3a5c516c2b --- /dev/null +++ b/migrations/2024-10-29-090055_private-community/down.sql @@ -0,0 +1,52 @@ +-- Remove private visibility +ALTER TYPE community_visibility RENAME TO community_visibility__; + +CREATE TYPE community_visibility AS enum ( + 'Public', + 'LocalOnly' +); + +ALTER TABLE community + ALTER COLUMN visibility DROP DEFAULT; + +ALTER TABLE community + ALTER COLUMN visibility TYPE community_visibility + USING visibility::text::community_visibility; + +ALTER TABLE community + ALTER COLUMN visibility SET DEFAULT 'Public'; + +DROP TYPE community_visibility__; + +-- Revert community follower changes +CREATE OR REPLACE FUNCTION convert_follower_state (s community_follower_state) + RETURNS bool + LANGUAGE sql + AS $$ + SELECT + CASE WHEN s = 'Pending' THEN + TRUE + ELSE + FALSE + END +$$; + +ALTER TABLE community_follower + ALTER COLUMN state TYPE bool + USING convert_follower_state (state); + +DROP FUNCTION convert_follower_state; + +ALTER TABLE community_follower + ALTER COLUMN state SET DEFAULT FALSE; + +ALTER TABLE community_follower RENAME COLUMN state TO pending; + +DROP TYPE community_follower_state; + +ALTER TABLE community_follower + DROP COLUMN approver_id; + +ALTER TABLE ONLY local_site + ALTER COLUMN federation_signed_fetch SET DEFAULT FALSE; + diff --git a/migrations/2024-10-29-090055_private-community/up.sql b/migrations/2024-10-29-090055_private-community/up.sql new file mode 100644 index 0000000000..d1c0585ae0 --- /dev/null +++ b/migrations/2024-10-29-090055_private-community/up.sql @@ -0,0 +1,47 @@ +ALTER TYPE community_visibility + ADD value 'Private'; + +-- Change `community_follower.pending` to `state` enum +CREATE TYPE community_follower_state AS enum ( + 'Accepted', + 'Pending', + 'ApprovalRequired' +); + +ALTER TABLE community_follower + ALTER COLUMN pending DROP DEFAULT; + +CREATE OR REPLACE FUNCTION convert_follower_state (b bool) + RETURNS community_follower_state + LANGUAGE sql + AS $$ + SELECT + CASE WHEN b = TRUE THEN + 'Pending'::community_follower_state + ELSE + 'Accepted'::community_follower_state + END +$$; + +ALTER TABLE community_follower + ALTER COLUMN pending TYPE community_follower_state + USING convert_follower_state (pending); + +DROP FUNCTION convert_follower_state; + +ALTER TABLE community_follower RENAME COLUMN pending TO state; + +-- Add column for mod who approved the private community follower +-- Dont use foreign key here, otherwise joining to person table doesnt work easily +ALTER TABLE community_follower + ADD COLUMN approver_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE; + +-- Enable signed fetch, necessary to fetch content in private communities +ALTER TABLE ONLY local_site + ALTER COLUMN federation_signed_fetch SET DEFAULT TRUE; + +UPDATE + local_site +SET + federation_signed_fetch = TRUE; + diff --git a/migrations/2024-10-27-134311_smoosh-tables-together/down.sql b/migrations/2024-11-10-134311_smoosh-tables-together/down.sql similarity index 95% rename from migrations/2024-10-27-134311_smoosh-tables-together/down.sql rename to migrations/2024-11-10-134311_smoosh-tables-together/down.sql index 4103b63bdc..29b95d2cd7 100644 --- a/migrations/2024-10-27-134311_smoosh-tables-together/down.sql +++ b/migrations/2024-11-10-134311_smoosh-tables-together/down.sql @@ -190,7 +190,9 @@ ALTER TABLE comment_like RENAME COLUMN like_score TO score; ALTER TABLE community_follower RENAME COLUMN followed TO published; -ALTER TABLE community_follower RENAME COLUMN follow_pending TO pending; +ALTER TABLE community_follower RENAME COLUMN follow_state TO state; + +ALTER TABLE community_follower RENAME COLUMN follow_approver_id TO approver_id; ALTER TABLE instance_block RENAME COLUMN blocked TO published; @@ -216,14 +218,11 @@ ALTER TABLE community_follower DROP CONSTRAINT community_actions_check_received_ban, ALTER COLUMN published SET NOT NULL, ALTER COLUMN published SET DEFAULT now(), - ALTER COLUMN pending SET NOT NULL, - -- This `SET DEFAULT` is done for community follow, but not person follow. It's not a mistake - -- in this migration. Believe it or not, `pending` only had a default value in community follow. - ALTER COLUMN pending SET DEFAULT FALSE, - DROP COLUMN blocked, - DROP COLUMN became_moderator, - DROP COLUMN received_ban, - DROP COLUMN ban_expires; + ALTER COLUMN state SET NOT NULL, + DROP COLUMN blocked, + DROP COLUMN became_moderator, + DROP COLUMN received_ban, + DROP COLUMN ban_expires; ALTER TABLE instance_block ALTER COLUMN published SET NOT NULL, @@ -265,6 +264,8 @@ ALTER TABLE community_follower RENAME CONSTRAINT community_actions_community_id_ ALTER TABLE community_follower RENAME CONSTRAINT community_actions_person_id_fkey TO community_follower_person_id_fkey; +ALTER TABLE community_follower RENAME CONSTRAINT community_actions_follow_approver_id_fkey TO community_follower_approver_id_fkey; + ALTER INDEX instance_actions_pkey RENAME TO instance_block_pkey; ALTER TABLE instance_block RENAME CONSTRAINT instance_actions_instance_id_fkey TO instance_block_instance_id_fkey; diff --git a/migrations/2024-10-27-134311_smoosh-tables-together/up.sql b/migrations/2024-11-10-134311_smoosh-tables-together/up.sql similarity index 92% rename from migrations/2024-10-27-134311_smoosh-tables-together/up.sql rename to migrations/2024-11-10-134311_smoosh-tables-together/up.sql index 51d609ba37..aadf956924 100644 --- a/migrations/2024-10-27-134311_smoosh-tables-together/up.sql +++ b/migrations/2024-11-10-134311_smoosh-tables-together/up.sql @@ -17,7 +17,9 @@ ALTER TABLE comment_actions RENAME COLUMN score TO like_score; ALTER TABLE community_actions RENAME COLUMN published TO followed; -ALTER TABLE community_actions RENAME COLUMN pending TO follow_pending; +ALTER TABLE community_actions RENAME COLUMN state TO follow_state; + +ALTER TABLE community_actions RENAME COLUMN approver_id TO follow_approver_id; ALTER TABLE instance_actions RENAME COLUMN published TO blocked; @@ -41,16 +43,13 @@ ALTER TABLE comment_actions ALTER TABLE community_actions ALTER COLUMN followed DROP NOT NULL, ALTER COLUMN followed DROP DEFAULT, - ALTER COLUMN follow_pending DROP NOT NULL, - -- This `DROP DEFAULT` is done for community follow, but not person follow. It's not a mistake - -- in this migration. Believe it or not, `pending` only had a default value in community follow. - ALTER COLUMN follow_pending DROP DEFAULT, - ADD COLUMN blocked timestamptz, - ADD COLUMN became_moderator timestamptz, - ADD COLUMN received_ban timestamptz, - ADD COLUMN ban_expires timestamptz, - ADD CONSTRAINT community_actions_check_followed CHECK ((followed IS NULL) = (follow_pending IS NULL)), - ADD CONSTRAINT community_actions_check_received_ban CHECK (NOT (received_ban IS NULL AND ban_expires IS NOT NULL)); + ALTER COLUMN follow_state DROP NOT NULL, + ADD COLUMN blocked timestamptz, + ADD COLUMN became_moderator timestamptz, + ADD COLUMN received_ban timestamptz, + ADD COLUMN ban_expires timestamptz, + ADD CONSTRAINT community_actions_check_followed CHECK ((followed IS NULL) = (follow_state IS NULL) AND NOT (followed IS NULL AND follow_approver_id IS NOT NULL)), + ADD CONSTRAINT community_actions_check_received_ban CHECK (NOT (received_ban IS NULL AND ban_expires IS NOT NULL)); ALTER TABLE instance_actions ALTER COLUMN blocked DROP NOT NULL, @@ -218,6 +217,8 @@ ALTER TABLE community_actions RENAME CONSTRAINT community_follower_community_id_ ALTER TABLE community_actions RENAME CONSTRAINT community_follower_person_id_fkey TO community_actions_person_id_fkey; +ALTER TABLE community_actions RENAME CONSTRAINT community_follower_approver_id_fkey TO community_actions_follow_approver_id_fkey; + ALTER INDEX instance_block_pkey RENAME TO instance_actions_pkey; ALTER TABLE instance_actions RENAME CONSTRAINT instance_block_instance_id_fkey TO instance_actions_instance_id_fkey; @@ -268,7 +269,7 @@ WHERE CREATE INDEX idx_community_actions_followed_not_null ON community_actions (person_id, community_id) WHERE - followed IS NOT NULL OR follow_pending IS NOT NULL; + followed IS NOT NULL OR follow_state IS NOT NULL; CREATE INDEX idx_community_actions_blocked_not_null ON community_actions (person_id, community_id) WHERE @@ -323,7 +324,7 @@ WHERE CREATE statistics comment_actions_liked_stat ON (liked IS NULL), (like_score IS NULL) FROM comment_actions; -CREATE statistics community_actions_followed_stat ON (followed IS NULL), (follow_pending IS NULL) +CREATE statistics community_actions_followed_stat ON (followed IS NULL), (follow_state IS NULL) FROM community_actions; CREATE statistics person_actions_followed_stat ON (followed IS NULL), (follow_pending IS NULL) diff --git a/scripts/start_dev_db.sh b/scripts/start_dev_db.sh index 5965316ba5..1cbe9e16ab 100644 --- a/scripts/start_dev_db.sh +++ b/scripts/start_dev_db.sh @@ -2,8 +2,12 @@ export PGDATA="$PWD/dev_pgdata" export PGHOST=$PWD + +# Necessary to encode the dev db path into proper URL params +export ENCODED_HOST=$(printf $PWD | jq -sRr @uri) + export PGUSER=postgres -export DATABASE_URL="postgresql://lemmy:password@/lemmy?host=$PWD" +export DATABASE_URL="postgresql://lemmy:password@$ENCODED_HOST/lemmy" export LEMMY_DATABASE_URL=$DATABASE_URL export PGDATABASE=lemmy diff --git a/scripts/test.sh b/scripts/test.sh index e08148db06..d3e95886ff 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -20,9 +20,7 @@ if [ -n "$PACKAGE" ]; then cargo test -p $PACKAGE --all-features --no-fail-fast $TEST else - cargo test --workspace --no-fail-fast - # Testing lemmy utils all features in particular (for ts-rs bindings) - cargo test -p lemmy_utils --all-features --no-fail-fast + cargo test --workspace --all-features --no-fail-fast fi # Add this to do printlns: -- --nocapture diff --git a/scripts/ts_bindings_check.sh b/scripts/ts_bindings_check.sh new file mode 100755 index 0000000000..a925081c83 --- /dev/null +++ b/scripts/ts_bindings_check.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +# This check is only used for CI. + +CWD="$(cd -P -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)" + +cd "$CWD/../" + +# Export the ts-rs bindings +cargo test --workspace export_bindings + +# Make sure no rows are returned +! grep -nr --include=\*.ts ' | null' ./crates/ diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index df1aebf84f..fd65e0671e 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -17,6 +17,11 @@ use lemmy_api::{ block::block_community, follow::follow_community, hide::hide_community, + pending_follows::{ + approve::post_pending_follows_approve, + count::get_pending_follows_count, + list::get_pending_follows_list, + }, random::get_random_community, transfer::transfer_community, }, @@ -204,7 +209,14 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/remove", web::post().to(remove_community)) .route("/transfer", web::post().to(transfer_community)) .route("/ban_user", web::post().to(ban_from_community)) - .route("/mod", web::post().to(add_mod_to_community)), + .route("/mod", web::post().to(add_mod_to_community)) + .service( + web::scope("/pending_follows") + .wrap(rate_limit.message()) + .route("/count", web::get().to(get_pending_follows_count)) + .route("/list", web::get().to(get_pending_follows_list)) + .route("/approve", web::post().to(post_pending_follows_approve)), + ), ) .service( web::scope("/federated_instances") diff --git a/src/lib.rs b/src/lib.rs index 4aee4be69a..9da09f65be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -116,7 +116,7 @@ pub async fn start_lemmy_server(args: CmdArgs) -> LemmyResult<()> { } // Set up the connection pool - let pool = build_db_pool().await?; + let pool = build_db_pool()?; // Run the Code-required migrations run_advanced_migrations(&mut (&pool).into(), &SETTINGS).await?; diff --git a/src/prometheus_metrics.rs b/src/prometheus_metrics.rs index c4ab204e7a..512d63f389 100644 --- a/src/prometheus_metrics.rs +++ b/src/prometheus_metrics.rs @@ -47,7 +47,7 @@ pub fn serve_prometheus(config: PrometheusConfig, lemmy_context: LemmyContext) - // handler for the /metrics path async fn metrics(context: web::Data>) -> LemmyResult { // collect metrics - collect_db_pool_metrics(&context).await; + collect_db_pool_metrics(&context); let mut buffer = Vec::new(); let encoder = TextEncoder::new(); @@ -84,7 +84,7 @@ fn create_db_pool_metrics() -> LemmyResult { Ok(metrics) } -async fn collect_db_pool_metrics(context: &PromContext) { +fn collect_db_pool_metrics(context: &PromContext) { let pool_status = context.lemmy.inner_pool().status(); context .db_pool_metrics diff --git a/src/scheduled_tasks.rs b/src/scheduled_tasks.rs index 6c9e8e5423..148df00fea 100644 --- a/src/scheduled_tasks.rs +++ b/src/scheduled_tasks.rs @@ -401,10 +401,10 @@ async fn active_counts(pool: &mut DbPool<'_>) { ("6 months", "half_year"), ]; - for i in &intervals { + for (full_form, abbr) in &intervals { let update_site_stmt = format!( "update site_aggregates set users_active_{} = (select * from site_aggregates_activity('{}')) where site_id = 1", - i.1, i.0 + abbr, full_form ); sql_query(update_site_stmt) .execute(&mut conn) @@ -412,7 +412,7 @@ async fn active_counts(pool: &mut DbPool<'_>) { .inspect_err(|e| error!("Failed to update site stats: {e}")) .ok(); - let update_community_stmt = format!("update community_aggregates ca set users_active_{} = mv.count_ from community_aggregates_activity('{}') mv where ca.community_id = mv.community_id_", i.1, i.0); + let update_community_stmt = format!("update community_aggregates ca set users_active_{} = mv.count_ from community_aggregates_activity('{}') mv where ca.community_id = mv.community_id_", abbr, full_form); sql_query(update_community_stmt) .execute(&mut conn) .await @@ -503,7 +503,6 @@ async fn publish_scheduled_posts(context: &Data) { // send out post via federation and webmention let send_activity = SendActivityData::CreatePost(post.clone()); ActivityChannel::submit_activity(send_activity, context) - .await .inspect_err(|e| error!("Failed federate scheduled post: {e}")) .ok(); send_webmention(post, community); @@ -616,7 +615,10 @@ mod tests { use crate::scheduled_tasks::build_update_instance_form; use lemmy_api_common::request::client_builder; - use lemmy_utils::{error::LemmyResult, settings::structs::Settings, LemmyErrorType}; + use lemmy_utils::{ + error::{LemmyErrorType, LemmyResult}, + settings::structs::Settings, + }; use pretty_assertions::assert_eq; use reqwest_middleware::ClientBuilder; use serial_test::serial; diff --git a/src/session_middleware.rs b/src/session_middleware.rs index ec8f4399c3..b495bdbb94 100644 --- a/src/session_middleware.rs +++ b/src/session_middleware.rs @@ -125,7 +125,7 @@ mod tests { // hack, necessary so that config file can be loaded from hardcoded, relative path set_current_dir("crates/utils")?; - let pool_ = build_db_pool_for_tests().await; + let pool_ = build_db_pool_for_tests(); let pool = &mut (&pool_).into(); let secret = Secret::init(pool).await?;