Adding search_combined.score column, and DB triggers.

This commit is contained in:
Dessalines 2025-01-20 16:16:28 -05:00
parent 7e6ca820fc
commit 25920ad8b4
12 changed files with 238 additions and 490 deletions

View file

@ -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<SearchType>,
#[cfg_attr(feature = "full", ts(optional))]
// TODO
pub sort: Option<PostSortType>,
pub sort: Option<SearchSortType>,
#[cfg_attr(feature = "full", ts(optional))]
pub listing_type: Option<ListingType>,
#[cfg_attr(feature = "full", ts(optional))]

View file

@ -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,

View file

@ -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 ();

View file

@ -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,

View file

@ -999,6 +999,7 @@ diesel::table! {
search_combined (id) {
id -> Int4,
published -> Timestamptz,
score -> Int8,
post_id -> Nullable<Int4>,
comment_id -> Nullable<Int4>,
community_id -> Nullable<Int4>,

View file

@ -20,6 +20,7 @@ use serde_with::skip_serializing_none;
pub struct SearchCombined {
pub id: SearchCombinedId,
pub published: DateTime<Utc>,
pub score: i64,
pub post_id: Option<PostId>,
pub comment_id: Option<CommentId>,
pub community_id: Option<CommunityId>,

View file

@ -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<CommunityId>,
pub creator_id: Option<PersonId>,
pub type_: Option<SearchType>,
// TODO sorts need to be added
// pub sort: Option<PostSortType>,
pub sort: Option<SearchSortType>,
pub listing_type: Option<ListingType>,
pub title_only: Option<bool>,
pub post_url_only: Option<bool>,
@ -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::<SearchCombinedViewInternal>(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<Data> {
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(())

View file

@ -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

View file

@ -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;

View file

@ -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 }

View file

@ -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<Person>,
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<Person>,
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<Person>,
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<Person>,
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<Person>,
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<Person>,
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<Person>,
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<Person>,
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<Person>,
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<Person>,
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<Person>,
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<Person>,
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<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 person.
pub struct AdminPurgePersonView {
pub admin_purge_person: AdminPurgePerson,
#[cfg_attr(feature = "full", ts(optional))]
pub admin: Option<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 post.
pub struct AdminPurgePostView {
pub admin_purge_post: AdminPurgePost,
#[cfg_attr(feature = "full", ts(optional))]
pub admin: Option<Person>,
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<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 post.
pub struct AdminAllowInstanceView {
pub admin_allow_instance: AdminAllowInstance,
pub instance: Instance,
#[cfg_attr(feature = "full", ts(optional))]
pub admin: Option<Person>,
}
/// 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<AdminAllowInstance>,
pub admin_block_instance: Option<AdminBlockInstance>,
pub admin_purge_comment: Option<AdminPurgeComment>,
pub admin_purge_community: Option<AdminPurgeCommunity>,
pub admin_purge_person: Option<AdminPurgePerson>,
pub admin_purge_post: Option<AdminPurgePost>,
pub mod_add: Option<ModAdd>,
pub mod_add_community: Option<ModAddCommunity>,
pub mod_ban: Option<ModBan>,
pub mod_ban_from_community: Option<ModBanFromCommunity>,
pub mod_feature_post: Option<ModFeaturePost>,
pub mod_hide_community: Option<ModHideCommunity>,
pub mod_lock_post: Option<ModLockPost>,
pub mod_remove_comment: Option<ModRemoveComment>,
pub mod_remove_community: Option<ModRemoveCommunity>,
pub mod_remove_post: Option<ModRemovePost>,
pub mod_transfer_community: Option<ModTransferCommunity>,
// Specific fields
// Shared
pub moderator: Option<Person>,
pub other_person: Option<Person>,
pub instance: Option<Instance>,
pub community: Option<Community>,
pub post: Option<Post>,
pub comment: Option<Comment>,
}
#[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),
}

View file

@ -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;