* Showing # of unread comments for posts. Fixes #2134 * Fix lint. * Forgot to remove comment list update. * Fix clippy
This commit is contained in:
parent
f2537ba7db
commit
0aeb78b8f3
10 changed files with 128 additions and 1 deletions
|
@ -63,6 +63,7 @@ impl PerformCrud for GetComments {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let parent_path_cloned = parent_path.to_owned();
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let local_user = local_user_view.map(|l| l.local_user);
|
let local_user = local_user_view.map(|l| l.local_user);
|
||||||
let mut comments = blocking(context.pool(), move |conn| {
|
let mut comments = blocking(context.pool(), move |conn| {
|
||||||
|
@ -74,7 +75,7 @@ impl PerformCrud for GetComments {
|
||||||
.saved_only(saved_only)
|
.saved_only(saved_only)
|
||||||
.community_id(community_id)
|
.community_id(community_id)
|
||||||
.community_actor_id(community_actor_id)
|
.community_actor_id(community_actor_id)
|
||||||
.parent_path(parent_path)
|
.parent_path(parent_path_cloned)
|
||||||
.post_id(post_id)
|
.post_id(post_id)
|
||||||
.local_user(local_user.as_ref())
|
.local_user(local_user.as_ref())
|
||||||
.page(page)
|
.page(page)
|
||||||
|
|
|
@ -5,6 +5,7 @@ use lemmy_api_common::{
|
||||||
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt, mark_post_as_read},
|
utils::{blocking, check_private_instance, get_local_user_view_from_jwt_opt, mark_post_as_read},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
|
||||||
source::comment::Comment,
|
source::comment::Comment,
|
||||||
traits::{Crud, DeleteableOrRemoveable},
|
traits::{Crud, DeleteableOrRemoveable},
|
||||||
};
|
};
|
||||||
|
@ -64,6 +65,23 @@ impl PerformCrud for GetPost {
|
||||||
.await?
|
.await?
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?;
|
||||||
|
|
||||||
|
// Insert into PersonPostAggregates
|
||||||
|
// to update the read_comments count
|
||||||
|
if let Some(person_id) = person_id {
|
||||||
|
let read_comments = post_view.counts.comments;
|
||||||
|
let person_post_agg_form = PersonPostAggregatesForm {
|
||||||
|
person_id,
|
||||||
|
post_id,
|
||||||
|
read_comments,
|
||||||
|
..PersonPostAggregatesForm::default()
|
||||||
|
};
|
||||||
|
blocking(context.pool(), move |conn| {
|
||||||
|
PersonPostAggregates::upsert(conn, &person_post_agg_form)
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_find_post"))?;
|
||||||
|
}
|
||||||
|
|
||||||
// Blank out deleted or removed info for non-logged in users
|
// Blank out deleted or removed info for non-logged in users
|
||||||
if person_id.is_none() {
|
if person_id.is_none() {
|
||||||
if post_view.post.deleted || post_view.post.removed {
|
if post_view.post.deleted || post_view.post.removed {
|
||||||
|
|
|
@ -5,6 +5,8 @@ pub mod community_aggregates;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod person_aggregates;
|
pub mod person_aggregates;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
|
pub mod person_post_aggregates;
|
||||||
|
#[cfg(feature = "full")]
|
||||||
pub mod post_aggregates;
|
pub mod post_aggregates;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod site_aggregates;
|
pub mod site_aggregates;
|
||||||
|
|
27
crates/db_schema/src/aggregates/person_post_aggregates.rs
Normal file
27
crates/db_schema/src/aggregates/person_post_aggregates.rs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
use crate::{
|
||||||
|
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
|
||||||
|
newtypes::{PersonId, PostId},
|
||||||
|
};
|
||||||
|
use diesel::{result::Error, *};
|
||||||
|
|
||||||
|
impl PersonPostAggregates {
|
||||||
|
pub fn upsert(conn: &mut PgConnection, form: &PersonPostAggregatesForm) -> Result<Self, Error> {
|
||||||
|
use crate::schema::person_post_aggregates::dsl::*;
|
||||||
|
insert_into(person_post_aggregates)
|
||||||
|
.values(form)
|
||||||
|
.on_conflict((person_id, post_id))
|
||||||
|
.do_update()
|
||||||
|
.set(form)
|
||||||
|
.get_result::<Self>(conn)
|
||||||
|
}
|
||||||
|
pub fn read(
|
||||||
|
conn: &mut PgConnection,
|
||||||
|
person_id_: PersonId,
|
||||||
|
post_id_: PostId,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
use crate::schema::person_post_aggregates::dsl::*;
|
||||||
|
person_post_aggregates
|
||||||
|
.filter(post_id.eq(post_id_).and(person_id.eq(person_id_)))
|
||||||
|
.first::<Self>(conn)
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ use crate::schema::{
|
||||||
comment_aggregates,
|
comment_aggregates,
|
||||||
community_aggregates,
|
community_aggregates,
|
||||||
person_aggregates,
|
person_aggregates,
|
||||||
|
person_post_aggregates,
|
||||||
post_aggregates,
|
post_aggregates,
|
||||||
site_aggregates,
|
site_aggregates,
|
||||||
};
|
};
|
||||||
|
@ -76,6 +77,28 @@ pub struct PostAggregates {
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
||||||
#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
|
#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = person_post_aggregates))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::person::Person)))]
|
||||||
|
pub struct PersonPostAggregates {
|
||||||
|
pub id: i32,
|
||||||
|
pub person_id: PersonId,
|
||||||
|
pub post_id: PostId,
|
||||||
|
pub read_comments: i64,
|
||||||
|
pub published: chrono::NaiveDateTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Default)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
|
||||||
|
#[cfg_attr(feature = "full", diesel(table_name = person_post_aggregates))]
|
||||||
|
pub struct PersonPostAggregatesForm {
|
||||||
|
pub person_id: PersonId,
|
||||||
|
pub post_id: PostId,
|
||||||
|
pub read_comments: i64,
|
||||||
|
pub published: Option<chrono::NaiveDateTime>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Serialize, Deserialize, Clone)]
|
||||||
|
#[cfg_attr(feature = "full", derive(Queryable, Associations, Identifiable))]
|
||||||
#[cfg_attr(feature = "full", diesel(table_name = site_aggregates))]
|
#[cfg_attr(feature = "full", diesel(table_name = site_aggregates))]
|
||||||
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::site::Site)))]
|
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::site::Site)))]
|
||||||
pub struct SiteAggregates {
|
pub struct SiteAggregates {
|
||||||
|
|
|
@ -380,6 +380,16 @@ table! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table! {
|
||||||
|
person_post_aggregates (id) {
|
||||||
|
id -> Int4,
|
||||||
|
person_id -> Int4,
|
||||||
|
post_id -> Int4,
|
||||||
|
read_comments -> Int8,
|
||||||
|
published -> Timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
table! {
|
table! {
|
||||||
post_aggregates (id) {
|
post_aggregates (id) {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
|
@ -667,6 +677,8 @@ joinable!(comment_reply -> comment (comment_id));
|
||||||
joinable!(comment_reply -> person (recipient_id));
|
joinable!(comment_reply -> person (recipient_id));
|
||||||
joinable!(post -> community (community_id));
|
joinable!(post -> community (community_id));
|
||||||
joinable!(post -> person (creator_id));
|
joinable!(post -> person (creator_id));
|
||||||
|
joinable!(person_post_aggregates -> post (post_id));
|
||||||
|
joinable!(person_post_aggregates -> person (person_id));
|
||||||
joinable!(post_aggregates -> post (post_id));
|
joinable!(post_aggregates -> post (post_id));
|
||||||
joinable!(post_like -> person (person_id));
|
joinable!(post_like -> person (person_id));
|
||||||
joinable!(post_like -> post (post_id));
|
joinable!(post_like -> post (post_id));
|
||||||
|
@ -725,6 +737,7 @@ allow_tables_to_appear_in_same_query!(
|
||||||
person_ban,
|
person_ban,
|
||||||
person_block,
|
person_block,
|
||||||
person_mention,
|
person_mention,
|
||||||
|
person_post_aggregates,
|
||||||
comment_reply,
|
comment_reply,
|
||||||
post,
|
post,
|
||||||
post_aggregates,
|
post_aggregates,
|
||||||
|
|
|
@ -11,6 +11,7 @@ use lemmy_db_schema::{
|
||||||
local_user_language,
|
local_user_language,
|
||||||
person,
|
person,
|
||||||
person_block,
|
person_block,
|
||||||
|
person_post_aggregates,
|
||||||
post,
|
post,
|
||||||
post_aggregates,
|
post_aggregates,
|
||||||
post_like,
|
post_like,
|
||||||
|
@ -43,8 +44,11 @@ type PostViewTuple = (
|
||||||
Option<PostRead>,
|
Option<PostRead>,
|
||||||
Option<PersonBlock>,
|
Option<PersonBlock>,
|
||||||
Option<i16>,
|
Option<i16>,
|
||||||
|
i64,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
sql_function!(fn coalesce(x: sql_types::Nullable<sql_types::BigInt>, y: sql_types::BigInt) -> sql_types::BigInt);
|
||||||
|
|
||||||
impl PostView {
|
impl PostView {
|
||||||
pub fn read(
|
pub fn read(
|
||||||
conn: &mut PgConnection,
|
conn: &mut PgConnection,
|
||||||
|
@ -64,6 +68,7 @@ impl PostView {
|
||||||
read,
|
read,
|
||||||
creator_blocked,
|
creator_blocked,
|
||||||
post_like,
|
post_like,
|
||||||
|
unread_comments,
|
||||||
) = post::table
|
) = post::table
|
||||||
.find(post_id)
|
.find(post_id)
|
||||||
.inner_join(person::table)
|
.inner_join(person::table)
|
||||||
|
@ -116,6 +121,13 @@ impl PostView {
|
||||||
.and(post_like::person_id.eq(person_id_join)),
|
.and(post_like::person_id.eq(person_id_join)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.left_join(
|
||||||
|
person_post_aggregates::table.on(
|
||||||
|
post::id
|
||||||
|
.eq(person_post_aggregates::post_id)
|
||||||
|
.and(person_post_aggregates::person_id.eq(person_id_join)),
|
||||||
|
),
|
||||||
|
)
|
||||||
.select((
|
.select((
|
||||||
post::all_columns,
|
post::all_columns,
|
||||||
Person::safe_columns_tuple(),
|
Person::safe_columns_tuple(),
|
||||||
|
@ -127,6 +139,10 @@ impl PostView {
|
||||||
post_read::all_columns.nullable(),
|
post_read::all_columns.nullable(),
|
||||||
person_block::all_columns.nullable(),
|
person_block::all_columns.nullable(),
|
||||||
post_like::score.nullable(),
|
post_like::score.nullable(),
|
||||||
|
coalesce(
|
||||||
|
post_aggregates::comments.nullable() - person_post_aggregates::read_comments.nullable(),
|
||||||
|
post_aggregates::comments,
|
||||||
|
),
|
||||||
))
|
))
|
||||||
.first::<PostViewTuple>(conn)?;
|
.first::<PostViewTuple>(conn)?;
|
||||||
|
|
||||||
|
@ -149,6 +165,7 @@ impl PostView {
|
||||||
read: read.is_some(),
|
read: read.is_some(),
|
||||||
creator_blocked: creator_blocked.is_some(),
|
creator_blocked: creator_blocked.is_some(),
|
||||||
my_vote,
|
my_vote,
|
||||||
|
unread_comments,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -237,6 +254,13 @@ impl<'a> PostQuery<'a> {
|
||||||
.and(post_like::person_id.eq(person_id_join)),
|
.and(post_like::person_id.eq(person_id_join)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.left_join(
|
||||||
|
person_post_aggregates::table.on(
|
||||||
|
post::id
|
||||||
|
.eq(person_post_aggregates::post_id)
|
||||||
|
.and(person_post_aggregates::person_id.eq(person_id_join)),
|
||||||
|
),
|
||||||
|
)
|
||||||
.left_join(
|
.left_join(
|
||||||
local_user_language::table.on(
|
local_user_language::table.on(
|
||||||
post::language_id
|
post::language_id
|
||||||
|
@ -255,6 +279,10 @@ impl<'a> PostQuery<'a> {
|
||||||
post_read::all_columns.nullable(),
|
post_read::all_columns.nullable(),
|
||||||
person_block::all_columns.nullable(),
|
person_block::all_columns.nullable(),
|
||||||
post_like::score.nullable(),
|
post_like::score.nullable(),
|
||||||
|
coalesce(
|
||||||
|
post_aggregates::comments.nullable() - person_post_aggregates::read_comments.nullable(),
|
||||||
|
post_aggregates::comments,
|
||||||
|
),
|
||||||
))
|
))
|
||||||
.into_boxed();
|
.into_boxed();
|
||||||
|
|
||||||
|
@ -412,6 +440,7 @@ impl ViewToVec for PostView {
|
||||||
read: a.7.is_some(),
|
read: a.7.is_some(),
|
||||||
creator_blocked: a.8.is_some(),
|
creator_blocked: a.8.is_some(),
|
||||||
my_vote: a.9,
|
my_vote: a.9,
|
||||||
|
unread_comments: a.10,
|
||||||
})
|
})
|
||||||
.collect::<Vec<Self>>()
|
.collect::<Vec<Self>>()
|
||||||
}
|
}
|
||||||
|
@ -806,6 +835,7 @@ mod tests {
|
||||||
language_id: LanguageId(47),
|
language_id: LanguageId(47),
|
||||||
},
|
},
|
||||||
my_vote: None,
|
my_vote: None,
|
||||||
|
unread_comments: 0,
|
||||||
creator: PersonSafe {
|
creator: PersonSafe {
|
||||||
id: inserted_person.id,
|
id: inserted_person.id,
|
||||||
name: inserted_person.name.clone(),
|
name: inserted_person.name.clone(),
|
||||||
|
|
|
@ -85,6 +85,7 @@ pub struct PostView {
|
||||||
pub read: bool, // Left join to PostRead
|
pub read: bool, // Left join to PostRead
|
||||||
pub creator_blocked: bool, // Left join to PersonBlock
|
pub creator_blocked: bool, // Left join to PersonBlock
|
||||||
pub my_vote: Option<i16>, // Left join to PostLike
|
pub my_vote: Option<i16>, // Left join to PostLike
|
||||||
|
pub unread_comments: i64, // Left join to PersonPostAggregates
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
drop table person_post_aggregates;
|
|
@ -0,0 +1,11 @@
|
||||||
|
-- This table stores the # of read comments for a person, on a post
|
||||||
|
-- It can then be joined to post_aggregates to get an unread count:
|
||||||
|
-- unread = post_aggregates.comments - person_post_aggregates.read_comments
|
||||||
|
create table person_post_aggregates(
|
||||||
|
id serial primary key,
|
||||||
|
person_id int references person on update cascade on delete cascade not null,
|
||||||
|
post_id int references post on update cascade on delete cascade not null,
|
||||||
|
read_comments bigint not null default 0,
|
||||||
|
published timestamp not null default now(),
|
||||||
|
unique(person_id, post_id)
|
||||||
|
);
|
Loading…
Reference in a new issue