diff --git a/lemmy_api/src/user.rs b/lemmy_api/src/user.rs index df6284a5..ddd03d23 100644 --- a/lemmy_api/src/user.rs +++ b/lemmy_api/src/user.rs @@ -31,13 +31,13 @@ use lemmy_db::{ user::*, user_mention::*, }, - user_mention_view::*, views::{ comment_view::CommentQueryBuilder, community_follower_view::CommunityFollowerView, community_moderator_view::CommunityModeratorView, post_view::PostQueryBuilder, site_view::SiteView, + user_mention_view::{UserMentionQueryBuilder, UserMentionView}, user_view::{UserViewDangerous, UserViewSafe}, }, Crud, @@ -774,7 +774,7 @@ impl Perform for GetUserMentions { let unread_only = data.unread_only; let user_id = user.id; let mentions = blocking(context.pool(), move |conn| { - UserMentionQueryBuilder::create(conn, user_id) + UserMentionQueryBuilder::create(conn, Some(user_id), user_id) .sort(&sort) .unread_only(unread_only) .page(page) @@ -819,13 +819,11 @@ impl Perform for MarkUserMentionAsRead { let user_mention_id = read_user_mention.id; let user_id = user.id; let user_mention_view = blocking(context.pool(), move |conn| { - UserMentionView::read(conn, user_mention_id, user_id) + UserMentionView::read(conn, user_mention_id, Some(user_id)) }) .await??; - Ok(UserMentionResponse { - mention: user_mention_view, - }) + Ok(UserMentionResponse { user_mention_view }) } } diff --git a/lemmy_db/src/lib.rs b/lemmy_db/src/lib.rs index 449cfc2b..ba5dccdf 100644 --- a/lemmy_db/src/lib.rs +++ b/lemmy_db/src/lib.rs @@ -15,7 +15,6 @@ pub mod comment_report; pub mod moderator_views; pub mod post_report; pub mod private_message_view; -pub mod user_mention_view; pub mod aggregates; pub mod schema; diff --git a/lemmy_db/src/schema.rs b/lemmy_db/src/schema.rs index 5fa5e371..cbfce876 100644 --- a/lemmy_db/src/schema.rs +++ b/lemmy_db/src/schema.rs @@ -617,6 +617,7 @@ table! { joinable!(comment_alias_1 -> user_alias_1 (creator_id)); joinable!(comment -> comment_alias_1 (parent_id)); +joinable!(user_mention -> user_alias_1 (recipient_id)); joinable!(comment -> post (post_id)); joinable!(comment -> user_ (creator_id)); diff --git a/lemmy_db/src/source/user_mention.rs b/lemmy_db/src/source/user_mention.rs index 7ad96521..ed5c2c79 100644 --- a/lemmy_db/src/source/user_mention.rs +++ b/lemmy_db/src/source/user_mention.rs @@ -1,8 +1,9 @@ use super::comment::Comment; use crate::{schema::user_mention, Crud}; use diesel::{dsl::*, result::Error, *}; +use serde::Serialize; -#[derive(Queryable, Associations, Identifiable, PartialEq, Debug)] +#[derive(Clone, Queryable, Associations, Identifiable, PartialEq, Debug, Serialize)] #[belongs_to(Comment)] #[table_name = "user_mention"] pub struct UserMention { diff --git a/lemmy_db/src/user_mention_view.rs b/lemmy_db/src/user_mention_view.rs deleted file mode 100644 index d1ce5ebd..00000000 --- a/lemmy_db/src/user_mention_view.rs +++ /dev/null @@ -1,231 +0,0 @@ -use crate::{limit_and_offset, MaybeOptional, SortType}; -use diesel::{dsl::*, pg::Pg, result::Error, *}; -use serde::Serialize; - -// The faked schema since diesel doesn't do views -table! { - user_mention_view (id) { - id -> Int4, - user_mention_id -> Int4, - creator_id -> Int4, - creator_actor_id -> Text, - creator_local -> Bool, - post_id -> Int4, - post_name -> Varchar, - parent_id -> Nullable, - content -> Text, - removed -> Bool, - read -> Bool, - published -> Timestamp, - updated -> Nullable, - deleted -> Bool, - community_id -> Int4, - community_actor_id -> Text, - community_local -> Bool, - community_name -> Varchar, - community_icon -> Nullable, - banned -> Bool, - banned_from_community -> Bool, - creator_name -> Varchar, - creator_preferred_username -> Nullable, - creator_avatar -> Nullable, - score -> BigInt, - upvotes -> BigInt, - downvotes -> BigInt, - hot_rank -> Int4, - hot_rank_active -> Int4, - user_id -> Nullable, - my_vote -> Nullable, - saved -> Nullable, - recipient_id -> Int4, - recipient_actor_id -> Text, - recipient_local -> Bool, - } -} - -table! { - user_mention_fast_view (id) { - id -> Int4, - user_mention_id -> Int4, - creator_id -> Int4, - creator_actor_id -> Text, - creator_local -> Bool, - post_id -> Int4, - post_name -> Varchar, - parent_id -> Nullable, - content -> Text, - removed -> Bool, - read -> Bool, - published -> Timestamp, - updated -> Nullable, - deleted -> Bool, - community_id -> Int4, - community_actor_id -> Text, - community_local -> Bool, - community_name -> Varchar, - community_icon -> Nullable, - banned -> Bool, - banned_from_community -> Bool, - creator_name -> Varchar, - creator_preferred_username -> Nullable, - creator_avatar -> Nullable, - score -> BigInt, - upvotes -> BigInt, - downvotes -> BigInt, - hot_rank -> Int4, - hot_rank_active -> Int4, - user_id -> Nullable, - my_vote -> Nullable, - saved -> Nullable, - recipient_id -> Int4, - recipient_actor_id -> Text, - recipient_local -> Bool, - } -} - -#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, QueryableByName, Clone)] -#[table_name = "user_mention_fast_view"] -pub struct UserMentionView { - pub id: i32, - pub user_mention_id: i32, - pub creator_id: i32, - pub creator_actor_id: String, - pub creator_local: bool, - pub post_id: i32, - pub post_name: String, - pub parent_id: Option, - pub content: String, - pub removed: bool, - pub read: bool, - pub published: chrono::NaiveDateTime, - pub updated: Option, - pub deleted: bool, - pub community_id: i32, - pub community_actor_id: String, - pub community_local: bool, - pub community_name: String, - pub community_icon: Option, - pub banned: bool, - pub banned_from_community: bool, - pub creator_name: String, - pub creator_preferred_username: Option, - pub creator_avatar: Option, - pub score: i64, - pub upvotes: i64, - pub downvotes: i64, - pub hot_rank: i32, - pub hot_rank_active: i32, - pub user_id: Option, - pub my_vote: Option, - pub saved: Option, - pub recipient_id: i32, - pub recipient_actor_id: String, - pub recipient_local: bool, -} - -pub struct UserMentionQueryBuilder<'a> { - conn: &'a PgConnection, - query: super::user_mention_view::user_mention_fast_view::BoxedQuery<'a, Pg>, - for_user_id: i32, - sort: &'a SortType, - unread_only: bool, - page: Option, - limit: Option, -} - -impl<'a> UserMentionQueryBuilder<'a> { - pub fn create(conn: &'a PgConnection, for_user_id: i32) -> Self { - use super::user_mention_view::user_mention_fast_view::dsl::*; - - let query = user_mention_fast_view.into_boxed(); - - UserMentionQueryBuilder { - conn, - query, - for_user_id, - sort: &SortType::New, - unread_only: false, - page: None, - limit: None, - } - } - - pub fn sort(mut self, sort: &'a SortType) -> Self { - self.sort = sort; - self - } - - pub fn unread_only(mut self, unread_only: bool) -> Self { - self.unread_only = unread_only; - self - } - - pub fn page>(mut self, page: T) -> Self { - self.page = page.get_optional(); - self - } - - pub fn limit>(mut self, limit: T) -> Self { - self.limit = limit.get_optional(); - self - } - - pub fn list(self) -> Result, Error> { - use super::user_mention_view::user_mention_fast_view::dsl::*; - - let mut query = self.query; - - if self.unread_only { - query = query.filter(read.eq(false)); - } - - query = query - .filter(user_id.eq(self.for_user_id)) - .filter(recipient_id.eq(self.for_user_id)); - - query = match self.sort { - SortType::Hot => query - .order_by(hot_rank.desc()) - .then_order_by(published.desc()), - SortType::Active => query - .order_by(hot_rank_active.desc()) - .then_order_by(published.desc()), - SortType::New => query.order_by(published.desc()), - SortType::TopAll => query.order_by(score.desc()), - SortType::TopYear => query - .filter(published.gt(now - 1.years())) - .order_by(score.desc()), - SortType::TopMonth => query - .filter(published.gt(now - 1.months())) - .order_by(score.desc()), - SortType::TopWeek => query - .filter(published.gt(now - 1.weeks())) - .order_by(score.desc()), - SortType::TopDay => query - .filter(published.gt(now - 1.days())) - .order_by(score.desc()), - // _ => query.order_by(published.desc()), - }; - - let (limit, offset) = limit_and_offset(self.page, self.limit); - query - .limit(limit) - .offset(offset) - .load::(self.conn) - } -} - -impl UserMentionView { - pub fn read( - conn: &PgConnection, - from_user_mention_id: i32, - from_recipient_id: i32, - ) -> Result { - use super::user_mention_view::user_mention_fast_view::dsl::*; - - user_mention_fast_view - .filter(user_mention_id.eq(from_user_mention_id)) - .filter(user_id.eq(from_recipient_id)) - .first::(conn) - } -} diff --git a/lemmy_db/src/views/mod.rs b/lemmy_db/src/views/mod.rs index a3295ec0..2516caeb 100644 --- a/lemmy_db/src/views/mod.rs +++ b/lemmy_db/src/views/mod.rs @@ -5,6 +5,7 @@ pub mod community_user_ban_view; pub mod community_view; pub mod post_view; pub mod site_view; +pub mod user_mention_view; pub mod user_view; pub(crate) trait ViewToVec { diff --git a/lemmy_db/src/views/user_mention_view.rs b/lemmy_db/src/views/user_mention_view.rs new file mode 100644 index 00000000..ba3bff54 --- /dev/null +++ b/lemmy_db/src/views/user_mention_view.rs @@ -0,0 +1,552 @@ +use crate::{ + aggregates::comment_aggregates::CommentAggregates, + functions::hot_rank, + limit_and_offset, + schema::{ + comment, + comment_aggregates, + comment_like, + comment_saved, + community, + community_follower, + community_user_ban, + post, + user_, + user_alias_1, + user_mention, + }, + source::{ + comment::{Comment, CommentSaved}, + community::{Community, CommunityFollower, CommunitySafe, CommunityUserBan}, + post::Post, + user::{UserAlias1, UserSafe, UserSafeAlias1, User_}, + user_mention::UserMention, + }, + views::ViewToVec, + MaybeOptional, + SortType, + ToSafe, +}; +use diesel::{result::Error, *}; +use serde::Serialize; + +#[derive(Debug, PartialEq, Serialize, Clone)] +pub struct UserMentionView { + pub user_mention: UserMention, + pub comment: Comment, + pub creator: UserSafe, + pub post: Post, + pub community: CommunitySafe, + pub recipient: UserSafeAlias1, + pub counts: CommentAggregates, + pub creator_banned_from_community: bool, // Left Join to CommunityUserBan + pub subscribed: bool, // Left join to CommunityFollower + pub saved: bool, // Left join to CommentSaved + pub my_vote: Option, // Left join to CommentLike +} + +type UserMentionViewTuple = ( + UserMention, + Comment, + UserSafe, + Post, + CommunitySafe, + UserSafeAlias1, + CommentAggregates, + Option, + Option, + Option, + Option, +); + +impl UserMentionView { + pub fn read( + conn: &PgConnection, + user_mention_id: i32, + my_user_id: Option, + ) -> Result { + // The left join below will return None in this case + let user_id_join = my_user_id.unwrap_or(-1); + + let ( + user_mention, + comment, + creator, + post, + community, + recipient, + counts, + creator_banned_from_community, + subscribed, + saved, + my_vote, + ) = user_mention::table + .find(user_mention_id) + .inner_join(comment::table) + .inner_join(user_::table.on(comment::creator_id.eq(user_::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(user_alias_1::table) + .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id))) + .left_join( + community_user_ban::table.on( + community::id + .eq(community_user_ban::community_id) + .and(community_user_ban::user_id.eq(comment::creator_id)), + ), + ) + .left_join( + community_follower::table.on( + post::community_id + .eq(community_follower::community_id) + .and(community_follower::user_id.eq(user_id_join)), + ), + ) + .left_join( + comment_saved::table.on( + comment::id + .eq(comment_saved::comment_id) + .and(comment_saved::user_id.eq(user_id_join)), + ), + ) + .left_join( + comment_like::table.on( + comment::id + .eq(comment_like::comment_id) + .and(comment_like::user_id.eq(user_id_join)), + ), + ) + .select(( + user_mention::all_columns, + comment::all_columns, + User_::safe_columns_tuple(), + post::all_columns, + Community::safe_columns_tuple(), + UserAlias1::safe_columns_tuple(), + comment_aggregates::all_columns, + community_user_ban::all_columns.nullable(), + community_follower::all_columns.nullable(), + comment_saved::all_columns.nullable(), + comment_like::score.nullable(), + )) + .first::(conn)?; + + Ok(UserMentionView { + user_mention, + comment, + creator, + post, + community, + recipient, + counts, + creator_banned_from_community: creator_banned_from_community.is_some(), + subscribed: subscribed.is_some(), + saved: saved.is_some(), + my_vote, + }) + } +} + +mod join_types { + use crate::schema::{ + comment, + comment_aggregates, + comment_like, + comment_saved, + community, + community_follower, + community_user_ban, + post, + user_, + user_alias_1, + user_mention, + }; + use diesel::{ + pg::Pg, + query_builder::BoxedSelectStatement, + query_source::joins::{Inner, Join, JoinOn, LeftOuter}, + sql_types::*, + }; + + // /// TODO awful, but necessary because of the boxed join + pub(super) type BoxedUserMentionJoin<'a> = BoxedSelectStatement< + 'a, + ( + (Integer, Integer, Integer, Bool, Timestamp), + ( + Integer, + Integer, + Integer, + Nullable, + Text, + Bool, + Bool, + Timestamp, + Nullable, + Bool, + Text, + Bool, + ), + ( + Integer, + Text, + Nullable, + Nullable, + Bool, + Bool, + Timestamp, + Nullable, + Nullable, + Text, + Nullable, + Bool, + Nullable, + Bool, + ), + ( + Integer, + Text, + Nullable, + Nullable, + Integer, + Integer, + Bool, + Bool, + Timestamp, + Nullable, + Bool, + Bool, + Bool, + Nullable, + Nullable, + Nullable, + Nullable, + Text, + Bool, + ), + ( + Integer, + Text, + Text, + Nullable, + Integer, + Integer, + Bool, + Timestamp, + Nullable, + Bool, + Bool, + Text, + Bool, + Nullable, + Nullable, + ), + ( + Integer, + Text, + Nullable, + Nullable, + Bool, + Bool, + Timestamp, + Nullable, + Nullable, + Text, + Nullable, + Bool, + Nullable, + Bool, + ), + (Integer, Integer, BigInt, BigInt, BigInt), + Nullable<(Integer, Integer, Integer, Timestamp)>, + Nullable<(Integer, Integer, Integer, Timestamp, Nullable)>, + Nullable<(Integer, Integer, Integer, Timestamp)>, + Nullable, + ), + JoinOn< + Join< + JoinOn< + Join< + JoinOn< + Join< + JoinOn< + Join< + JoinOn< + Join< + JoinOn< + Join< + JoinOn< + Join< + JoinOn< + Join< + JoinOn< + Join< + JoinOn< + Join, + diesel::expression::operators::Eq< + diesel::expression::nullable::Nullable< + user_mention::columns::comment_id, + >, + diesel::expression::nullable::Nullable< + comment::columns::id, + >, + >, + >, + user_::table, + Inner, + >, + diesel::expression::operators::Eq< + comment::columns::creator_id, + user_::columns::id, + >, + >, + post::table, + Inner, + >, + diesel::expression::operators::Eq< + comment::columns::post_id, + post::columns::id, + >, + >, + community::table, + Inner, + >, + diesel::expression::operators::Eq< + post::columns::community_id, + community::columns::id, + >, + >, + user_alias_1::table, + Inner, + >, + diesel::expression::operators::Eq< + diesel::expression::nullable::Nullable< + user_mention::columns::recipient_id, + >, + diesel::expression::nullable::Nullable, + >, + >, + comment_aggregates::table, + Inner, + >, + diesel::expression::operators::Eq< + comment::columns::id, + comment_aggregates::columns::comment_id, + >, + >, + community_user_ban::table, + LeftOuter, + >, + diesel::expression::operators::And< + diesel::expression::operators::Eq< + community::columns::id, + community_user_ban::columns::community_id, + >, + diesel::expression::operators::Eq< + community_user_ban::columns::user_id, + comment::columns::creator_id, + >, + >, + >, + community_follower::table, + LeftOuter, + >, + diesel::expression::operators::And< + diesel::expression::operators::Eq< + post::columns::community_id, + community_follower::columns::community_id, + >, + diesel::expression::operators::Eq< + community_follower::columns::user_id, + diesel::expression::bound::Bound, + >, + >, + >, + comment_saved::table, + LeftOuter, + >, + diesel::expression::operators::And< + diesel::expression::operators::Eq< + comment::columns::id, + comment_saved::columns::comment_id, + >, + diesel::expression::operators::Eq< + comment_saved::columns::user_id, + diesel::expression::bound::Bound, + >, + >, + >, + comment_like::table, + LeftOuter, + >, + diesel::expression::operators::And< + diesel::expression::operators::Eq, + diesel::expression::operators::Eq< + comment_like::columns::user_id, + diesel::expression::bound::Bound, + >, + >, + >, + Pg, + >; +} + +pub struct UserMentionQueryBuilder<'a> { + conn: &'a PgConnection, + query: join_types::BoxedUserMentionJoin<'a>, + for_recipient_id: i32, + sort: &'a SortType, + unread_only: bool, + page: Option, + limit: Option, +} + +impl<'a> UserMentionQueryBuilder<'a> { + pub fn create(conn: &'a PgConnection, my_user_id: Option, for_recipient_id: i32) -> Self { + // The left join below will return None in this case + let user_id_join = my_user_id.unwrap_or(-1); + + let query = user_mention::table + .inner_join(comment::table) + .inner_join(user_::table.on(comment::creator_id.eq(user_::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(user_alias_1::table) + .inner_join(comment_aggregates::table.on(comment::id.eq(comment_aggregates::comment_id))) + .left_join( + community_user_ban::table.on( + community::id + .eq(community_user_ban::community_id) + .and(community_user_ban::user_id.eq(comment::creator_id)), + ), + ) + .left_join( + community_follower::table.on( + post::community_id + .eq(community_follower::community_id) + .and(community_follower::user_id.eq(user_id_join)), + ), + ) + .left_join( + comment_saved::table.on( + comment::id + .eq(comment_saved::comment_id) + .and(comment_saved::user_id.eq(user_id_join)), + ), + ) + .left_join( + comment_like::table.on( + comment::id + .eq(comment_like::comment_id) + .and(comment_like::user_id.eq(user_id_join)), + ), + ) + .select(( + user_mention::all_columns, + comment::all_columns, + User_::safe_columns_tuple(), + post::all_columns, + Community::safe_columns_tuple(), + UserAlias1::safe_columns_tuple(), + comment_aggregates::all_columns, + community_user_ban::all_columns.nullable(), + community_follower::all_columns.nullable(), + comment_saved::all_columns.nullable(), + comment_like::score.nullable(), + )) + .into_boxed(); + + UserMentionQueryBuilder { + conn, + query, + for_recipient_id, + sort: &SortType::New, + unread_only: false, + page: None, + limit: None, + } + } + + pub fn sort(mut self, sort: &'a SortType) -> Self { + self.sort = sort; + self + } + + pub fn unread_only(mut self, unread_only: bool) -> Self { + self.unread_only = unread_only; + self + } + + pub fn page>(mut self, page: T) -> Self { + self.page = page.get_optional(); + self + } + + pub fn limit>(mut self, limit: T) -> Self { + self.limit = limit.get_optional(); + self + } + + pub fn list(self) -> Result, Error> { + use diesel::dsl::*; + + let mut query = self.query; + + query = query.filter(user_mention::recipient_id.eq(self.for_recipient_id)); + + if self.unread_only { + query = query.filter(user_mention::read.eq(false)); + } + + query = match self.sort { + SortType::Hot | SortType::Active => query + .order_by(hot_rank(comment_aggregates::score, comment::published).desc()) + .then_order_by(comment::published.desc()), + SortType::New => query.order_by(comment::published.desc()), + SortType::TopAll => query.order_by(comment_aggregates::score.desc()), + SortType::TopYear => query + .filter(comment::published.gt(now - 1.years())) + .order_by(comment_aggregates::score.desc()), + SortType::TopMonth => query + .filter(comment::published.gt(now - 1.months())) + .order_by(comment_aggregates::score.desc()), + SortType::TopWeek => query + .filter(comment::published.gt(now - 1.weeks())) + .order_by(comment_aggregates::score.desc()), + SortType::TopDay => query + .filter(comment::published.gt(now - 1.days())) + .order_by(comment_aggregates::score.desc()), + }; + + let (limit, offset) = limit_and_offset(self.page, self.limit); + + let res = query + .limit(limit) + .offset(offset) + .load::(self.conn)?; + + Ok(UserMentionView::to_vec(res)) + } +} + +impl ViewToVec for UserMentionView { + type DbTuple = UserMentionViewTuple; + fn to_vec(posts: Vec) -> Vec { + posts + .iter() + .map(|a| Self { + user_mention: a.0.to_owned(), + comment: a.1.to_owned(), + creator: a.2.to_owned(), + post: a.3.to_owned(), + community: a.4.to_owned(), + recipient: a.5.to_owned(), + counts: a.6.to_owned(), + creator_banned_from_community: a.7.is_some(), + subscribed: a.8.is_some(), + saved: a.9.is_some(), + my_vote: a.10, + }) + .collect::>() + } +} diff --git a/lemmy_db/src/views/user_view.rs b/lemmy_db/src/views/user_view.rs index 4d4e78c7..6ce559e9 100644 --- a/lemmy_db/src/views/user_view.rs +++ b/lemmy_db/src/views/user_view.rs @@ -70,6 +70,7 @@ impl UserViewSafe { } } +// TODO can get rid of this by not boxing the query before the list() mod join_types { use crate::schema::{user_, user_aggregates}; use diesel::{ diff --git a/lemmy_structs/src/user.rs b/lemmy_structs/src/user.rs index 600bf660..9ebb14f4 100644 --- a/lemmy_structs/src/user.rs +++ b/lemmy_structs/src/user.rs @@ -1,11 +1,11 @@ use lemmy_db::{ private_message_view::PrivateMessageView, - user_mention_view::UserMentionView, views::{ comment_view::CommentView, community_follower_view::CommunityFollowerView, community_moderator_view::CommunityModeratorView, post_view::PostView, + user_mention_view::UserMentionView, user_view::{UserViewDangerous, UserViewSafe}, }, }; @@ -162,7 +162,7 @@ pub struct MarkUserMentionAsRead { #[derive(Serialize, Clone)] pub struct UserMentionResponse { - pub mention: UserMentionView, + pub user_mention_view: UserMentionView, } #[derive(Deserialize)] diff --git a/src/routes/feeds.rs b/src/routes/feeds.rs index e8ab1037..8c8004c1 100644 --- a/src/routes/feeds.rs +++ b/src/routes/feeds.rs @@ -5,11 +5,11 @@ use diesel::PgConnection; use lemmy_api::claims::Claims; use lemmy_db::{ source::{community::Community, user::User_}, - user_mention_view::{UserMentionQueryBuilder, UserMentionView}, views::{ comment_view::{CommentQueryBuilder, CommentView}, post_view::{PostQueryBuilder, PostView}, site_view::SiteView, + user_mention_view::{UserMentionQueryBuilder, UserMentionView}, }, ListingType, SortType, @@ -253,7 +253,7 @@ fn get_feed_inbox(conn: &PgConnection, jwt: String) -> Result, LemmyError>>()?;