From 25920ad8b46c019f6c4fe153b7ea92f57e159d94 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Mon, 20 Jan 2025 16:16:28 -0500 Subject: [PATCH] Adding search_combined.score column, and DB triggers. --- crates/api_common/src/site.rs | 4 +- crates/apub/src/api/search.rs | 2 +- .../db_schema/replaceable_schema/triggers.sql | 90 +++++ crates/db_schema/src/lib.rs | 18 +- crates/db_schema/src/schema.rs | 1 + .../db_schema/src/source/combined/search.rs | 1 + .../src/combined/search_combined_view.rs | 127 +++++-- crates/db_views_actor/Cargo.toml | 50 --- crates/db_views_actor/src/lib.rs | 15 - crates/db_views_moderator/Cargo.toml | 47 --- crates/db_views_moderator/src/structs.rs | 332 ------------------ .../up.sql | 41 ++- 12 files changed, 238 insertions(+), 490 deletions(-) delete mode 100644 crates/db_views_actor/Cargo.toml delete mode 100644 crates/db_views_actor/src/lib.rs delete mode 100644 crates/db_views_moderator/Cargo.toml delete mode 100644 crates/db_views_moderator/src/structs.rs diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index 3ed7fb9fa..25dcf90b3 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -27,6 +27,7 @@ use lemmy_db_schema::{ PostListingMode, PostSortType, RegistrationMode, + SearchSortType, SearchType, }; use lemmy_db_views::structs::{ @@ -66,8 +67,7 @@ pub struct Search { #[cfg_attr(feature = "full", ts(optional))] pub type_: Option, #[cfg_attr(feature = "full", ts(optional))] - // TODO - pub sort: Option, + pub sort: Option, #[cfg_attr(feature = "full", ts(optional))] pub listing_type: Option, #[cfg_attr(feature = "full", ts(optional))] diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index 160fef731..41e1ded39 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -48,7 +48,7 @@ pub async fn search( community_id, creator_id: data.creator_id, type_: data.type_, - // TODO add sorts + sort: data.sort, listing_type: data.listing_type, title_only: data.title_only, post_url_only: data.post_url_only, diff --git a/crates/db_schema/replaceable_schema/triggers.sql b/crates/db_schema/replaceable_schema/triggers.sql index f58bbbd53..915504715 100644 --- a/crates/db_schema/replaceable_schema/triggers.sql +++ b/crates/db_schema/replaceable_schema/triggers.sql @@ -922,3 +922,93 @@ CALL r.create_search_combined_trigger ('community'); CALL r.create_search_combined_trigger ('person'); +-- You also need to triggers to update the `score` column. +-- post | post_aggregates::score +-- comment | comment_aggregates::score +-- community | community_aggregates::users_active_monthly +-- person | person_aggregates::post_score +-- +-- Post score +CREATE FUNCTION r.search_combined_post_score_update () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + search_combined + SET + score = NEW.score + WHERE + post_id = NEW.post_id; + RETURN NULL; +END +$$; + +CREATE TRIGGER search_combined_post_score + AFTER UPDATE OF score ON post_aggregates + FOR EACH ROW + EXECUTE FUNCTION r.search_combined_post_score_update (); + +-- Comment score +CREATE FUNCTION r.search_combined_comment_score_update () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + search_combined + SET + score = NEW.score + WHERE + comment_id = NEW.comment_id; + RETURN NULL; +END +$$; + +CREATE TRIGGER search_combined_comment_score + AFTER UPDATE OF score ON comment_aggregates + FOR EACH ROW + EXECUTE FUNCTION r.search_combined_comment_score_update (); + +-- Person score +CREATE FUNCTION r.search_combined_person_score_update () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + search_combined + SET + score = NEW.post_score + WHERE + person_id = NEW.person_id; + RETURN NULL; +END +$$; + +CREATE TRIGGER search_combined_person_score + AFTER UPDATE OF post_score ON person_aggregates + FOR EACH ROW + EXECUTE FUNCTION r.search_combined_person_score_update (); + +-- Community score +CREATE FUNCTION r.search_combined_community_score_update () + RETURNS TRIGGER + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE + search_combined + SET + score = NEW.users_active_month + WHERE + community_id = NEW.community_id; + RETURN NULL; +END +$$; + +CREATE TRIGGER search_combined_community_score + AFTER UPDATE OF users_active_month ON community_aggregates + FOR EACH ROW + EXECUTE FUNCTION r.search_combined_community_score_update (); + diff --git a/crates/db_schema/src/lib.rs b/crates/db_schema/src/lib.rs index c077f758b..9de0e357f 100644 --- a/crates/db_schema/src/lib.rs +++ b/crates/db_schema/src/lib.rs @@ -101,6 +101,19 @@ pub enum CommentSortType { Controversial, } +#[derive( + EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash, +)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The search sort types. +pub enum SearchSortType { + #[default] + New, + Top, + Old, +} + #[derive( EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Default, Hash, )] @@ -166,11 +179,14 @@ pub enum PostListingMode { SmallCard, } -#[derive(EnumString, Display, Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Hash)] +#[derive( + EnumString, Display, Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Hash, +)] #[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", ts(export))] /// The type of content returned from a search. pub enum SearchType { + #[default] All, Comments, Posts, diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 2e229ce85..84a14bf22 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -999,6 +999,7 @@ diesel::table! { search_combined (id) { id -> Int4, published -> Timestamptz, + score -> Int8, post_id -> Nullable, comment_id -> Nullable, community_id -> Nullable, diff --git a/crates/db_schema/src/source/combined/search.rs b/crates/db_schema/src/source/combined/search.rs index f5b2f9a5d..fe7387fea 100644 --- a/crates/db_schema/src/source/combined/search.rs +++ b/crates/db_schema/src/source/combined/search.rs @@ -20,6 +20,7 @@ use serde_with::skip_serializing_none; pub struct SearchCombined { pub id: SearchCombinedId, pub published: DateTime, + pub score: i64, pub post_id: Option, pub comment_id: Option, pub community_id: Option, diff --git a/crates/db_views/src/combined/search_combined_view.rs b/crates/db_views/src/combined/search_combined_view.rs index 1d391c5b9..29ee3e7e1 100644 --- a/crates/db_views/src/combined/search_combined_view.rs +++ b/crates/db_views/src/combined/search_combined_view.rs @@ -47,12 +47,22 @@ use lemmy_db_schema::{ combined::search::{search_combined_keys as key, SearchCombined}, community::CommunityFollower, }, - utils::{actions, actions_alias, functions::coalesce, fuzzy_search, get_conn, DbPool}, + utils::{ + actions, + actions_alias, + functions::coalesce, + fuzzy_search, + get_conn, + DbPool, + ReverseTimestampKey, + }, InternalToCombinedView, ListingType, + SearchSortType, SearchType, }; use lemmy_utils::error::LemmyResult; +use SearchSortType::*; impl SearchCombinedPaginationCursor { // get cursor for page that starts immediately after the given post @@ -96,8 +106,7 @@ pub struct SearchCombinedQuery { pub community_id: Option, pub creator_id: Option, pub type_: Option, - // TODO sorts need to be added - // pub sort: Option, + pub sort: Option, pub listing_type: Option, pub title_only: Option, pub post_url_only: Option, @@ -314,15 +323,13 @@ impl SearchCombinedQuery { }; // Type - if let Some(type_) = self.type_ { - query = match type_ { - SearchType::All => query, - SearchType::Posts => query.filter(search_combined::post_id.is_not_null()), - SearchType::Comments => query.filter(search_combined::comment_id.is_not_null()), - SearchType::Communities => query.filter(search_combined::community_id.is_not_null()), - SearchType::Users => query.filter(search_combined::person_id.is_not_null()), - } - } + query = match self.type_.unwrap_or_default() { + SearchType::All => query, + SearchType::Posts => query.filter(search_combined::post_id.is_not_null()), + SearchType::Comments => query.filter(search_combined::comment_id.is_not_null()), + SearchType::Communities => query.filter(search_combined::community_id.is_not_null()), + SearchType::Users => query.filter(search_combined::person_id.is_not_null()), + }; // Listing type let is_subscribed = community_actions::followed.is_not_null(); @@ -359,11 +366,13 @@ impl SearchCombinedQuery { query = query.after(page_after); } - // Sorting by published - query = query - .then_desc(key::published) - // Tie breaker - .then_desc(key::id); + query = match self.sort.unwrap_or_default() { + New => query.then_desc(key::published), + Old => query.then_desc(ReverseTimestampKey(key::published)), + Top => query.then_desc(key::score), + }; + // finally use unique id as tie breaker + query = query.then_desc(key::id); let res = query.load::(conn).await?; @@ -479,6 +488,7 @@ mod tests { }, traits::{Crud, Likeable}, utils::{build_db_pool_for_tests, DbPool}, + SearchSortType, SearchType, }; use lemmy_utils::error::LemmyResult; @@ -504,6 +514,9 @@ mod tests { async fn init_data(pool: &mut DbPool<'_>) -> LemmyResult { let instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?; + let sara_form = PersonInsertForm::test_form(instance.id, "sara_pcv"); + let sara = Person::create(pool, &sara_form).await?; + let timmy_form = PersonInsertForm::test_form(instance.id, "timmy_pcv"); let timmy = Person::create(pool, &timmy_form).await?; let timmy_local_user_form = LocalUserInsertForm::test_form(timmy.id); @@ -515,9 +528,6 @@ mod tests { counts: Default::default(), }; - let sara_form = PersonInsertForm::test_form(instance.id, "sara_pcv"); - let sara = Person::create(pool, &sara_form).await?; - let community_form = CommunityInsertForm { description: Some("ask lemmy things".into()), ..CommunityInsertForm::new( @@ -685,13 +695,13 @@ mod tests { } if let SearchCombinedView::Person(v) = &search[8] { - assert_eq!(data.sara.id, v.person.id); + assert_eq!(data.timmy.id, v.person.id); } else { panic!("wrong type"); } if let SearchCombinedView::Person(v) = &search[9] { - assert_eq!(data.timmy.id, v.person.id); + assert_eq!(data.sara.id, v.person.id); } else { panic!("wrong type"); } @@ -743,6 +753,21 @@ mod tests { assert_length!(1, search_disliked_only); + // Test sorts + // Test Old sort + let search_old_sort = SearchCombinedQuery { + sort: Some(SearchSortType::Old), + ..Default::default() + } + .list(pool, &Some(data.timmy_view.clone())) + .await?; + if let SearchCombinedView::Person(v) = &search_old_sort[0] { + assert_eq!(data.sara.id, v.person.id); + } else { + panic!("wrong type"); + } + assert_length!(10, search_old_sort); + // Remove a post and delete a comment Post::update( pool, @@ -865,13 +890,13 @@ mod tests { // Make sure the types are correct if let SearchCombinedView::Person(v) = &person_search[0] { - assert_eq!(data.sara.id, v.person.id); + assert_eq!(data.timmy.id, v.person.id); } else { panic!("wrong type"); } if let SearchCombinedView::Person(v) = &person_search[1] { - assert_eq!(data.timmy.id, v.person.id); + assert_eq!(data.sara.id, v.person.id); } else { panic!("wrong type"); } @@ -907,6 +932,23 @@ mod tests { panic!("wrong type"); } + // Test Top sorting (uses post score) + let person_search_sort_top = SearchCombinedQuery { + type_: Some(SearchType::Users), + sort: Some(SearchSortType::Top), + ..Default::default() + } + .list(pool, &None) + .await?; + assert_length!(2, person_search_sort_top); + + // Sara should be first, as she has a higher score + if let SearchCombinedView::Person(v) = &person_search_sort_top[0] { + assert_eq!(data.sara.id, v.person.id); + } else { + panic!("wrong type"); + } + cleanup(data, pool).await?; Ok(()) @@ -1020,6 +1062,24 @@ mod tests { // Should be zero because you disliked your own post assert_length!(0, post_search_disliked_only); + // Test top sort + let post_search_sort_top = SearchCombinedQuery { + type_: Some(SearchType::Posts), + sort: Some(SearchSortType::Top), + ..Default::default() + } + .list(pool, &None) + .await?; + assert_length!(3, post_search_sort_top); + + // Timmy_post_2 has a dislike, so it should be last + if let SearchCombinedView::Post(v) = &post_search_sort_top[2] { + assert_eq!(data.timmy_post_2.id, v.post.id); + assert_eq!(data.community.id, v.community.id); + } else { + panic!("wrong type"); + } + cleanup(data, pool).await?; Ok(()) @@ -1108,6 +1168,25 @@ mod tests { assert_length!(1, comment_search_disliked_only); + // Test top sort + let comment_search_sort_top = SearchCombinedQuery { + type_: Some(SearchType::Comments), + sort: Some(SearchSortType::Top), + ..Default::default() + } + .list(pool, &None) + .await?; + assert_length!(3, comment_search_sort_top); + + // Sara comment 2 is disliked, so should be last + if let SearchCombinedView::Comment(v) = &comment_search_sort_top[2] { + assert_eq!(data.sara_comment_2.id, v.comment.id); + assert_eq!(data.timmy_post_2.id, v.post.id); + assert_eq!(data.community.id, v.community.id); + } else { + panic!("wrong type"); + } + cleanup(data, pool).await?; Ok(()) diff --git a/crates/db_views_actor/Cargo.toml b/crates/db_views_actor/Cargo.toml deleted file mode 100644 index 34d64d0b0..000000000 --- a/crates/db_views_actor/Cargo.toml +++ /dev/null @@ -1,50 +0,0 @@ -[package] -name = "lemmy_db_views_actor" -version.workspace = true -edition.workspace = true -description.workspace = true -license.workspace = true -homepage.workspace = true -documentation.workspace = true -repository.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[features] -full = [ - "lemmy_db_schema/full", - "lemmy_utils/full", - "i-love-jesus", - "diesel", - "diesel-async", - "ts-rs", -] - -[dependencies] -lemmy_db_schema = { workspace = true } -diesel = { workspace = true, features = [ - "chrono", - "postgres", - "serde_json", -], optional = true } -diesel-async = { workspace = true, features = [ - "deadpool", - "postgres", -], optional = true } -serde = { workspace = true } -serde_with = { workspace = true } -ts-rs = { workspace = true, optional = true } -chrono.workspace = true -strum = { workspace = true } -lemmy_utils = { workspace = true, optional = true } -i-love-jesus = { workspace = true, optional = true } - -[dev-dependencies] -serial_test = { workspace = true } -tokio = { workspace = true } -pretty_assertions = { workspace = true } -url.workspace = true diff --git a/crates/db_views_actor/src/lib.rs b/crates/db_views_actor/src/lib.rs deleted file mode 100644 index 3f3991734..000000000 --- a/crates/db_views_actor/src/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -#[cfg(feature = "full")] -pub mod community_follower_view; -#[cfg(feature = "full")] -pub mod community_moderator_view; -#[cfg(feature = "full")] -pub mod community_person_ban_view; -#[cfg(feature = "full")] -pub mod community_view; -#[cfg(feature = "full")] -pub mod inbox_combined_view; -#[cfg(feature = "full")] -pub mod person_view; -#[cfg(feature = "full")] -pub mod private_message_view; -pub mod structs; diff --git a/crates/db_views_moderator/Cargo.toml b/crates/db_views_moderator/Cargo.toml deleted file mode 100644 index a7257c4f1..000000000 --- a/crates/db_views_moderator/Cargo.toml +++ /dev/null @@ -1,47 +0,0 @@ -[package] -name = "lemmy_db_views_moderator" -version.workspace = true -edition.workspace = true -description.workspace = true -license.workspace = true -homepage.workspace = true -documentation.workspace = true -repository.workspace = true - -[lib] -doctest = false - -[lints] -workspace = true - -[features] -full = [ - "lemmy_db_schema/full", - "lemmy_utils", - "i-love-jesus", - "diesel", - "diesel-async", - "ts-rs", -] - -[dependencies] -lemmy_db_schema = { workspace = true } -lemmy_utils = { workspace = true, optional = true } -i-love-jesus = { workspace = true, optional = true } -diesel = { workspace = true, features = [ - "chrono", - "postgres", - "serde_json", -], optional = true } -diesel-async = { workspace = true, features = [ - "deadpool", - "postgres", -], optional = true } -serde = { workspace = true } -serde_with = { workspace = true } -ts-rs = { workspace = true, optional = true } - -[dev-dependencies] -serial_test = { workspace = true } -tokio = { workspace = true } -pretty_assertions = { workspace = true } diff --git a/crates/db_views_moderator/src/structs.rs b/crates/db_views_moderator/src/structs.rs deleted file mode 100644 index 513a79705..000000000 --- a/crates/db_views_moderator/src/structs.rs +++ /dev/null @@ -1,332 +0,0 @@ -#[cfg(feature = "full")] -use diesel::Queryable; -use lemmy_db_schema::source::{ - comment::Comment, - community::Community, - instance::Instance, - mod_log::{ - admin::{ - AdminAllowInstance, - AdminBlockInstance, - AdminPurgeComment, - AdminPurgeCommunity, - AdminPurgePerson, - AdminPurgePost, - }, - moderator::{ - ModAdd, - ModAddCommunity, - ModBan, - ModBanFromCommunity, - ModFeaturePost, - ModHideCommunity, - ModLockPost, - ModRemoveComment, - ModRemoveCommunity, - ModRemovePost, - ModTransferCommunity, - }, - }, - person::Person, - post::Post, -}; -use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; -#[cfg(feature = "full")] -use ts_rs::TS; - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// 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 other_person: Person, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// 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 other_person: Person, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// 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 other_person: Person, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// When someone is banned from the site. -pub struct ModBanView { - pub mod_ban: ModBan, - #[cfg_attr(feature = "full", ts(optional))] - pub moderator: Option, - pub other_person: Person, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// 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, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// 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 other_person: Person, - pub post: Post, - pub community: Community, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// When a moderator removes a comment. -pub struct ModRemoveCommentView { - pub mod_remove_comment: ModRemoveComment, - #[cfg_attr(feature = "full", ts(optional))] - pub moderator: Option, - pub other_person: Person, - pub comment: Comment, - pub post: Post, - pub community: Community, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// 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, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// When a moderator removes a post. -pub struct ModRemovePostView { - pub mod_remove_post: ModRemovePost, - #[cfg_attr(feature = "full", ts(optional))] - pub moderator: Option, - pub other_person: Person, - pub post: Post, - pub community: Community, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// 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 other_person: Person, - pub post: Post, - pub community: Community, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// 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 other_person: Person, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// 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, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// When an admin purges a community. -pub struct AdminPurgeCommunityView { - pub admin_purge_community: AdminPurgeCommunity, - #[cfg_attr(feature = "full", ts(optional))] - pub admin: Option, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// When an admin purges a person. -pub struct AdminPurgePersonView { - pub admin_purge_person: AdminPurgePerson, - #[cfg_attr(feature = "full", ts(optional))] - pub admin: Option, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// 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, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// When an admin purges a post. -pub struct AdminBlockInstanceView { - pub admin_block_instance: AdminBlockInstance, - pub instance: Instance, - #[cfg_attr(feature = "full", ts(optional))] - pub admin: Option, -} - -#[skip_serializing_none] -#[derive(Debug, PartialEq, 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))] -/// When an admin purges a post. -pub struct AdminAllowInstanceView { - pub admin_allow_instance: AdminAllowInstance, - pub instance: Instance, - #[cfg_attr(feature = "full", ts(optional))] - pub admin: Option, -} - -/// like PaginationCursor but for the modlog_combined -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -pub struct ModlogCombinedPaginationCursor(pub String); - -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(Queryable))] -#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] -/// A combined modlog view -pub struct ModlogCombinedViewInternal { - // Specific - pub admin_allow_instance: Option, - pub admin_block_instance: Option, - pub admin_purge_comment: Option, - pub admin_purge_community: Option, - pub admin_purge_person: Option, - pub admin_purge_post: Option, - pub mod_add: Option, - pub mod_add_community: Option, - pub mod_ban: Option, - pub mod_ban_from_community: Option, - pub mod_feature_post: Option, - pub mod_hide_community: Option, - pub mod_lock_post: Option, - pub mod_remove_comment: Option, - pub mod_remove_community: Option, - pub mod_remove_post: Option, - pub mod_transfer_community: Option, - // Specific fields - - // Shared - pub moderator: Option, - pub other_person: Option, - pub instance: Option, - pub community: Option, - pub post: Option, - pub comment: Option, -} - -#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] -#[cfg_attr(feature = "full", derive(TS))] -#[cfg_attr(feature = "full", ts(export))] -// Use serde's internal tagging, to work easier with javascript libraries -#[serde(tag = "type_")] -pub enum ModlogCombinedView { - AdminAllowInstance(AdminAllowInstanceView), - AdminBlockInstance(AdminBlockInstanceView), - AdminPurgeComment(AdminPurgeCommentView), - AdminPurgeCommunity(AdminPurgeCommunityView), - AdminPurgePerson(AdminPurgePersonView), - AdminPurgePost(AdminPurgePostView), - ModAdd(ModAddView), - ModAddCommunity(ModAddCommunityView), - ModBan(ModBanView), - ModBanFromCommunity(ModBanFromCommunityView), - ModFeaturePost(ModFeaturePostView), - ModHideCommunity(ModHideCommunityView), - ModLockPost(ModLockPostView), - ModRemoveComment(ModRemoveCommentView), - ModRemoveCommunity(ModRemoveCommunityView), - ModRemovePost(ModRemovePostView), - ModTransferCommunity(ModTransferCommunityView), -} diff --git a/migrations/2024-12-12-222846_add_search_combined_table/up.sql b/migrations/2024-12-12-222846_add_search_combined_table/up.sql index d21cc9122..31d82115a 100644 --- a/migrations/2024-12-12-222846_add_search_combined_table/up.sql +++ b/migrations/2024-12-12-222846_add_search_combined_table/up.sql @@ -3,15 +3,12 @@ CREATE TABLE search_combined ( id serial PRIMARY KEY, published timestamptz NOT NULL, - -- TODO Need to figure out all the possible sort types, unified into SearchSortType - -- This is difficult because other than published, there is no unified way to sort them. - -- - -- All have published. - -- post and comment have top and time-limited scores and ranks. - -- persons have post and comment counts, and scores (not time-limited). - -- communities have subscribers, post and comment counts, and active users per X time. - -- - -- I'm thinking just published and score (and use active_monthly users as the community score), is the best way to start. + -- This is used for the top sort + -- For persons: its post score + -- For comments: score, + -- For posts: score, + -- For community: users active monthly + score bigint NOT NULL DEFAULT 0, post_id int UNIQUE REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE, comment_id int UNIQUE REFERENCES COMMENT ON UPDATE CASCADE ON DELETE CASCADE, community_id int UNIQUE REFERENCES community ON UPDATE CASCADE ON DELETE CASCADE, @@ -25,40 +22,48 @@ CREATE INDEX idx_search_combined_published ON search_combined (published DESC, i CREATE INDEX idx_search_combined_published_asc ON search_combined (reverse_timestamp_sort (published) DESC, id DESC); -- Updating the history -INSERT INTO search_combined (published, post_id, comment_id, community_id, person_id) +INSERT INTO search_combined (published, score, post_id, comment_id, community_id, person_id) SELECT - published, + p.published, + score, id, NULL::int, NULL::int, NULL::int FROM - post + post p + INNER JOIN post_aggregates pa ON p.id = pa.post_id UNION ALL SELECT - published, + c.published, + score, NULL::int, id, NULL::int, NULL::int FROM - comment + comment c + INNER JOIN comment_aggregates ca ON c.id = ca.comment_id UNION ALL SELECT - published, + c.published, + users_active_month, NULL::int, NULL::int, id, NULL::int FROM - community + community c + INNER JOIN community_aggregates ca ON c.id = ca.community_id UNION ALL SELECT - published, + p.published, + post_score, NULL::int, NULL::int, NULL::int, id FROM - person; + person p + INNER JOIN person_aggregates pa ON p.id = pa.person_id;