Merge branch 'post_body_mentions' into combined_inbox

This commit is contained in:
Dessalines 2024-12-10 14:33:28 -05:00
commit f133079f0b
39 changed files with 889 additions and 389 deletions

View file

@ -5,10 +5,10 @@ use lemmy_api_common::{
comment::{CommentResponse, CreateCommentLike}, comment::{CommentResponse, CreateCommentLike},
context::LemmyContext, context::LemmyContext,
send_activity::{ActivityChannel, SendActivityData}, send_activity::{ActivityChannel, SendActivityData},
utils::{check_bot_account, check_community_user_action, check_local_vote_mode, VoteItem}, utils::{check_bot_account, check_community_user_action, check_local_vote_mode},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::LocalUserId, newtypes::{LocalUserId, PostOrCommentId},
source::{ source::{
comment::{CommentLike, CommentLikeForm}, comment::{CommentLike, CommentLikeForm},
comment_reply::CommentReply, comment_reply::CommentReply,
@ -33,7 +33,7 @@ pub async fn like_comment(
check_local_vote_mode( check_local_vote_mode(
data.score, data.score,
VoteItem::Comment(comment_id), PostOrCommentId::Comment(comment_id),
&local_site, &local_site,
local_user_view.person.id, local_user_view.person.id,
&mut context.pool(), &mut context.pool(),

View file

@ -0,0 +1,36 @@
use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{
context::LemmyContext,
person::{GetPersonCommentMentions, GetPersonCommentMentionsResponse},
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::person_comment_mention_view::PersonCommentMentionQuery;
use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))]
pub async fn list_comment_mentions(
data: Query<GetPersonCommentMentions>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<GetPersonCommentMentionsResponse>> {
let sort = data.sort;
let page = data.page;
let limit = data.limit;
let unread_only = data.unread_only.unwrap_or_default();
let person_id = Some(local_user_view.person.id);
let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
let comment_mentions = PersonCommentMentionQuery {
recipient_id: person_id,
my_person_id: person_id,
sort,
unread_only,
show_bot_accounts,
page,
limit,
}
.list(&mut context.pool())
.await?;
Ok(Json(GetPersonCommentMentionsResponse { comment_mentions }))
}

View file

@ -1,18 +1,18 @@
use actix_web::web::{Data, Json, Query}; use actix_web::web::{Data, Json, Query};
use lemmy_api_common::{ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
person::{GetPersonMentions, GetPersonMentionsResponse}, person::{GetPersonPostMentions, GetPersonPostMentionsResponse},
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::person_mention_view::PersonMentionQuery; use lemmy_db_views_actor::person_post_mention_view::PersonPostMentionQuery;
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
pub async fn list_mentions( pub async fn list_post_mentions(
data: Query<GetPersonMentions>, data: Query<GetPersonPostMentions>,
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<GetPersonMentionsResponse>> { ) -> LemmyResult<Json<GetPersonPostMentionsResponse>> {
let sort = data.sort; let sort = data.sort;
let page = data.page; let page = data.page;
let limit = data.limit; let limit = data.limit;
@ -20,7 +20,7 @@ pub async fn list_mentions(
let person_id = Some(local_user_view.person.id); let person_id = Some(local_user_view.person.id);
let show_bot_accounts = local_user_view.local_user.show_bot_accounts; let show_bot_accounts = local_user_view.local_user.show_bot_accounts;
let mentions = PersonMentionQuery { let post_mentions = PersonPostMentionQuery {
recipient_id: person_id, recipient_id: person_id,
my_person_id: person_id, my_person_id: person_id,
sort, sort,
@ -32,5 +32,5 @@ pub async fn list_mentions(
.list(&mut context.pool()) .list(&mut context.pool())
.await?; .await?;
Ok(Json(GetPersonMentionsResponse { mentions })) Ok(Json(GetPersonPostMentionsResponse { post_mentions }))
} }

View file

@ -2,7 +2,7 @@ use actix_web::web::{Data, Json};
use lemmy_api_common::{context::LemmyContext, person::GetRepliesResponse}; use lemmy_api_common::{context::LemmyContext, person::GetRepliesResponse};
use lemmy_db_schema::source::{ use lemmy_db_schema::source::{
comment_reply::CommentReply, comment_reply::CommentReply,
person_mention::PersonMention, person_comment_mention::PersonCommentMention,
private_message::PrivateMessage, private_message::PrivateMessage,
}; };
use lemmy_db_views::structs::LocalUserView; use lemmy_db_views::structs::LocalUserView;
@ -20,8 +20,8 @@ pub async fn mark_all_notifications_read(
.await .await
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
// Mark all user mentions as read // Mark all comment mentions as read
PersonMention::mark_all_as_read(&mut context.pool(), person_id) PersonCommentMention::mark_all_as_read(&mut context.pool(), person_id)
.await .await
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?; .with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;

View file

@ -0,0 +1,50 @@
use actix_web::web::{Data, Json};
use lemmy_api_common::{
context::LemmyContext,
person::{MarkPersonCommentMentionAsRead, PersonCommentMentionResponse},
};
use lemmy_db_schema::{
source::person_comment_mention::{PersonCommentMention, PersonCommentMentionUpdateForm},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonCommentMentionView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_comment_mention_as_read(
data: Json<MarkPersonCommentMentionAsRead>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<PersonCommentMentionResponse>> {
let person_comment_mention_id = data.person_comment_mention_id;
let read_person_comment_mention =
PersonCommentMention::read(&mut context.pool(), person_comment_mention_id).await?;
if local_user_view.person.id != read_person_comment_mention.recipient_id {
Err(LemmyErrorType::CouldntUpdateComment)?
}
let person_comment_mention_id = read_person_comment_mention.id;
let read = Some(data.read);
PersonCommentMention::update(
&mut context.pool(),
person_comment_mention_id,
&PersonCommentMentionUpdateForm { read },
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
let person_comment_mention_id = read_person_comment_mention.id;
let person_id = local_user_view.person.id;
let person_comment_mention_view = PersonCommentMentionView::read(
&mut context.pool(),
person_comment_mention_id,
Some(person_id),
)
.await?;
Ok(Json(PersonCommentMentionResponse {
person_comment_mention_view,
}))
}

View file

@ -1,45 +0,0 @@
use actix_web::web::{Data, Json};
use lemmy_api_common::{
context::LemmyContext,
person::{MarkPersonMentionAsRead, PersonMentionResponse},
};
use lemmy_db_schema::{
source::person_mention::{PersonMention, PersonMentionUpdateForm},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonMentionView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_person_mention_as_read(
data: Json<MarkPersonMentionAsRead>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<PersonMentionResponse>> {
let person_mention_id = data.person_mention_id;
let read_person_mention = PersonMention::read(&mut context.pool(), person_mention_id).await?;
if local_user_view.person.id != read_person_mention.recipient_id {
Err(LemmyErrorType::CouldntUpdateComment)?
}
let person_mention_id = read_person_mention.id;
let read = Some(data.read);
PersonMention::update(
&mut context.pool(),
person_mention_id,
&PersonMentionUpdateForm { read },
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdateComment)?;
let person_mention_id = read_person_mention.id;
let person_id = local_user_view.person.id;
let person_mention_view =
PersonMentionView::read(&mut context.pool(), person_mention_id, Some(person_id)).await?;
Ok(Json(PersonMentionResponse {
person_mention_view,
}))
}

View file

@ -0,0 +1,47 @@
use actix_web::web::{Data, Json};
use lemmy_api_common::{
context::LemmyContext,
person::{MarkPersonPostMentionAsRead, PersonPostMentionResponse},
};
use lemmy_db_schema::{
source::person_post_mention::{PersonPostMention, PersonPostMentionUpdateForm},
traits::Crud,
};
use lemmy_db_views::structs::LocalUserView;
use lemmy_db_views_actor::structs::PersonPostMentionView;
use lemmy_utils::error::{LemmyErrorExt, LemmyErrorType, LemmyResult};
#[tracing::instrument(skip(context))]
pub async fn mark_post_mention_as_read(
data: Json<MarkPersonPostMentionAsRead>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> LemmyResult<Json<PersonPostMentionResponse>> {
let person_post_mention_id = data.person_post_mention_id;
let read_person_post_mention =
PersonPostMention::read(&mut context.pool(), person_post_mention_id).await?;
if local_user_view.person.id != read_person_post_mention.recipient_id {
Err(LemmyErrorType::CouldntUpdatePost)?
}
let person_post_mention_id = read_person_post_mention.id;
let read = Some(data.read);
PersonPostMention::update(
&mut context.pool(),
person_post_mention_id,
&PersonPostMentionUpdateForm { read },
)
.await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
let person_post_mention_id = read_person_post_mention.id;
let person_id = local_user_view.person.id;
let person_post_mention_view =
PersonPostMentionView::read(&mut context.pool(), person_post_mention_id, Some(person_id))
.await?;
Ok(Json(PersonPostMentionResponse {
person_post_mention_view,
}))
}

View file

@ -1,6 +1,8 @@
pub mod list_mentions; pub mod list_comment_mentions;
pub mod list_post_mentions;
pub mod list_replies; pub mod list_replies;
pub mod mark_all_read; pub mod mark_all_read;
pub mod mark_mention_read; pub mod mark_comment_mention_read;
pub mod mark_post_mention_read;
pub mod mark_reply_read; pub mod mark_reply_read;
pub mod unread_count; pub mod unread_count;

View file

@ -1,7 +1,11 @@
use actix_web::web::{Data, Json}; use actix_web::web::{Data, Json};
use lemmy_api_common::{context::LemmyContext, person::GetUnreadCountResponse}; use lemmy_api_common::{context::LemmyContext, person::GetUnreadCountResponse};
use lemmy_db_views::structs::{LocalUserView, PrivateMessageView}; use lemmy_db_views::structs::{LocalUserView, PrivateMessageView};
use lemmy_db_views_actor::structs::{CommentReplyView, PersonMentionView}; use lemmy_db_views_actor::structs::{
CommentReplyView,
PersonCommentMentionView,
PersonPostMentionView,
};
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
#[tracing::instrument(skip(context))] #[tracing::instrument(skip(context))]
@ -12,18 +16,23 @@ pub async fn unread_count(
let person_id = local_user_view.person.id; let person_id = local_user_view.person.id;
let replies = let replies =
CommentReplyView::get_unread_replies(&mut context.pool(), &local_user_view.local_user).await?; CommentReplyView::get_unread_count(&mut context.pool(), &local_user_view.local_user).await?;
let mentions = let comment_mentions =
PersonMentionView::get_unread_mentions(&mut context.pool(), &local_user_view.local_user) PersonCommentMentionView::get_unread_count(&mut context.pool(), &local_user_view.local_user)
.await?;
let post_mentions =
PersonPostMentionView::get_unread_count(&mut context.pool(), &local_user_view.local_user)
.await?; .await?;
let private_messages = let private_messages =
PrivateMessageView::get_unread_messages(&mut context.pool(), person_id).await?; PrivateMessageView::get_unread_count(&mut context.pool(), person_id).await?;
Ok(Json(GetUnreadCountResponse { Ok(Json(GetUnreadCountResponse {
replies, replies,
mentions, comment_mentions,
post_mentions,
private_messages, private_messages,
})) }))
} }

View file

@ -5,9 +5,15 @@ use lemmy_api_common::{
context::LemmyContext, context::LemmyContext,
post::{CreatePostLike, PostResponse}, post::{CreatePostLike, PostResponse},
send_activity::{ActivityChannel, SendActivityData}, send_activity::{ActivityChannel, SendActivityData},
utils::{check_bot_account, check_community_user_action, check_local_vote_mode, VoteItem}, utils::{
check_bot_account,
check_community_user_action,
check_local_vote_mode,
mark_post_as_read,
},
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::PostOrCommentId,
source::{ source::{
local_site::LocalSite, local_site::LocalSite,
post::{PostLike, PostLikeForm, PostRead, PostReadForm}, post::{PostLike, PostLikeForm, PostRead, PostReadForm},
@ -29,7 +35,7 @@ pub async fn like_post(
check_local_vote_mode( check_local_vote_mode(
data.score, data.score,
VoteItem::Post(post_id), PostOrCommentId::Post(post_id),
&local_site, &local_site,
local_user_view.person.id, local_user_view.person.id,
&mut context.pool(), &mut context.pool(),

View file

@ -12,15 +12,15 @@ use crate::{
}; };
use actix_web::web::Json; use actix_web::web::Json;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::{CommentId, CommunityId, LocalUserId, PostId}, newtypes::{CommentId, CommunityId, LocalUserId, PostId, PostOrCommentId},
source::{ source::{
actor_language::CommunityLanguage, actor_language::CommunityLanguage,
comment::Comment, comment::Comment,
comment_reply::{CommentReply, CommentReplyInsertForm}, comment_reply::{CommentReply, CommentReplyInsertForm},
community::Community, community::Community,
person::Person, person::Person,
person_mention::{PersonMention, PersonMentionInsertForm}, person_comment_mention::{PersonCommentMention, PersonCommentMentionInsertForm},
post::Post, person_post_mention::{PersonPostMention, PersonPostMentionInsertForm},
}, },
traits::Crud, traits::Crud,
}; };
@ -94,7 +94,7 @@ pub async fn build_post_response(
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn send_local_notifs( pub async fn send_local_notifs(
mentions: Vec<MentionData>, mentions: Vec<MentionData>,
comment_id: CommentId, post_or_comment_id: PostOrCommentId,
person: &Person, person: &Person,
do_send_email: bool, do_send_email: bool,
context: &LemmyContext, context: &LemmyContext,
@ -125,6 +125,34 @@ pub async fn send_local_notifs(
let community = Community::read(&mut context.pool(), post.community_id).await?; let community = Community::read(&mut context.pool(), post.community_id).await?;
(comment, post, community) (comment, post, community)
}; };
// let person = my_local_user.person;
// Read the comment view to get extra info
let (comment_opt, post, community) = match post_or_comment_id {
PostOrCommentId::Post(post_id) => {
let post_view = PostView::read(
&mut context.pool(),
post_id,
local_user_view.map(|view| &view.local_user),
false,
)
.await?;
(None, post_view.post, post_view.community)
}
PostOrCommentId::Comment(comment_id) => {
let comment_view = CommentView::read(
&mut context.pool(),
comment_id,
local_user_view.map(|view| &view.local_user),
)
.await?;
(
Some(comment_view.comment),
comment_view.post,
comment_view.community,
)
}
};
// Send the local mentions // Send the local mentions
for mention in mentions for mention in mentions
@ -140,22 +168,38 @@ pub async fn send_local_notifs(
// below by checking recipient ids // below by checking recipient ids
recipient_ids.push(mention_user_view.local_user.id); recipient_ids.push(mention_user_view.local_user.id);
let user_mention_form = PersonMentionInsertForm { // Make the correct reply form depending on whether its a post or comment mention
recipient_id: mention_user_view.person.id, let comment_content_or_post_body = if let Some(comment) = &comment_opt {
comment_id, let person_comment_mention_form = PersonCommentMentionInsertForm {
read: None, recipient_id: mention_user_view.person.id,
}; comment_id: comment.id,
read: None,
};
// Allow this to fail softly, since comment edits might re-update or replace it // Allow this to fail softly, since comment edits might re-update or replace it
// Let the uniqueness handle this fail // Let the uniqueness handle this fail
PersonMention::create(&mut context.pool(), &user_mention_form) PersonCommentMention::create(&mut context.pool(), &person_comment_mention_form)
.await .await
.ok(); .ok();
comment.content.clone()
} else {
let person_post_mention_form = PersonPostMentionInsertForm {
recipient_id: mention_user_view.person.id,
post_id: post.id,
read: None,
};
// Allow this to fail softly, since edits might re-update or replace it
PersonPostMention::create(&mut context.pool(), &person_post_mention_form)
.await
.ok();
post.body.clone().unwrap_or_default()
};
// Send an email to those local users that have notifications on // Send an email to those local users that have notifications on
if do_send_email { if do_send_email {
let lang = get_interface_language(&mention_user_view); let lang = get_interface_language(&mention_user_view);
let content = markdown_to_html(&comment.content); let content = markdown_to_html(&comment_content_or_post_body);
send_email_to_user( send_email_to_user(
&mention_user_view, &mention_user_view,
&lang.notification_mentioned_by_subject(&person.name), &lang.notification_mentioned_by_subject(&person.name),
@ -168,99 +212,101 @@ pub async fn send_local_notifs(
} }
// Send comment_reply to the parent commenter / poster // Send comment_reply to the parent commenter / poster
if let Some(parent_comment_id) = comment.parent_comment_id() { if let Some(comment) = &comment_opt {
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id).await?; if let Some(parent_comment_id) = comment.parent_comment_id() {
let parent_comment = Comment::read(&mut context.pool(), parent_comment_id).await?;
// Get the parent commenter local_user // Get the parent commenter local_user
let parent_creator_id = parent_comment.creator_id; let parent_creator_id = parent_comment.creator_id;
let check_blocks = check_person_instance_community_block( let check_blocks = check_person_instance_community_block(
person.id, person.id,
parent_creator_id, parent_creator_id,
// Only block from the community's instance_id // Only block from the community's instance_id
community.instance_id, community.instance_id,
community.id, community.id,
&mut context.pool(), &mut context.pool(),
) )
.await .await
.is_err(); .is_err();
// Don't send a notif to yourself // Don't send a notif to yourself
if parent_comment.creator_id != person.id && !check_blocks { if parent_comment.creator_id != person.id && !check_blocks {
let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await; let user_view = LocalUserView::read_person(&mut context.pool(), parent_creator_id).await;
if let Ok(parent_user_view) = user_view { if let Ok(parent_user_view) = user_view {
// Don't duplicate notif if already mentioned by checking recipient ids // Don't duplicate notif if already mentioned by checking recipient ids
if !recipient_ids.contains(&parent_user_view.local_user.id) { if !recipient_ids.contains(&parent_user_view.local_user.id) {
recipient_ids.push(parent_user_view.local_user.id); recipient_ids.push(parent_user_view.local_user.id);
let comment_reply_form = CommentReplyInsertForm { let comment_reply_form = CommentReplyInsertForm {
recipient_id: parent_user_view.person.id, recipient_id: parent_user_view.person.id,
comment_id: comment.id, comment_id: comment.id,
read: None, read: None,
}; };
// Allow this to fail softly, since comment edits might re-update or replace it // Allow this to fail softly, since comment edits might re-update or replace it
// Let the uniqueness handle this fail // Let the uniqueness handle this fail
CommentReply::create(&mut context.pool(), &comment_reply_form) CommentReply::create(&mut context.pool(), &comment_reply_form)
.await .await
.ok(); .ok();
if do_send_email { if do_send_email {
let lang = get_interface_language(&parent_user_view); let lang = get_interface_language(&parent_user_view);
let content = markdown_to_html(&comment.content); let content = markdown_to_html(&comment.content);
send_email_to_user( send_email_to_user(
&parent_user_view, &parent_user_view,
&lang.notification_comment_reply_subject(&person.name), &lang.notification_comment_reply_subject(&person.name),
&lang.notification_comment_reply_body(&content, &inbox_link, &person.name), &lang.notification_comment_reply_body(&content, &inbox_link, &person.name),
context.settings(), context.settings(),
) )
.await .await
}
} }
} }
} }
} } else {
} else { // Use the post creator to check blocks
// Use the post creator to check blocks let check_blocks = check_person_instance_community_block(
let check_blocks = check_person_instance_community_block( person.id,
person.id, post.creator_id,
post.creator_id, // Only block from the community's instance_id
// Only block from the community's instance_id community.instance_id,
community.instance_id, community.id,
community.id, &mut context.pool(),
&mut context.pool(), )
) .await
.await .is_err();
.is_err();
if post.creator_id != person.id && !check_blocks { if post.creator_id != person.id && !check_blocks {
let creator_id = post.creator_id; let creator_id = post.creator_id;
let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await; let parent_user = LocalUserView::read_person(&mut context.pool(), creator_id).await;
if let Ok(parent_user_view) = parent_user { if let Ok(parent_user_view) = parent_user {
if !recipient_ids.contains(&parent_user_view.local_user.id) { if !recipient_ids.contains(&parent_user_view.local_user.id) {
recipient_ids.push(parent_user_view.local_user.id); recipient_ids.push(parent_user_view.local_user.id);
let comment_reply_form = CommentReplyInsertForm { let comment_reply_form = CommentReplyInsertForm {
recipient_id: parent_user_view.person.id, recipient_id: parent_user_view.person.id,
comment_id: comment.id, comment_id: comment.id,
read: None, read: None,
}; };
// Allow this to fail softly, since comment edits might re-update or replace it // Allow this to fail softly, since comment edits might re-update or replace it
// Let the uniqueness handle this fail // Let the uniqueness handle this fail
CommentReply::create(&mut context.pool(), &comment_reply_form) CommentReply::create(&mut context.pool(), &comment_reply_form)
.await .await
.ok(); .ok();
if do_send_email { if do_send_email {
let lang = get_interface_language(&parent_user_view); let lang = get_interface_language(&parent_user_view);
let content = markdown_to_html(&comment.content); let content = markdown_to_html(&comment.content);
send_email_to_user( send_email_to_user(
&parent_user_view, &parent_user_view,
&lang.notification_post_reply_subject(&person.name), &lang.notification_post_reply_subject(&person.name),
&lang.notification_post_reply_body(&content, &inbox_link, &person.name), &lang.notification_post_reply_body(&content, &inbox_link, &person.name),
context.settings(), context.settings(),
) )
.await .await
}
} }
} }
} }

View file

@ -1,5 +1,12 @@
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonId, PersonMentionId}, newtypes::{
CommentReplyId,
CommunityId,
LanguageId,
PersonCommentMentionId,
PersonId,
PersonPostMentionId,
},
sensitive::SensitiveString, sensitive::SensitiveString,
source::{login_token::LoginToken, site::Site}, source::{login_token::LoginToken, site::Site},
CommentSortType, CommentSortType,
@ -16,7 +23,8 @@ use lemmy_db_views::structs::{
use lemmy_db_views_actor::structs::{ use lemmy_db_views_actor::structs::{
CommentReplyView, CommentReplyView,
CommunityModeratorView, CommunityModeratorView,
PersonMentionView, PersonCommentMentionView,
PersonPostMentionView,
PersonView, PersonView,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -394,8 +402,7 @@ pub struct GetRepliesResponse {
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Get mentions for your user. /// Get mentions for your user.
pub struct GetPersonMentions { pub struct GetPersonCommentMentions {
#[cfg_attr(feature = "full", ts(optional))]
pub sort: Option<CommentSortType>, pub sort: Option<CommentSortType>,
#[cfg_attr(feature = "full", ts(optional))] #[cfg_attr(feature = "full", ts(optional))]
pub page: Option<i64>, pub page: Option<i64>,
@ -409,16 +416,16 @@ pub struct GetPersonMentions {
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// The response of mentions for your user. /// The response of mentions for your user.
pub struct GetPersonMentionsResponse { pub struct GetPersonCommentMentionsResponse {
pub mentions: Vec<PersonMentionView>, pub comment_mentions: Vec<PersonCommentMentionView>,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// Mark a person mention as read. /// Mark a person mention as read.
pub struct MarkPersonMentionAsRead { pub struct MarkPersonCommentMentionAsRead {
pub person_mention_id: PersonMentionId, pub person_comment_mention_id: PersonCommentMentionId,
pub read: bool, pub read: bool,
} }
@ -426,8 +433,45 @@ pub struct MarkPersonMentionAsRead {
#[cfg_attr(feature = "full", derive(TS))] #[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// The response for a person mention action. /// The response for a person mention action.
pub struct PersonMentionResponse { pub struct PersonCommentMentionResponse {
pub person_mention_view: PersonMentionView, pub person_comment_mention_view: PersonCommentMentionView,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Get mentions for your user.
pub struct GetPersonPostMentions {
pub sort: Option<PostSortType>,
pub page: Option<i64>,
pub limit: Option<i64>,
pub unread_only: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// The response of mentions for your user.
pub struct GetPersonPostMentionsResponse {
pub post_mentions: Vec<PersonPostMentionView>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// Mark a person mention as read.
pub struct MarkPersonPostMentionAsRead {
pub person_post_mention_id: PersonPostMentionId,
pub read: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS))]
#[cfg_attr(feature = "full", ts(export))]
/// The response for a person mention action.
pub struct PersonPostMentionResponse {
pub person_post_mention_view: PersonPostMentionView,
} }
#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] #[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)]
@ -499,7 +543,8 @@ pub struct GetReportCountResponse {
/// A response containing counts for your notifications. /// A response containing counts for your notifications.
pub struct GetUnreadCountResponse { pub struct GetUnreadCountResponse {
pub replies: i64, pub replies: i64,
pub mentions: i64, pub comment_mentions: i64,
pub post_mentions: i64,
pub private_messages: i64, pub private_messages: i64,
} }

View file

@ -11,7 +11,7 @@ use chrono::{DateTime, Days, Local, TimeZone, Utc};
use enum_map::{enum_map, EnumMap}; use enum_map::{enum_map, EnumMap};
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm}, aggregates::structs::{PersonPostAggregates, PersonPostAggregatesForm},
newtypes::{CommentId, CommunityId, DbUrl, InstanceId, PersonId, PostId}, newtypes::{CommentId, CommunityId, DbUrl, InstanceId, PersonId, PostId, PostOrCommentId},
source::{ source::{
comment::{Comment, CommentLike, CommentUpdateForm}, comment::{Comment, CommentLike, CommentUpdateForm},
community::{Community, CommunityModerator, CommunityUpdateForm}, community::{Community, CommunityModerator, CommunityUpdateForm},
@ -293,23 +293,17 @@ pub async fn check_person_instance_community_block(
Ok(()) Ok(())
} }
/// A vote item type used to check the vote mode.
pub enum VoteItem {
Post(PostId),
Comment(CommentId),
}
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub async fn check_local_vote_mode( pub async fn check_local_vote_mode(
score: i16, score: i16,
vote_item: VoteItem, post_or_comment_id: PostOrCommentId,
local_site: &LocalSite, local_site: &LocalSite,
person_id: PersonId, person_id: PersonId,
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
) -> LemmyResult<()> { ) -> LemmyResult<()> {
let (downvote_setting, upvote_setting) = match vote_item { let (downvote_setting, upvote_setting) = match post_or_comment_id {
VoteItem::Post(_) => (local_site.post_downvotes, local_site.post_upvotes), PostOrCommentId::Post(_) => (local_site.post_downvotes, local_site.post_upvotes),
VoteItem::Comment(_) => (local_site.comment_downvotes, local_site.comment_upvotes), PostOrCommentId::Comment(_) => (local_site.comment_downvotes, local_site.comment_upvotes),
}; };
let downvote_fail = score == -1 && downvote_setting == FederationMode::Disable; let downvote_fail = score == -1 && downvote_setting == FederationMode::Disable;
@ -317,9 +311,11 @@ pub async fn check_local_vote_mode(
// Undo previous vote for item if new vote fails // Undo previous vote for item if new vote fails
if downvote_fail || upvote_fail { if downvote_fail || upvote_fail {
match vote_item { match post_or_comment_id {
VoteItem::Post(post_id) => PostLike::remove(pool, person_id, post_id).await?, PostOrCommentId::Post(post_id) => PostLike::remove(pool, person_id, post_id).await?,
VoteItem::Comment(comment_id) => CommentLike::remove(pool, person_id, comment_id).await?, PostOrCommentId::Comment(comment_id) => {
CommentLike::remove(pool, person_id, comment_id).await?
}
}; };
} }
Ok(()) Ok(())

View file

@ -16,12 +16,13 @@ use lemmy_api_common::{
}, },
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
impls::actor_language::validate_post_language, impls::actor_language::default_post_language,
newtypes::PostOrCommentId,
source::{ source::{
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm}, comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm},
comment_reply::{CommentReply, CommentReplyUpdateForm}, comment_reply::{CommentReply, CommentReplyUpdateForm},
local_site::LocalSite, local_site::LocalSite,
person_mention::{PersonMention, PersonMentionUpdateForm}, person_comment_mention::{PersonCommentMention, PersonCommentMentionUpdateForm},
}, },
traits::{Crud, Likeable}, traits::{Crud, Likeable},
}; };
@ -117,7 +118,7 @@ pub async fn create_comment(
let mentions = scrape_text_for_mentions(&content); let mentions = scrape_text_for_mentions(&content);
let recipient_ids = send_local_notifs( let recipient_ids = send_local_notifs(
mentions, mentions,
inserted_comment_id, PostOrCommentId::Comment(inserted_comment_id),
&local_user_view.person, &local_user_view.person,
true, true,
&context, &context,
@ -169,17 +170,18 @@ pub async fn create_comment(
.with_lemmy_type(LemmyErrorType::CouldntUpdateReplies)?; .with_lemmy_type(LemmyErrorType::CouldntUpdateReplies)?;
} }
// If the parent has PersonMentions mark them as read too // If the parent has PersonCommentMentions mark them as read too
let person_mention = let person_comment_mention =
PersonMention::read_by_comment_and_person(&mut context.pool(), parent_id, person_id).await; PersonCommentMention::read_by_comment_and_person(&mut context.pool(), parent_id, person_id)
if let Ok(Some(mention)) = person_mention { .await;
PersonMention::update( if let Ok(Some(mention)) = person_comment_mention {
PersonCommentMention::update(
&mut context.pool(), &mut context.pool(),
mention.id, mention.id,
&PersonMentionUpdateForm { read: Some(true) }, &PersonCommentMentionUpdateForm { read: Some(true) },
) )
.await .await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePersonMentions)?; .with_lemmy_type(LemmyErrorType::CouldntUpdatePersonCommentMentions)?;
} }
} }

View file

@ -8,6 +8,7 @@ use lemmy_api_common::{
utils::check_community_user_action, utils::check_community_user_action,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::PostOrCommentId,
source::comment::{Comment, CommentUpdateForm}, source::comment::{Comment, CommentUpdateForm},
traits::Crud, traits::Crud,
}; };
@ -60,7 +61,7 @@ pub async fn delete_comment(
let recipient_ids = send_local_notifs( let recipient_ids = send_local_notifs(
vec![], vec![],
comment_id, PostOrCommentId::Comment(comment_id),
&local_user_view.person, &local_user_view.person,
false, false,
&context, &context,

View file

@ -8,6 +8,7 @@ use lemmy_api_common::{
utils::check_community_mod_action, utils::check_community_mod_action,
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::PostOrCommentId,
source::{ source::{
comment::{Comment, CommentUpdateForm}, comment::{Comment, CommentUpdateForm},
comment_report::CommentReport, comment_report::CommentReport,
@ -82,7 +83,7 @@ pub async fn remove_comment(
let recipient_ids = send_local_notifs( let recipient_ids = send_local_notifs(
vec![], vec![],
comment_id, PostOrCommentId::Comment(comment_id),
&local_user_view.person, &local_user_view.person,
false, false,
&context, &context,

View file

@ -14,7 +14,7 @@ use lemmy_api_common::{
}, },
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
impls::actor_language::validate_post_language, newtypes::PostOrCommentId,
source::{ source::{
comment::{Comment, CommentUpdateForm}, comment::{Comment, CommentUpdateForm},
local_site::LocalSite, local_site::LocalSite,
@ -86,7 +86,7 @@ pub async fn update_comment(
let mentions = scrape_text_for_mentions(&updated_comment_content); let mentions = scrape_text_for_mentions(&updated_comment_content);
let recipient_ids = send_local_notifs( let recipient_ids = send_local_notifs(
mentions, mentions,
comment_id, PostOrCommentId::Comment(comment_id),
&local_user_view.person, &local_user_view.person,
false, false,
&context, &context,

View file

@ -2,7 +2,7 @@ use super::convert_published_time;
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use actix_web::web::Json; use actix_web::web::Json;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::{build_post_response, send_local_notifs},
context::LemmyContext, context::LemmyContext,
post::{CreatePost, PostResponse}, post::{CreatePost, PostResponse},
request::generate_post_link_metadata, request::generate_post_link_metadata,
@ -16,7 +16,8 @@ use lemmy_api_common::{
}, },
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
impls::actor_language::validate_post_language, impls::actor_language::default_post_language,
newtypes::PostOrCommentId,
source::{ source::{
community::Community, community::Community,
local_site::LocalSite, local_site::LocalSite,
@ -32,6 +33,7 @@ use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
spawn_try_task, spawn_try_task,
utils::{ utils::{
mention::scrape_text_for_mentions,
slurs::check_slurs, slurs::check_slurs,
validation::{ validation::{
is_url_blocked, is_url_blocked,
@ -148,8 +150,21 @@ pub async fn create_post(
.await .await
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?; .with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
let read_form = PostReadForm::new(post_id, person_id); // Scan the post body for user mentions, add those rows
PostRead::mark_as_read(&mut context.pool(), &read_form).await?; let mentions = scrape_text_for_mentions(&inserted_post.body.clone().unwrap_or_default());
send_local_notifs(
mentions,
PostOrCommentId::Post(inserted_post.id),
&local_user_view.person,
true,
&context,
Some(&local_user_view),
)
.await?;
// TODO
PostRead::mark_as_read(&mut context.pool(), &read_form).await?;
mark_post_as_read(person_id, post_id, &mut context.pool()).await?;
build_post_response(&context, community_id, local_user_view, post_id).await build_post_response(&context, community_id, local_user_view, post_id).await
} }

View file

@ -3,7 +3,7 @@ use activitypub_federation::config::Data;
use actix_web::web::Json; use actix_web::web::Json;
use chrono::Utc; use chrono::Utc;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::{build_post_response, send_local_notifs},
context::LemmyContext, context::LemmyContext,
post::{EditPost, PostResponse}, post::{EditPost, PostResponse},
request::generate_post_link_metadata, request::generate_post_link_metadata,
@ -16,6 +16,7 @@ use lemmy_api_common::{
}, },
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::PostOrCommentId,
impls::actor_language::validate_post_language, impls::actor_language::validate_post_language,
source::{ source::{
community::Community, community::Community,
@ -29,6 +30,7 @@ use lemmy_db_views::structs::{LocalUserView, PostView};
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyErrorExt, LemmyErrorType, LemmyResult}, error::{LemmyErrorExt, LemmyErrorType, LemmyResult},
utils::{ utils::{
mention::scrape_text_for_mentions,
slurs::check_slurs, slurs::check_slurs,
validation::{ validation::{
is_url_blocked, is_url_blocked,
@ -142,6 +144,18 @@ pub async fn update_post(
.await .await
.with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?; .with_lemmy_type(LemmyErrorType::CouldntUpdatePost)?;
// Scan the post body for user mentions, add those rows
let mentions = scrape_text_for_mentions(&updated_post.body.clone().unwrap_or_default());
send_local_notifs(
mentions,
PostOrCommentId::Post(updated_post.id),
&local_user_view.person,
false,
&context,
Some(&local_user_view),
)
.await?;
// send out federation/webmention if necessary // send out federation/webmention if necessary
match ( match (
orig_post.post.scheduled_publish_time, orig_post.post.scheduled_publish_time,

View file

@ -29,7 +29,7 @@ use lemmy_api_common::{
}; };
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::CommentAggregates, aggregates::structs::CommentAggregates,
newtypes::PersonId, newtypes::{PersonId, PostOrCommentId},
source::{ source::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
comment::{Comment, CommentLike, CommentLikeForm}, comment::{Comment, CommentLike, CommentLikeForm},
@ -176,10 +176,17 @@ impl ActivityHandler for CreateOrUpdateNote {
// TODO: for compatibility with other projects, it would be much better to read this from cc or // TODO: for compatibility with other projects, it would be much better to read this from cc or
// tags // tags
let mentions = scrape_text_for_mentions(&comment.content); let mentions = scrape_text_for_mentions(&comment.content);
// TODO: this fails in local community comment as CommentView::read() returns nothing // TODO: this fails in local community comment as CommentView::read() returns nothing
// without passing LocalUser // without passing LocalUser
send_local_notifs(mentions, comment.id, &actor, do_send_email, context, None).await?; send_local_notifs(
mentions,
PostOrCommentId::Comment(comment.id),
&actor,
do_send_email,
context,
None,
)
.await?;
Ok(()) Ok(())
} }
} }

View file

@ -20,10 +20,10 @@ use activitypub_federation::{
protocol::verification::{verify_domains_match, verify_urls_match}, protocol::verification::{verify_domains_match, verify_urls_match},
traits::{ActivityHandler, Actor, Object}, traits::{ActivityHandler, Actor, Object},
}; };
use lemmy_api_common::context::LemmyContext; use lemmy_api_common::{build_response::send_local_notifs, context::LemmyContext};
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::PostAggregates, aggregates::structs::PostAggregates,
newtypes::PersonId, newtypes::{PersonId, PostOrCommentId},
source::{ source::{
activity::ActivitySendTargets, activity::ActivitySendTargets,
community::Community, community::Community,
@ -32,7 +32,10 @@ use lemmy_db_schema::{
}, },
traits::{Crud, Likeable}, traits::{Crud, Likeable},
}; };
use lemmy_utils::error::{LemmyError, LemmyResult}; use lemmy_utils::{
error::{LemmyError, LemmyResult},
utils::mention::scrape_text_for_mentions,
};
use url::Url; use url::Url;
impl CreateOrUpdatePage { impl CreateOrUpdatePage {
@ -124,6 +127,21 @@ impl ActivityHandler for CreateOrUpdatePage {
// Calculate initial hot_rank for post // Calculate initial hot_rank for post
PostAggregates::update_ranks(&mut context.pool(), post.id).await?; PostAggregates::update_ranks(&mut context.pool(), post.id).await?;
let do_send_email = self.kind == CreateOrUpdateType::Create;
let actor = self.actor.dereference(context).await?;
// Send the post body mentions
let mentions = scrape_text_for_mentions(&post.body.clone().unwrap_or_default());
send_local_notifs(
mentions,
PostOrCommentId::Post(post.id),
&actor,
do_send_email,
context,
None,
)
.await?;
Ok(()) Ok(())
} }
} }

View file

@ -27,7 +27,8 @@ pub mod oauth_provider;
pub mod password_reset_request; pub mod password_reset_request;
pub mod person; pub mod person;
pub mod person_block; pub mod person_block;
pub mod person_mention; pub mod person_comment_mention;
pub mod person_post_mention;
pub mod post; pub mod post;
pub mod post_report; pub mod post_report;
pub mod private_message; pub mod private_message;

View file

@ -0,0 +1,83 @@
use crate::{
diesel::OptionalExtension,
newtypes::{CommentId, PersonCommentMentionId, PersonId},
schema::person_comment_mention,
source::person_comment_mention::{
PersonCommentMention,
PersonCommentMentionInsertForm,
PersonCommentMentionUpdateForm,
},
traits::Crud,
utils::{get_conn, DbPool},
};
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
#[async_trait]
impl Crud for PersonCommentMention {
type InsertForm = PersonCommentMentionInsertForm;
type UpdateForm = PersonCommentMentionUpdateForm;
type IdType = PersonCommentMentionId;
async fn create(
pool: &mut DbPool<'_>,
person_comment_mention_form: &Self::InsertForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
// since the return here isnt utilized, we dont need to do an update
// but get_result doesn't return the existing row here
insert_into(person_comment_mention::table)
.values(person_comment_mention_form)
.on_conflict((
person_comment_mention::recipient_id,
person_comment_mention::comment_id,
))
.do_update()
.set(person_comment_mention_form)
.get_result::<Self>(conn)
.await
}
async fn update(
pool: &mut DbPool<'_>,
person_comment_mention_id: PersonCommentMentionId,
person_comment_mention_form: &Self::UpdateForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(person_comment_mention::table.find(person_comment_mention_id))
.set(person_comment_mention_form)
.get_result::<Self>(conn)
.await
}
}
impl PersonCommentMention {
pub async fn mark_all_as_read(
pool: &mut DbPool<'_>,
for_recipient_id: PersonId,
) -> Result<Vec<PersonCommentMention>, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(
person_comment_mention::table
.filter(person_comment_mention::recipient_id.eq(for_recipient_id))
.filter(person_comment_mention::read.eq(false)),
)
.set(person_comment_mention::read.eq(true))
.get_results::<Self>(conn)
.await
}
pub async fn read_by_comment_and_person(
pool: &mut DbPool<'_>,
for_comment_id: CommentId,
for_recipient_id: PersonId,
) -> Result<Option<Self>, Error> {
let conn = &mut get_conn(pool).await?;
person_comment_mention::table
.filter(person_comment_mention::comment_id.eq(for_comment_id))
.filter(person_comment_mention::recipient_id.eq(for_recipient_id))
.first(conn)
.await
.optional()
}
}

View file

@ -1,76 +0,0 @@
use crate::{
diesel::OptionalExtension,
newtypes::{CommentId, PersonId, PersonMentionId},
schema::person_mention,
source::person_mention::{PersonMention, PersonMentionInsertForm, PersonMentionUpdateForm},
traits::Crud,
utils::{get_conn, DbPool},
};
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
#[async_trait]
impl Crud for PersonMention {
type InsertForm = PersonMentionInsertForm;
type UpdateForm = PersonMentionUpdateForm;
type IdType = PersonMentionId;
async fn create(
pool: &mut DbPool<'_>,
person_mention_form: &Self::InsertForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
// since the return here isnt utilized, we dont need to do an update
// but get_result doesn't return the existing row here
insert_into(person_mention::table)
.values(person_mention_form)
.on_conflict((person_mention::recipient_id, person_mention::comment_id))
.do_update()
.set(person_mention_form)
.get_result::<Self>(conn)
.await
}
async fn update(
pool: &mut DbPool<'_>,
person_mention_id: PersonMentionId,
person_mention_form: &Self::UpdateForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(person_mention::table.find(person_mention_id))
.set(person_mention_form)
.get_result::<Self>(conn)
.await
}
}
impl PersonMention {
pub async fn mark_all_as_read(
pool: &mut DbPool<'_>,
for_recipient_id: PersonId,
) -> Result<Vec<PersonMention>, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(
person_mention::table
.filter(person_mention::recipient_id.eq(for_recipient_id))
.filter(person_mention::read.eq(false)),
)
.set(person_mention::read.eq(true))
.get_results::<Self>(conn)
.await
}
pub async fn read_by_comment_and_person(
pool: &mut DbPool<'_>,
for_comment_id: CommentId,
for_recipient_id: PersonId,
) -> Result<Option<Self>, Error> {
let conn = &mut get_conn(pool).await?;
person_mention::table
.filter(person_mention::comment_id.eq(for_comment_id))
.filter(person_mention::recipient_id.eq(for_recipient_id))
.first(conn)
.await
.optional()
}
}

View file

@ -0,0 +1,83 @@
use crate::{
diesel::OptionalExtension,
newtypes::{PersonId, PersonPostMentionId, PostId},
schema::person_post_mention,
source::person_post_mention::{
PersonPostMention,
PersonPostMentionInsertForm,
PersonPostMentionUpdateForm,
},
traits::Crud,
utils::{get_conn, DbPool},
};
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl;
#[async_trait]
impl Crud for PersonPostMention {
type InsertForm = PersonPostMentionInsertForm;
type UpdateForm = PersonPostMentionUpdateForm;
type IdType = PersonPostMentionId;
async fn create(
pool: &mut DbPool<'_>,
person_post_mention_form: &Self::InsertForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
// since the return here isnt utilized, we dont need to do an update
// but get_result doesn't return the existing row here
insert_into(person_post_mention::table)
.values(person_post_mention_form)
.on_conflict((
person_post_mention::recipient_id,
person_post_mention::post_id,
))
.do_update()
.set(person_post_mention_form)
.get_result::<Self>(conn)
.await
}
async fn update(
pool: &mut DbPool<'_>,
person_post_mention_id: PersonPostMentionId,
person_post_mention_form: &Self::UpdateForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(person_post_mention::table.find(person_post_mention_id))
.set(person_post_mention_form)
.get_result::<Self>(conn)
.await
}
}
impl PersonPostMention {
pub async fn mark_all_as_read(
pool: &mut DbPool<'_>,
for_recipient_id: PersonId,
) -> Result<Vec<PersonPostMention>, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(
person_post_mention::table
.filter(person_post_mention::recipient_id.eq(for_recipient_id))
.filter(person_post_mention::read.eq(false)),
)
.set(person_post_mention::read.eq(true))
.get_results::<Self>(conn)
.await
}
pub async fn read_by_post_and_person(
pool: &mut DbPool<'_>,
for_post_id: PostId,
for_recipient_id: PersonId,
) -> Result<Option<Self>, Error> {
let conn = &mut get_conn(pool).await?;
person_post_mention::table
.filter(person_post_mention::post_id.eq(for_post_id))
.filter(person_post_mention::recipient_id.eq(for_recipient_id))
.first(conn)
.await
.optional()
}
}

View file

@ -55,6 +55,11 @@ impl fmt::Display for CommentId {
} }
} }
pub enum PostOrCommentId {
Post(PostId),
Comment(CommentId),
}
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)] #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Default, Serialize, Deserialize)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))] #[cfg_attr(feature = "full", derive(DieselNewType, TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
@ -82,8 +87,14 @@ impl fmt::Display for PrivateMessageId {
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)] #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))] #[cfg_attr(feature = "full", derive(DieselNewType, TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// The person mention id. /// The person comment mention id.
pub struct PersonMentionId(i32); pub struct PersonCommentMentionId(i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))]
#[cfg_attr(feature = "full", ts(export))]
/// The person post mention id.
pub struct PersonPostMentionId(i32);
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)] #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))] #[cfg_attr(feature = "full", derive(DieselNewType, TS))]

View file

@ -753,6 +753,16 @@ diesel::table! {
} }
} }
diesel::table! {
person_comment_mention (id) {
id -> Int4,
recipient_id -> Int4,
comment_id -> Int4,
read -> Bool,
published -> Timestamptz,
}
}
diesel::table! { diesel::table! {
person_content_combined (id) { person_content_combined (id) {
id -> Int4, id -> Int4,
@ -763,10 +773,10 @@ diesel::table! {
} }
diesel::table! { diesel::table! {
person_mention (id) { person_post_mention (id) {
id -> Int4, id -> Int4,
recipient_id -> Int4, recipient_id -> Int4,
comment_id -> Int4, post_id -> Int4,
read -> Bool, read -> Bool,
published -> Timestamptz, published -> Timestamptz,
} }
@ -1090,10 +1100,12 @@ diesel::joinable!(password_reset_request -> local_user (local_user_id));
diesel::joinable!(person -> instance (instance_id)); diesel::joinable!(person -> instance (instance_id));
diesel::joinable!(person_aggregates -> person (person_id)); diesel::joinable!(person_aggregates -> person (person_id));
diesel::joinable!(person_ban -> person (person_id)); diesel::joinable!(person_ban -> person (person_id));
diesel::joinable!(person_comment_mention -> comment (comment_id));
diesel::joinable!(person_comment_mention -> person (recipient_id));
diesel::joinable!(person_content_combined -> comment (comment_id)); diesel::joinable!(person_content_combined -> comment (comment_id));
diesel::joinable!(person_content_combined -> post (post_id)); diesel::joinable!(person_content_combined -> post (post_id));
diesel::joinable!(person_mention -> comment (comment_id)); diesel::joinable!(person_post_mention -> person (recipient_id));
diesel::joinable!(person_mention -> person (recipient_id)); diesel::joinable!(person_post_mention -> post (post_id));
diesel::joinable!(person_saved_combined -> comment (comment_id)); diesel::joinable!(person_saved_combined -> comment (comment_id));
diesel::joinable!(person_saved_combined -> person (person_id)); diesel::joinable!(person_saved_combined -> person (person_id));
diesel::joinable!(person_saved_combined -> post (post_id)); diesel::joinable!(person_saved_combined -> post (post_id));
@ -1172,8 +1184,9 @@ diesel::allow_tables_to_appear_in_same_query!(
person_actions, person_actions,
person_aggregates, person_aggregates,
person_ban, person_ban,
person_comment_mention,
person_content_combined, person_content_combined,
person_mention, person_post_mention,
person_saved_combined, person_saved_combined,
post, post,
post_actions, post_actions,

View file

@ -33,7 +33,8 @@ pub mod oauth_provider;
pub mod password_reset_request; pub mod password_reset_request;
pub mod person; pub mod person;
pub mod person_block; pub mod person_block;
pub mod person_mention; pub mod person_comment_mention;
pub mod person_post_mention;
pub mod post; pub mod post;
pub mod post_report; pub mod post_report;
pub mod private_message; pub mod private_message;

View file

@ -1,6 +1,6 @@
use crate::newtypes::{CommentId, PersonId, PersonMentionId}; use crate::newtypes::{CommentId, PersonCommentMentionId, PersonId};
#[cfg(feature = "full")] #[cfg(feature = "full")]
use crate::schema::person_mention; use crate::schema::person_comment_mention;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "full")] #[cfg(feature = "full")]
@ -12,12 +12,12 @@ use ts_rs::TS;
derive(Queryable, Selectable, Associations, Identifiable, TS) derive(Queryable, Selectable, Associations, Identifiable, TS)
)] )]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))] #[cfg_attr(feature = "full", diesel(belongs_to(crate::source::comment::Comment)))]
#[cfg_attr(feature = "full", diesel(table_name = person_mention))] #[cfg_attr(feature = "full", diesel(table_name = person_comment_mention))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// A person mention. /// A person mention.
pub struct PersonMention { pub struct PersonCommentMention {
pub id: PersonMentionId, pub id: PersonCommentMentionId,
pub recipient_id: PersonId, pub recipient_id: PersonId,
pub comment_id: CommentId, pub comment_id: CommentId,
pub read: bool, pub read: bool,
@ -25,15 +25,15 @@ pub struct PersonMention {
} }
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = person_mention))] #[cfg_attr(feature = "full", diesel(table_name = person_comment_mention))]
pub struct PersonMentionInsertForm { pub struct PersonCommentMentionInsertForm {
pub recipient_id: PersonId, pub recipient_id: PersonId,
pub comment_id: CommentId, pub comment_id: CommentId,
pub read: Option<bool>, pub read: Option<bool>,
} }
#[cfg_attr(feature = "full", derive(AsChangeset))] #[cfg_attr(feature = "full", derive(AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = person_mention))] #[cfg_attr(feature = "full", diesel(table_name = person_comment_mention))]
pub struct PersonMentionUpdateForm { pub struct PersonCommentMentionUpdateForm {
pub read: Option<bool>, pub read: Option<bool>,
} }

View file

@ -0,0 +1,39 @@
use crate::newtypes::{PersonId, PersonPostMentionId, PostId};
#[cfg(feature = "full")]
use crate::schema::person_post_mention;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[cfg(feature = "full")]
use ts_rs::TS;
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[cfg_attr(
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable, TS)
)]
#[cfg_attr(feature = "full", diesel(belongs_to(crate::source::post::Post)))]
#[cfg_attr(feature = "full", diesel(table_name = person_post_mention))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))]
/// A person mention.
pub struct PersonPostMention {
pub id: PersonPostMentionId,
pub recipient_id: PersonId,
pub post_id: PostId,
pub read: bool,
pub published: DateTime<Utc>,
}
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = person_post_mention))]
pub struct PersonPostMentionInsertForm {
pub recipient_id: PersonId,
pub post_id: PostId,
pub read: Option<bool>,
}
#[cfg_attr(feature = "full", derive(AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = person_post_mention))]
pub struct PersonPostMentionUpdateForm {
pub read: Option<bool>,
}

View file

@ -114,7 +114,7 @@ impl PrivateMessageView {
} }
/// Gets the number of unread messages /// Gets the number of unread messages
pub async fn get_unread_messages( pub async fn get_unread_count(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
my_person_id: PersonId, my_person_id: PersonId,
) -> Result<i64, Error> { ) -> Result<i64, Error> {
@ -348,7 +348,7 @@ mod tests {
assert_length!(1, &timmy_messages); assert_length!(1, &timmy_messages);
let timmy_unread_messages = PrivateMessageView::get_unread_messages(pool, timmy.id).await?; let timmy_unread_messages = PrivateMessageView::get_unread_count(pool, timmy.id).await?;
assert_eq!(timmy_unread_messages, 1); assert_eq!(timmy_unread_messages, 1);
cleanup(instance.id, pool).await cleanup(instance.id, pool).await
@ -390,7 +390,7 @@ mod tests {
assert_length!(0, &timmy_messages); assert_length!(0, &timmy_messages);
let timmy_unread_messages = PrivateMessageView::get_unread_messages(pool, timmy.id).await?; let timmy_unread_messages = PrivateMessageView::get_unread_count(pool, timmy.id).await?;
assert_eq!(timmy_unread_messages, 0); assert_eq!(timmy_unread_messages, 0);
cleanup(instance.id, pool).await cleanup(instance.id, pool).await
} }

View file

@ -165,7 +165,7 @@ impl CommentReplyView {
} }
/// Gets the number of unread replies /// Gets the number of unread replies
pub async fn get_unread_replies( pub async fn get_unread_count(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
local_user: &LocalUser, local_user: &LocalUser,
) -> Result<i64, Error> { ) -> Result<i64, Error> {
@ -305,7 +305,7 @@ mod tests {
CommentReply::update(pool, inserted_reply.id, &comment_reply_update_form).await?; CommentReply::update(pool, inserted_reply.id, &comment_reply_update_form).await?;
// Test to make sure counts and blocks work correctly // Test to make sure counts and blocks work correctly
let unread_replies = CommentReplyView::get_unread_replies(pool, &recipient_local_user).await?; let unread_replies = CommentReplyView::get_unread_count(pool, &recipient_local_user).await?;
let query = CommentReplyQuery { let query = CommentReplyQuery {
recipient_id: Some(recipient_id), recipient_id: Some(recipient_id),
@ -328,7 +328,7 @@ mod tests {
PersonBlock::block(pool, &block_form).await?; PersonBlock::block(pool, &block_form).await?;
let unread_replies_after_block = let unread_replies_after_block =
CommentReplyView::get_unread_replies(pool, &recipient_local_user).await?; CommentReplyView::get_unread_count(pool, &recipient_local_user).await?;
let replies_after_block = query.clone().list(pool).await?; let replies_after_block = query.clone().list(pool).await?;
assert_eq!(0, unread_replies_after_block); assert_eq!(0, unread_replies_after_block);
assert_eq!(0, replies_after_block.len()); assert_eq!(0, replies_after_block.len());
@ -356,7 +356,7 @@ mod tests {
let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id).await?; let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id).await?;
let unread_replies_after_hide_bots = let unread_replies_after_hide_bots =
CommentReplyView::get_unread_replies(pool, &recipient_local_user_view.local_user).await?; CommentReplyView::get_unread_count(pool, &recipient_local_user_view.local_user).await?;
let mut query_without_bots = query.clone(); let mut query_without_bots = query.clone();
query_without_bots.show_bot_accounts = false; query_without_bots.show_bot_accounts = false;

View file

@ -9,7 +9,7 @@ pub mod community_person_ban_view;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod community_view; pub mod community_view;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod person_mention_view; pub mod person_comment_mention_view;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod person_view; pub mod person_view;
pub mod structs; pub mod structs;

View file

@ -1,4 +1,4 @@
use crate::structs::PersonMentionView; use crate::structs::PersonCommentMentionView;
use diesel::{ use diesel::{
dsl::{exists, not}, dsl::{exists, not},
pg::Pg, pg::Pg,
@ -12,7 +12,7 @@ use diesel::{
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
aliases::{self, creator_community_actions}, aliases::{self, creator_community_actions},
newtypes::{PersonId, PersonMentionId}, newtypes::{PersonCommentMentionId, PersonId},
schema::{ schema::{
comment, comment,
comment_actions, comment_actions,
@ -22,7 +22,7 @@ use lemmy_db_schema::{
local_user, local_user,
person, person,
person_actions, person_actions,
person_mention, person_comment_mention,
post, post,
}, },
source::{community::CommunityFollower, local_user::LocalUser}, source::{community::CommunityFollower, local_user::LocalUser},
@ -41,8 +41,8 @@ use lemmy_db_schema::{
}; };
fn queries<'a>() -> Queries< fn queries<'a>() -> Queries<
impl ReadFn<'a, PersonMentionView, (PersonMentionId, Option<PersonId>)>, impl ReadFn<'a, PersonCommentMentionView, (PersonCommentMentionId, Option<PersonId>)>,
impl ListFn<'a, PersonMentionView, PersonMentionQuery>, impl ListFn<'a, PersonCommentMentionView, PersonCommentMentionQuery>,
> { > {
let creator_is_admin = exists( let creator_is_admin = exists(
local_user::table.filter( local_user::table.filter(
@ -52,7 +52,7 @@ fn queries<'a>() -> Queries<
), ),
); );
let all_joins = move |query: person_mention::BoxedQuery<'a, Pg>, let all_joins = move |query: person_comment_mention::BoxedQuery<'a, Pg>,
my_person_id: Option<PersonId>| { my_person_id: Option<PersonId>| {
query query
.inner_join(comment::table) .inner_join(comment::table)
@ -78,7 +78,7 @@ fn queries<'a>() -> Queries<
post::community_id, post::community_id,
)) ))
.select(( .select((
person_mention::all_columns, person_comment_mention::all_columns,
comment::all_columns, comment::all_columns,
person::all_columns, person::all_columns,
post::all_columns, post::all_columns,
@ -102,35 +102,42 @@ fn queries<'a>() -> Queries<
)) ))
}; };
let read = let read = move |mut conn: DbConn<'a>,
move |mut conn: DbConn<'a>, (person_comment_mention_id, my_person_id): (
(person_mention_id, my_person_id): (PersonMentionId, Option<PersonId>)| async move { PersonCommentMentionId,
all_joins( Option<PersonId>,
person_mention::table.find(person_mention_id).into_boxed(), )| async move {
my_person_id, all_joins(
) person_comment_mention::table
.first(&mut conn) .find(person_comment_mention_id)
.await .into_boxed(),
}; my_person_id,
)
.first(&mut conn)
.await
};
let list = move |mut conn: DbConn<'a>, options: PersonMentionQuery| async move { let list = move |mut conn: DbConn<'a>, options: PersonCommentMentionQuery| async move {
// These filters need to be kept in sync with the filters in // These filters need to be kept in sync with the filters in
// PersonMentionView::get_unread_mentions() // PersonCommentMentionView::get_unread_mentions()
let mut query = all_joins(person_mention::table.into_boxed(), options.my_person_id); let mut query = all_joins(
person_comment_mention::table.into_boxed(),
options.my_person_id,
);
if let Some(recipient_id) = options.recipient_id { if let Some(recipient_id) = options.recipient_id {
query = query.filter(person_mention::recipient_id.eq(recipient_id)); query = query.filter(person_comment_mention::recipient_id.eq(recipient_id));
} }
if options.unread_only { if options.unread_only {
query = query.filter(person_mention::read.eq(false)); query = query.filter(person_comment_mention::read.eq(false));
} }
if !options.show_bot_accounts { if !options.show_bot_accounts {
query = query.filter(not(person::bot_account)); query = query.filter(not(person::bot_account));
}; };
query = match options.sort.unwrap_or(CommentSortType::Hot) { query = match options.sort.unwrap_or(CommentSortType::New) {
CommentSortType::Hot => query.then_order_by(comment_aggregates::hot_rank.desc()), CommentSortType::Hot => query.then_order_by(comment_aggregates::hot_rank.desc()),
CommentSortType::Controversial => { CommentSortType::Controversial => {
query.then_order_by(comment_aggregates::controversy_rank.desc()) query.then_order_by(comment_aggregates::controversy_rank.desc())
@ -148,33 +155,33 @@ fn queries<'a>() -> Queries<
query query
.limit(limit) .limit(limit)
.offset(offset) .offset(offset)
.load::<PersonMentionView>(&mut conn) .load::<PersonCommentMentionView>(&mut conn)
.await .await
}; };
Queries::new(read, list) Queries::new(read, list)
} }
impl PersonMentionView { impl PersonCommentMentionView {
pub async fn read( pub async fn read(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
person_mention_id: PersonMentionId, person_comment_mention_id: PersonCommentMentionId,
my_person_id: Option<PersonId>, my_person_id: Option<PersonId>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
queries() queries()
.read(pool, (person_mention_id, my_person_id)) .read(pool, (person_comment_mention_id, my_person_id))
.await .await
} }
/// Gets the number of unread mentions /// Gets the number of unread mentions
pub async fn get_unread_mentions( pub async fn get_unread_count(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
local_user: &LocalUser, local_user: &LocalUser,
) -> Result<i64, Error> { ) -> Result<i64, Error> {
use diesel::dsl::count; use diesel::dsl::count;
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let mut query = person_mention::table let mut query = person_comment_mention::table
.inner_join(comment::table) .inner_join(comment::table)
.left_join(actions( .left_join(actions(
person_actions::table, person_actions::table,
@ -192,18 +199,18 @@ impl PersonMentionView {
query query
// Don't count replies from blocked users // Don't count replies from blocked users
.filter(person_actions::blocked.is_null()) .filter(person_actions::blocked.is_null())
.filter(person_mention::recipient_id.eq(local_user.person_id)) .filter(person_comment_mention::recipient_id.eq(local_user.person_id))
.filter(person_mention::read.eq(false)) .filter(person_comment_mention::read.eq(false))
.filter(comment::deleted.eq(false)) .filter(comment::deleted.eq(false))
.filter(comment::removed.eq(false)) .filter(comment::removed.eq(false))
.select(count(person_mention::id)) .select(count(person_comment_mention::id))
.first::<i64>(conn) .first::<i64>(conn)
.await .await
} }
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct PersonMentionQuery { pub struct PersonCommentMentionQuery {
pub my_person_id: Option<PersonId>, pub my_person_id: Option<PersonId>,
pub recipient_id: Option<PersonId>, pub recipient_id: Option<PersonId>,
pub sort: Option<CommentSortType>, pub sort: Option<CommentSortType>,
@ -213,8 +220,8 @@ pub struct PersonMentionQuery {
pub limit: Option<i64>, pub limit: Option<i64>,
} }
impl PersonMentionQuery { impl PersonCommentMentionQuery {
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PersonMentionView>, Error> { pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PersonCommentMentionView>, Error> {
queries().list(pool, self).await queries().list(pool, self).await
} }
} }
@ -222,7 +229,10 @@ impl PersonMentionQuery {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{person_mention_view::PersonMentionQuery, structs::PersonMentionView}; use crate::{
person_comment_mention_view::PersonCommentMentionQuery,
structs::PersonCommentMentionView,
};
use lemmy_db_schema::{ use lemmy_db_schema::{
source::{ source::{
comment::{Comment, CommentInsertForm}, comment::{Comment, CommentInsertForm},
@ -231,7 +241,11 @@ mod tests {
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm}, local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
person::{Person, PersonInsertForm, PersonUpdateForm}, person::{Person, PersonInsertForm, PersonUpdateForm},
person_block::{PersonBlock, PersonBlockForm}, person_block::{PersonBlock, PersonBlockForm},
person_mention::{PersonMention, PersonMentionInsertForm, PersonMentionUpdateForm}, person_comment_mention::{
PersonCommentMention,
PersonCommentMentionInsertForm,
PersonCommentMentionUpdateForm,
},
post::{Post, PostInsertForm}, post::{Post, PostInsertForm},
}, },
traits::{Blockable, Crud}, traits::{Blockable, Crud},
@ -284,15 +298,15 @@ mod tests {
); );
let inserted_comment = Comment::create(pool, &comment_form, None).await?; let inserted_comment = Comment::create(pool, &comment_form, None).await?;
let person_mention_form = PersonMentionInsertForm { let person_comment_mention_form = PersonCommentMentionInsertForm {
recipient_id: inserted_recipient.id, recipient_id: inserted_recipient.id,
comment_id: inserted_comment.id, comment_id: inserted_comment.id,
read: None, read: None,
}; };
let inserted_mention = PersonMention::create(pool, &person_mention_form).await?; let inserted_mention = PersonCommentMention::create(pool, &person_comment_mention_form).await?;
let expected_mention = PersonMention { let expected_mention = PersonCommentMention {
id: inserted_mention.id, id: inserted_mention.id,
recipient_id: inserted_mention.recipient_id, recipient_id: inserted_mention.recipient_id,
comment_id: inserted_mention.comment_id, comment_id: inserted_mention.comment_id,
@ -300,17 +314,21 @@ mod tests {
published: inserted_mention.published, published: inserted_mention.published,
}; };
let read_mention = PersonMention::read(pool, inserted_mention.id).await?; let read_mention = PersonCommentMention::read(pool, inserted_mention.id).await?;
let person_mention_update_form = PersonMentionUpdateForm { read: Some(false) }; let person_comment_mention_update_form = PersonCommentMentionUpdateForm { read: Some(false) };
let updated_mention = let updated_mention = PersonCommentMention::update(
PersonMention::update(pool, inserted_mention.id, &person_mention_update_form).await?; pool,
inserted_mention.id,
&person_comment_mention_update_form,
)
.await?;
// Test to make sure counts and blocks work correctly // Test to make sure counts and blocks work correctly
let unread_mentions = let unread_mentions =
PersonMentionView::get_unread_mentions(pool, &recipient_local_user).await?; PersonCommentMentionView::get_unread_count(pool, &recipient_local_user).await?;
let query = PersonMentionQuery { let query = PersonCommentMentionQuery {
recipient_id: Some(recipient_id), recipient_id: Some(recipient_id),
my_person_id: Some(recipient_id), my_person_id: Some(recipient_id),
sort: None, sort: None,
@ -331,7 +349,7 @@ mod tests {
PersonBlock::block(pool, &block_form).await?; PersonBlock::block(pool, &block_form).await?;
let unread_mentions_after_block = let unread_mentions_after_block =
PersonMentionView::get_unread_mentions(pool, &recipient_local_user).await?; PersonCommentMentionView::get_unread_count(pool, &recipient_local_user).await?;
let mentions_after_block = query.clone().list(pool).await?; let mentions_after_block = query.clone().list(pool).await?;
assert_eq!(0, unread_mentions_after_block); assert_eq!(0, unread_mentions_after_block);
assert_eq!(0, mentions_after_block.len()); assert_eq!(0, mentions_after_block.len());
@ -359,7 +377,8 @@ mod tests {
let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id).await?; let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id).await?;
let unread_mentions_after_hide_bots = let unread_mentions_after_hide_bots =
PersonMentionView::get_unread_mentions(pool, &recipient_local_user_view.local_user).await?; PersonCommentMentionView::get_unread_count(pool, &recipient_local_user_view.local_user)
.await?;
let mut query_without_bots = query.clone(); let mut query_without_bots = query.clone();
query_without_bots.show_bot_accounts = false; query_without_bots.show_bot_accounts = false;

View file

@ -1,13 +1,14 @@
#[cfg(feature = "full")] #[cfg(feature = "full")]
use diesel::Queryable; use diesel::Queryable;
use lemmy_db_schema::{ use lemmy_db_schema::{
aggregates::structs::{CommentAggregates, CommunityAggregates, PersonAggregates}, aggregates::structs::{CommentAggregates, CommunityAggregates, PersonAggregates, PostAggregates},
source::{ source::{
comment::Comment, comment::Comment,
comment_reply::CommentReply, comment_reply::CommentReply,
community::Community, community::Community,
person::Person, person::Person,
person_mention::PersonMention, person_comment_mention::PersonCommentMention,
person_post_mention::PersonPostMention,
post::Post, post::Post,
}, },
SubscribedType, SubscribedType,
@ -93,9 +94,9 @@ pub enum CommunitySortType {
#[cfg_attr(feature = "full", derive(TS, Queryable))] #[cfg_attr(feature = "full", derive(TS, Queryable))]
#[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// A person mention view. /// A person comment mention view.
pub struct PersonMentionView { pub struct PersonCommentMentionView {
pub person_mention: PersonMention, pub person_comment_mention: PersonCommentMention,
pub comment: Comment, pub comment: Comment,
pub creator: Person, pub creator: Person,
pub post: Post, pub post: Post,
@ -113,6 +114,29 @@ pub struct PersonMentionView {
pub my_vote: Option<i16>, pub my_vote: Option<i16>,
} }
#[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))]
/// A person post mention view.
pub struct PersonPostMentionView {
pub person_post_mention: PersonPostMention,
pub post: Post,
pub creator: Person,
pub community: Community,
pub recipient: Person,
pub counts: PostAggregates,
pub creator_banned_from_community: bool,
pub banned_from_community: bool,
pub creator_is_moderator: bool,
pub creator_is_admin: bool,
pub subscribed: SubscribedType,
pub saved: bool,
pub creator_blocked: bool,
pub my_vote: Option<i16>,
}
#[skip_serializing_none] #[skip_serializing_none]
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
#[cfg_attr(feature = "full", derive(TS, Queryable))] #[cfg_attr(feature = "full", derive(TS, Queryable))]

View file

@ -17,8 +17,9 @@ use lemmy_db_views::{
}; };
use lemmy_db_views_actor::{ use lemmy_db_views_actor::{
comment_reply_view::CommentReplyQuery, comment_reply_view::CommentReplyQuery,
person_mention_view::PersonMentionQuery, person_comment_mention_view::PersonCommentMentionQuery,
structs::{CommentReplyView, PersonMentionView}, person_post_mention_view::PersonPostMentionQuery,
structs::{CommentReplyView, PersonCommentMentionView, PersonPostMentionView},
}; };
use lemmy_utils::{ use lemmy_utils::{
cache_header::cache_1hour, cache_header::cache_1hour,
@ -360,37 +361,53 @@ async fn get_feed_front(
async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> LemmyResult<Channel> { async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> LemmyResult<Channel> {
let site_view = SiteView::read_local(&mut context.pool()).await?; let site_view = SiteView::read_local(&mut context.pool()).await?;
let local_user = local_user_view_from_jwt(jwt, context).await?; let local_user = local_user_view_from_jwt(jwt, context).await?;
let person_id = local_user.local_user.person_id; let my_person_id = Some(local_user.person.id);
let recipient_id = Some(local_user.local_user.person_id);
let show_bot_accounts = local_user.local_user.show_bot_accounts; let show_bot_accounts = local_user.local_user.show_bot_accounts;
let limit = Some(RSS_FETCH_LIMIT);
let sort = CommentSortType::New;
check_private_instance(&Some(local_user.clone()), &site_view.local_site)?; check_private_instance(&Some(local_user.clone()), &site_view.local_site)?;
let replies = CommentReplyQuery { let replies = CommentReplyQuery {
recipient_id: (Some(person_id)), recipient_id,
my_person_id: (Some(person_id)), my_person_id,
show_bot_accounts: (show_bot_accounts), show_bot_accounts,
sort: (Some(sort)), sort: Some(CommentSortType::New),
limit: (Some(RSS_FETCH_LIMIT)), limit,
..Default::default() ..Default::default()
} }
.list(&mut context.pool()) .list(&mut context.pool())
.await?; .await?;
let mentions = PersonMentionQuery { let comment_mentions = PersonCommentMentionQuery {
recipient_id: (Some(person_id)), recipient_id,
my_person_id: (Some(person_id)), my_person_id,
show_bot_accounts: (show_bot_accounts), show_bot_accounts,
sort: (Some(sort)), sort: Some(CommentSortType::New),
limit: (Some(RSS_FETCH_LIMIT)), limit,
..Default::default()
}
.list(&mut context.pool())
.await?;
let post_mentions = PersonPostMentionQuery {
recipient_id,
my_person_id,
show_bot_accounts,
sort: Some(PostSortType::New),
limit,
..Default::default() ..Default::default()
} }
.list(&mut context.pool()) .list(&mut context.pool())
.await?; .await?;
let protocol_and_hostname = context.settings().get_protocol_and_hostname(); let protocol_and_hostname = context.settings().get_protocol_and_hostname();
let items = create_reply_and_mention_items(replies, mentions, &protocol_and_hostname)?; let items = create_reply_and_mention_items(
replies,
comment_mentions,
post_mentions,
&protocol_and_hostname,
)?;
let mut channel = Channel { let mut channel = Channel {
namespaces: RSS_NAMESPACE.clone(), namespaces: RSS_NAMESPACE.clone(),
@ -410,7 +427,8 @@ async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> LemmyResult<Channe
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
fn create_reply_and_mention_items( fn create_reply_and_mention_items(
replies: Vec<CommentReplyView>, replies: Vec<CommentReplyView>,
mentions: Vec<PersonMentionView>, comment_mentions: Vec<PersonCommentMentionView>,
post_mentions: Vec<PersonPostMentionView>,
protocol_and_hostname: &str, protocol_and_hostname: &str,
) -> LemmyResult<Vec<Item>> { ) -> LemmyResult<Vec<Item>> {
let mut reply_items: Vec<Item> = replies let mut reply_items: Vec<Item> = replies
@ -427,7 +445,7 @@ fn create_reply_and_mention_items(
}) })
.collect::<LemmyResult<Vec<Item>>>()?; .collect::<LemmyResult<Vec<Item>>>()?;
let mut mention_items: Vec<Item> = mentions let mut comment_mention_items: Vec<Item> = comment_mentions
.iter() .iter()
.map(|m| { .map(|m| {
let mention_url = format!("{}/comment/{}", protocol_and_hostname, m.comment.id); let mention_url = format!("{}/comment/{}", protocol_and_hostname, m.comment.id);
@ -441,7 +459,24 @@ fn create_reply_and_mention_items(
}) })
.collect::<LemmyResult<Vec<Item>>>()?; .collect::<LemmyResult<Vec<Item>>>()?;
reply_items.append(&mut mention_items); reply_items.append(&mut comment_mention_items);
let mut post_mention_items: Vec<Item> = post_mentions
.iter()
.map(|m| {
let mention_url = format!("{}/post/{}", protocol_and_hostname, m.post.id);
build_item(
&m.creator.name,
&m.post.published,
&mention_url,
&m.post.body.clone().unwrap_or_default(),
protocol_and_hostname,
)
})
.collect::<LemmyResult<Vec<Item>>>()?;
reply_items.append(&mut post_mention_items);
Ok(reply_items) Ok(reply_items)
} }

View file

@ -106,7 +106,7 @@ pub enum LemmyErrorType {
CouldntHidePost, CouldntHidePost,
CouldntUpdateCommunity, CouldntUpdateCommunity,
CouldntUpdateReplies, CouldntUpdateReplies,
CouldntUpdatePersonMentions, CouldntUpdatePersonCommentMentions,
CouldntCreatePost, CouldntCreatePost,
CouldntCreatePrivateMessage, CouldntCreatePrivateMessage,
CouldntUpdatePrivate, CouldntUpdatePrivate,

View file

@ -0,0 +1,5 @@
-- Rename the person_mention table to person_comment_mention
ALTER TABLE person_comment_mention RENAME TO person_mention;
-- Drop the new table
DROP TABLE person_post_mention;

View file

@ -0,0 +1,12 @@
-- Rename the person_mention table to person_comment_mention
ALTER TABLE person_mention RENAME TO person_comment_mention;
-- Create the new post_mention table
CREATE TABLE person_post_mention (
id serial PRIMARY KEY,
recipient_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 boolean DEFAULT FALSE NOT NULL,
published timestamptz NOT NULL DEFAULT now(),
UNIQUE (recipient_id, post_id)
);