mirror of
https://github.com/LemmyNet/lemmy.git
synced 2025-01-23 10:25:56 +00:00
Adding search_combined.score column, and DB triggers.
This commit is contained in:
parent
7e6ca820fc
commit
25920ad8b4
12 changed files with 238 additions and 490 deletions
|
@ -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))]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 ();
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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
|
|
@ -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;
|
|
@ -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 }
|
|
@ -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),
|
||||
}
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue