mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-22 04:11:17 +00:00
Finishing up post body mentions.
This commit is contained in:
parent
5b87cd8153
commit
6a7b1d417f
28 changed files with 1082 additions and 181 deletions
|
@ -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(),
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
use actix_web::web::{Data, Json, Query};
|
||||||
|
use lemmy_api_common::{
|
||||||
|
context::LemmyContext,
|
||||||
|
person::{GetPersonPostMentions, GetPersonPostMentionsResponse},
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_db_views_actor::person_post_mention_view::PersonPostMentionQuery;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
|
||||||
|
#[tracing::instrument(skip(context))]
|
||||||
|
pub async fn list_post_mentions(
|
||||||
|
data: Query<GetPersonPostMentions>,
|
||||||
|
context: Data<LemmyContext>,
|
||||||
|
local_user_view: LocalUserView,
|
||||||
|
) -> LemmyResult<Json<GetPersonPostMentionsResponse>> {
|
||||||
|
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 post_mentions = PersonPostMentionQuery {
|
||||||
|
recipient_id: person_id,
|
||||||
|
my_person_id: person_id,
|
||||||
|
sort,
|
||||||
|
unread_only,
|
||||||
|
show_bot_accounts,
|
||||||
|
page,
|
||||||
|
limit,
|
||||||
|
}
|
||||||
|
.list(&mut context.pool())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Json(GetPersonPostMentionsResponse { post_mentions }))
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}))
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
pub mod list_comment_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_comment_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;
|
||||||
|
|
|
@ -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, PersonCommentMentionView};
|
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))]
|
||||||
|
@ -14,16 +18,21 @@ pub async fn unread_count(
|
||||||
let replies =
|
let replies =
|
||||||
CommentReplyView::get_unread_count(&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 =
|
||||||
PersonCommentMentionView::get_unread_count(&mut context.pool(), &local_user_view.local_user)
|
PersonCommentMentionView::get_unread_count(&mut context.pool(), &local_user_view.local_user)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
let post_mentions =
|
||||||
|
PersonPostMentionView::get_unread_count(&mut context.pool(), &local_user_view.local_user)
|
||||||
|
.await?;
|
||||||
|
|
||||||
let private_messages =
|
let private_messages =
|
||||||
PrivateMessageView::get_unread_count(&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,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,10 +10,10 @@ use lemmy_api_common::{
|
||||||
check_community_user_action,
|
check_community_user_action,
|
||||||
check_local_vote_mode,
|
check_local_vote_mode,
|
||||||
mark_post_as_read,
|
mark_post_as_read,
|
||||||
VoteItem,
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
newtypes::PostOrCommentId,
|
||||||
source::{
|
source::{
|
||||||
community::Community,
|
community::Community,
|
||||||
local_site::LocalSite,
|
local_site::LocalSite,
|
||||||
|
@ -36,7 +36,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(),
|
||||||
|
|
|
@ -12,13 +12,14 @@ 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},
|
||||||
person::Person,
|
person::Person,
|
||||||
person_comment_mention::{PersonCommentMention, PersonCommentMentionInsertForm},
|
person_comment_mention::{PersonCommentMention, PersonCommentMentionInsertForm},
|
||||||
|
person_post_mention::{PersonPostMention, PersonPostMentionInsertForm},
|
||||||
},
|
},
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
|
@ -92,7 +93,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,
|
||||||
|
@ -103,15 +104,32 @@ pub async fn send_local_notifs(
|
||||||
|
|
||||||
// let person = my_local_user.person;
|
// let person = my_local_user.person;
|
||||||
// Read the comment view to get extra info
|
// Read the comment view to get extra info
|
||||||
let comment_view = CommentView::read(
|
|
||||||
&mut context.pool(),
|
let (comment_opt, post, community) = match post_or_comment_id {
|
||||||
comment_id,
|
PostOrCommentId::Post(post_id) => {
|
||||||
local_user_view.map(|view| &view.local_user),
|
let post_view = PostView::read(
|
||||||
)
|
&mut context.pool(),
|
||||||
.await?;
|
post_id,
|
||||||
let comment = comment_view.comment;
|
local_user_view.map(|view| &view.local_user),
|
||||||
let post = comment_view.post;
|
false,
|
||||||
let community = comment_view.community;
|
)
|
||||||
|
.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
|
||||||
|
@ -127,22 +145,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 person_comment_mention_form = PersonCommentMentionInsertForm {
|
// 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
|
||||||
PersonCommentMention::create(&mut context.pool(), &person_comment_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),
|
||||||
|
@ -155,99 +189,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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::{CommentReplyId, CommunityId, LanguageId, PersonCommentMentionId, PersonId},
|
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,
|
||||||
|
@ -12,6 +19,7 @@ use lemmy_db_views_actor::structs::{
|
||||||
CommentReplyView,
|
CommentReplyView,
|
||||||
CommunityModeratorView,
|
CommunityModeratorView,
|
||||||
PersonCommentMentionView,
|
PersonCommentMentionView,
|
||||||
|
PersonPostMentionView,
|
||||||
PersonView,
|
PersonView,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -325,6 +333,43 @@ pub struct PersonCommentMentionResponse {
|
||||||
pub person_comment_mention_view: PersonCommentMentionView,
|
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)]
|
||||||
#[cfg_attr(feature = "full", derive(TS))]
|
#[cfg_attr(feature = "full", derive(TS))]
|
||||||
#[cfg_attr(feature = "full", ts(export))]
|
#[cfg_attr(feature = "full", ts(export))]
|
||||||
|
@ -396,7 +441,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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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},
|
||||||
|
@ -301,23 +301,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;
|
||||||
|
@ -325,9 +319,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(())
|
||||||
|
|
|
@ -17,6 +17,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
impls::actor_language::default_post_language,
|
impls::actor_language::default_post_language,
|
||||||
|
newtypes::PostOrCommentId,
|
||||||
source::{
|
source::{
|
||||||
actor_language::CommunityLanguage,
|
actor_language::CommunityLanguage,
|
||||||
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm},
|
comment::{Comment, CommentInsertForm, CommentLike, CommentLikeForm},
|
||||||
|
@ -121,7 +122,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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -13,6 +13,7 @@ use lemmy_api_common::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
newtypes::PostOrCommentId,
|
||||||
source::{
|
source::{
|
||||||
actor_language::CommunityLanguage,
|
actor_language::CommunityLanguage,
|
||||||
comment::{Comment, CommentUpdateForm},
|
comment::{Comment, CommentUpdateForm},
|
||||||
|
@ -87,7 +88,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,
|
||||||
|
|
|
@ -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,
|
||||||
|
@ -18,6 +18,7 @@ use lemmy_api_common::{
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
impls::actor_language::default_post_language,
|
impls::actor_language::default_post_language,
|
||||||
|
newtypes::PostOrCommentId,
|
||||||
source::{
|
source::{
|
||||||
actor_language::CommunityLanguage,
|
actor_language::CommunityLanguage,
|
||||||
community::Community,
|
community::Community,
|
||||||
|
@ -34,6 +35,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,
|
||||||
|
@ -169,6 +171,18 @@ pub async fn create_post(
|
||||||
.await
|
.await
|
||||||
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
|
.with_lemmy_type(LemmyErrorType::CouldntLikePost)?;
|
||||||
|
|
||||||
|
// Scan the post body for user mentions, add those rows
|
||||||
|
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?;
|
||||||
|
|
||||||
mark_post_as_read(person_id, post_id, &mut context.pool()).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
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::{convert_published_time, create::send_webmention};
|
||||||
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::{EditPost, PostResponse},
|
post::{EditPost, PostResponse},
|
||||||
request::generate_post_link_metadata,
|
request::generate_post_link_metadata,
|
||||||
|
@ -15,6 +15,7 @@ use lemmy_api_common::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
|
newtypes::PostOrCommentId,
|
||||||
source::{
|
source::{
|
||||||
actor_language::CommunityLanguage,
|
actor_language::CommunityLanguage,
|
||||||
community::Community,
|
community::Community,
|
||||||
|
@ -28,6 +29,7 @@ use lemmy_db_views::structs::LocalUserView;
|
||||||
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.scheduled_publish_time,
|
orig_post.scheduled_publish_time,
|
||||||
|
|
|
@ -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},
|
||||||
|
@ -171,7 +171,15 @@ 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);
|
||||||
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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
@ -128,6 +131,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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
83
crates/db_schema/src/impls/person_post_mention.rs
Normal file
83
crates/db_schema/src/impls/person_post_mention.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))]
|
||||||
|
|
|
@ -752,7 +752,7 @@ diesel::table! {
|
||||||
recipient_id -> Int4,
|
recipient_id -> Int4,
|
||||||
post_id -> Int4,
|
post_id -> Int4,
|
||||||
read -> Bool,
|
read -> Bool,
|
||||||
published -> Timestamp,
|
published -> Timestamptz,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
39
crates/db_schema/src/source/person_post_mention.rs
Normal file
39
crates/db_schema/src/source/person_post_mention.rs
Normal 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>,
|
||||||
|
}
|
|
@ -10,8 +10,8 @@ pub mod community_person_ban_view;
|
||||||
pub mod community_view;
|
pub mod community_view;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod person_comment_mention_view;
|
pub mod person_comment_mention_view;
|
||||||
// #[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
// pub mod person_post_mention_view;
|
pub mod person_post_mention_view;
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub mod person_view;
|
pub mod person_view;
|
||||||
pub mod structs;
|
pub mod structs;
|
||||||
|
|
|
@ -216,7 +216,7 @@ fn queries<'a>() -> Queries<
|
||||||
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())
|
||||||
|
|
501
crates/db_views_actor/src/person_post_mention_view.rs
Normal file
501
crates/db_views_actor/src/person_post_mention_view.rs
Normal file
|
@ -0,0 +1,501 @@
|
||||||
|
use crate::structs::PersonPostMentionView;
|
||||||
|
use diesel::{
|
||||||
|
dsl::{exists, not, IntervalDsl},
|
||||||
|
pg::Pg,
|
||||||
|
result::Error,
|
||||||
|
sql_types,
|
||||||
|
BoolExpressionMethods,
|
||||||
|
BoxableExpression,
|
||||||
|
ExpressionMethods,
|
||||||
|
IntoSql,
|
||||||
|
JoinOnDsl,
|
||||||
|
NullableExpressionMethods,
|
||||||
|
QueryDsl,
|
||||||
|
};
|
||||||
|
use diesel_async::RunQueryDsl;
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
aliases,
|
||||||
|
newtypes::{PersonId, PersonPostMentionId},
|
||||||
|
schema::{
|
||||||
|
community,
|
||||||
|
community_follower,
|
||||||
|
community_moderator,
|
||||||
|
community_person_ban,
|
||||||
|
local_user,
|
||||||
|
person,
|
||||||
|
person_block,
|
||||||
|
person_post_mention,
|
||||||
|
post,
|
||||||
|
post_aggregates,
|
||||||
|
post_like,
|
||||||
|
post_saved,
|
||||||
|
},
|
||||||
|
source::local_user::LocalUser,
|
||||||
|
utils::{get_conn, limit_and_offset, now, DbConn, DbPool, ListFn, Queries, ReadFn},
|
||||||
|
PostSortType,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn queries<'a>() -> Queries<
|
||||||
|
impl ReadFn<'a, PersonPostMentionView, (PersonPostMentionId, Option<PersonId>)>,
|
||||||
|
impl ListFn<'a, PersonPostMentionView, PersonPostMentionQuery>,
|
||||||
|
> {
|
||||||
|
let is_creator_banned_from_community = exists(
|
||||||
|
community_person_ban::table.filter(
|
||||||
|
community::id
|
||||||
|
.eq(community_person_ban::community_id)
|
||||||
|
.and(community_person_ban::person_id.eq(post::creator_id)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let is_local_user_banned_from_community = |person_id| {
|
||||||
|
exists(
|
||||||
|
community_person_ban::table.filter(
|
||||||
|
community::id
|
||||||
|
.eq(community_person_ban::community_id)
|
||||||
|
.and(community_person_ban::person_id.eq(person_id)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_saved = |person_id| {
|
||||||
|
exists(
|
||||||
|
post_saved::table.filter(
|
||||||
|
post::id
|
||||||
|
.eq(post_saved::post_id)
|
||||||
|
.and(post_saved::person_id.eq(person_id)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_community_followed = |person_id| {
|
||||||
|
community_follower::table
|
||||||
|
.filter(
|
||||||
|
post::community_id
|
||||||
|
.eq(community_follower::community_id)
|
||||||
|
.and(community_follower::person_id.eq(person_id)),
|
||||||
|
)
|
||||||
|
.select(community_follower::pending.nullable())
|
||||||
|
.single_value()
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_creator_blocked = |person_id| {
|
||||||
|
exists(
|
||||||
|
person_block::table.filter(
|
||||||
|
post::creator_id
|
||||||
|
.eq(person_block::target_id)
|
||||||
|
.and(person_block::person_id.eq(person_id)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let score = |person_id| {
|
||||||
|
post_like::table
|
||||||
|
.filter(
|
||||||
|
post::id
|
||||||
|
.eq(post_like::post_id)
|
||||||
|
.and(post_like::person_id.eq(person_id)),
|
||||||
|
)
|
||||||
|
.select(post_like::score.nullable())
|
||||||
|
.single_value()
|
||||||
|
};
|
||||||
|
|
||||||
|
let creator_is_moderator = exists(
|
||||||
|
community_moderator::table.filter(
|
||||||
|
community::id
|
||||||
|
.eq(community_moderator::community_id)
|
||||||
|
.and(community_moderator::person_id.eq(post::creator_id)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let creator_is_admin = exists(
|
||||||
|
local_user::table.filter(
|
||||||
|
post::creator_id
|
||||||
|
.eq(local_user::person_id)
|
||||||
|
.and(local_user::admin.eq(true)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let all_joins = move |query: person_post_mention::BoxedQuery<'a, Pg>,
|
||||||
|
my_person_id: Option<PersonId>| {
|
||||||
|
let is_local_user_banned_from_community_selection: Box<
|
||||||
|
dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>,
|
||||||
|
> = if let Some(person_id) = my_person_id {
|
||||||
|
Box::new(is_local_user_banned_from_community(person_id))
|
||||||
|
} else {
|
||||||
|
Box::new(false.into_sql::<sql_types::Bool>())
|
||||||
|
};
|
||||||
|
let score_selection: Box<
|
||||||
|
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::SmallInt>>,
|
||||||
|
> = if let Some(person_id) = my_person_id {
|
||||||
|
Box::new(score(person_id))
|
||||||
|
} else {
|
||||||
|
Box::new(None::<i16>.into_sql::<sql_types::Nullable<sql_types::SmallInt>>())
|
||||||
|
};
|
||||||
|
|
||||||
|
let subscribed_type_selection: Box<
|
||||||
|
dyn BoxableExpression<_, Pg, SqlType = sql_types::Nullable<sql_types::Bool>>,
|
||||||
|
> = if let Some(person_id) = my_person_id {
|
||||||
|
Box::new(is_community_followed(person_id))
|
||||||
|
} else {
|
||||||
|
Box::new(None::<bool>.into_sql::<sql_types::Nullable<sql_types::Bool>>())
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_saved_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
|
||||||
|
if let Some(person_id) = my_person_id {
|
||||||
|
Box::new(is_saved(person_id))
|
||||||
|
} else {
|
||||||
|
Box::new(false.into_sql::<sql_types::Bool>())
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_creator_blocked_selection: Box<dyn BoxableExpression<_, Pg, SqlType = sql_types::Bool>> =
|
||||||
|
if let Some(person_id) = my_person_id {
|
||||||
|
Box::new(is_creator_blocked(person_id))
|
||||||
|
} else {
|
||||||
|
Box::new(false.into_sql::<sql_types::Bool>())
|
||||||
|
};
|
||||||
|
|
||||||
|
query
|
||||||
|
.inner_join(post::table)
|
||||||
|
.inner_join(person::table.on(post::creator_id.eq(person::id)))
|
||||||
|
.inner_join(community::table.on(post::community_id.eq(community::id)))
|
||||||
|
.inner_join(aliases::person1)
|
||||||
|
.inner_join(post_aggregates::table.on(post::id.eq(post_aggregates::post_id)))
|
||||||
|
.select((
|
||||||
|
person_post_mention::all_columns,
|
||||||
|
post::all_columns,
|
||||||
|
person::all_columns,
|
||||||
|
community::all_columns,
|
||||||
|
aliases::person1.fields(person::all_columns),
|
||||||
|
post_aggregates::all_columns,
|
||||||
|
is_creator_banned_from_community,
|
||||||
|
is_local_user_banned_from_community_selection,
|
||||||
|
creator_is_moderator,
|
||||||
|
creator_is_admin,
|
||||||
|
subscribed_type_selection,
|
||||||
|
is_saved_selection,
|
||||||
|
is_creator_blocked_selection,
|
||||||
|
score_selection,
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
let read = move |mut conn: DbConn<'a>,
|
||||||
|
(person_post_mention_id, my_person_id): (
|
||||||
|
PersonPostMentionId,
|
||||||
|
Option<PersonId>,
|
||||||
|
)| async move {
|
||||||
|
all_joins(
|
||||||
|
person_post_mention::table
|
||||||
|
.find(person_post_mention_id)
|
||||||
|
.into_boxed(),
|
||||||
|
my_person_id,
|
||||||
|
)
|
||||||
|
.first(&mut conn)
|
||||||
|
.await
|
||||||
|
};
|
||||||
|
|
||||||
|
let list = move |mut conn: DbConn<'a>, options: PersonPostMentionQuery| async move {
|
||||||
|
// These filters need to be kept in sync with the filters in
|
||||||
|
// PersonPostMentionView::get_unread_mentions()
|
||||||
|
let mut query = all_joins(
|
||||||
|
person_post_mention::table.into_boxed(),
|
||||||
|
options.my_person_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(recipient_id) = options.recipient_id {
|
||||||
|
query = query.filter(person_post_mention::recipient_id.eq(recipient_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.unread_only {
|
||||||
|
query = query.filter(person_post_mention::read.eq(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !options.show_bot_accounts {
|
||||||
|
query = query.filter(not(person::bot_account));
|
||||||
|
};
|
||||||
|
|
||||||
|
let time = |interval| post_aggregates::published.gt(now() - interval);
|
||||||
|
|
||||||
|
query = match options.sort.unwrap_or(PostSortType::New) {
|
||||||
|
PostSortType::Active => query.then_order_by(post_aggregates::hot_rank_active.desc()),
|
||||||
|
PostSortType::Hot => query.then_order_by(post_aggregates::hot_rank.desc()),
|
||||||
|
PostSortType::Controversial => query.then_order_by(post_aggregates::controversy_rank.desc()),
|
||||||
|
PostSortType::New => query.then_order_by(post_aggregates::published.desc()),
|
||||||
|
PostSortType::Old => query.then_order_by(post_aggregates::published.asc()),
|
||||||
|
PostSortType::TopAll => query.order_by(post_aggregates::score.desc()),
|
||||||
|
PostSortType::Scaled => query.then_order_by(post_aggregates::scaled_rank.desc()),
|
||||||
|
PostSortType::NewComments => query.then_order_by(post_aggregates::newest_comment_time.desc()),
|
||||||
|
PostSortType::MostComments => query.then_order_by(post_aggregates::comments.desc()),
|
||||||
|
PostSortType::TopYear => query
|
||||||
|
.then_order_by(post_aggregates::score.desc())
|
||||||
|
.filter(time(1.years())),
|
||||||
|
PostSortType::TopMonth => query
|
||||||
|
.then_order_by(post_aggregates::score.desc())
|
||||||
|
.filter(time(1.months())),
|
||||||
|
PostSortType::TopWeek => query
|
||||||
|
.then_order_by(post_aggregates::score.desc())
|
||||||
|
.filter(time(1.weeks())),
|
||||||
|
PostSortType::TopDay => query
|
||||||
|
.then_order_by(post_aggregates::score.desc())
|
||||||
|
.filter(time(1.days())),
|
||||||
|
PostSortType::TopHour => query
|
||||||
|
.then_order_by(post_aggregates::score.desc())
|
||||||
|
.filter(time(1.hours())),
|
||||||
|
PostSortType::TopSixHour => query
|
||||||
|
.then_order_by(post_aggregates::score.desc())
|
||||||
|
.filter(time(6.hours())),
|
||||||
|
PostSortType::TopTwelveHour => query
|
||||||
|
.then_order_by(post_aggregates::score.desc())
|
||||||
|
.filter(time(12.hours())),
|
||||||
|
PostSortType::TopThreeMonths => query
|
||||||
|
.then_order_by(post_aggregates::score.desc())
|
||||||
|
.filter(time(3.months())),
|
||||||
|
PostSortType::TopSixMonths => query
|
||||||
|
.then_order_by(post_aggregates::score.desc())
|
||||||
|
.filter(time(6.months())),
|
||||||
|
PostSortType::TopNineMonths => query
|
||||||
|
.then_order_by(post_aggregates::score.desc())
|
||||||
|
.filter(time(9.months())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't show mentions from blocked persons
|
||||||
|
if let Some(my_person_id) = options.my_person_id {
|
||||||
|
query = query.filter(not(is_creator_blocked(my_person_id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (limit, offset) = limit_and_offset(options.page, options.limit)?;
|
||||||
|
|
||||||
|
query
|
||||||
|
.limit(limit)
|
||||||
|
.offset(offset)
|
||||||
|
.load::<PersonPostMentionView>(&mut conn)
|
||||||
|
.await
|
||||||
|
};
|
||||||
|
|
||||||
|
Queries::new(read, list)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PersonPostMentionView {
|
||||||
|
pub async fn read(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
person_post_mention_id: PersonPostMentionId,
|
||||||
|
my_person_id: Option<PersonId>,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
queries()
|
||||||
|
.read(pool, (person_post_mention_id, my_person_id))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the number of unread mentions
|
||||||
|
pub async fn get_unread_count(
|
||||||
|
pool: &mut DbPool<'_>,
|
||||||
|
local_user: &LocalUser,
|
||||||
|
) -> Result<i64, Error> {
|
||||||
|
use diesel::dsl::count;
|
||||||
|
let conn = &mut get_conn(pool).await?;
|
||||||
|
|
||||||
|
let mut query = person_post_mention::table
|
||||||
|
.inner_join(post::table)
|
||||||
|
.left_join(
|
||||||
|
person_block::table.on(
|
||||||
|
post::creator_id
|
||||||
|
.eq(person_block::target_id)
|
||||||
|
.and(person_block::person_id.eq(local_user.person_id)),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.inner_join(person::table.on(post::creator_id.eq(person::id)))
|
||||||
|
.into_boxed();
|
||||||
|
|
||||||
|
// These filters need to be kept in sync with the filters in queries().list()
|
||||||
|
if !local_user.show_bot_accounts {
|
||||||
|
query = query.filter(not(person::bot_account));
|
||||||
|
}
|
||||||
|
|
||||||
|
query
|
||||||
|
// Don't count replies from blocked users
|
||||||
|
.filter(person_block::person_id.is_null())
|
||||||
|
.filter(person_post_mention::recipient_id.eq(local_user.person_id))
|
||||||
|
.filter(person_post_mention::read.eq(false))
|
||||||
|
.filter(post::deleted.eq(false))
|
||||||
|
.filter(post::removed.eq(false))
|
||||||
|
.select(count(person_post_mention::id))
|
||||||
|
.first::<i64>(conn)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct PersonPostMentionQuery {
|
||||||
|
pub my_person_id: Option<PersonId>,
|
||||||
|
pub recipient_id: Option<PersonId>,
|
||||||
|
pub sort: Option<PostSortType>,
|
||||||
|
pub unread_only: bool,
|
||||||
|
pub show_bot_accounts: bool,
|
||||||
|
pub page: Option<i64>,
|
||||||
|
pub limit: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PersonPostMentionQuery {
|
||||||
|
pub async fn list(self, pool: &mut DbPool<'_>) -> Result<Vec<PersonPostMentionView>, Error> {
|
||||||
|
queries().list(pool, self).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
|
||||||
|
use crate::{person_post_mention_view::PersonPostMentionQuery, structs::PersonPostMentionView};
|
||||||
|
use lemmy_db_schema::{
|
||||||
|
source::{
|
||||||
|
community::{Community, CommunityInsertForm},
|
||||||
|
instance::Instance,
|
||||||
|
local_user::{LocalUser, LocalUserInsertForm, LocalUserUpdateForm},
|
||||||
|
person::{Person, PersonInsertForm, PersonUpdateForm},
|
||||||
|
person_block::{PersonBlock, PersonBlockForm},
|
||||||
|
person_post_mention::{
|
||||||
|
PersonPostMention,
|
||||||
|
PersonPostMentionInsertForm,
|
||||||
|
PersonPostMentionUpdateForm,
|
||||||
|
},
|
||||||
|
post::{Post, PostInsertForm},
|
||||||
|
},
|
||||||
|
traits::{Blockable, Crud},
|
||||||
|
utils::build_db_pool_for_tests,
|
||||||
|
};
|
||||||
|
use lemmy_db_views::structs::LocalUserView;
|
||||||
|
use lemmy_utils::error::LemmyResult;
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
#[serial]
|
||||||
|
async fn test_crud() -> LemmyResult<()> {
|
||||||
|
let pool = &build_db_pool_for_tests().await;
|
||||||
|
let pool = &mut pool.into();
|
||||||
|
|
||||||
|
let inserted_instance = Instance::read_or_create(pool, "my_domain.tld".to_string()).await?;
|
||||||
|
|
||||||
|
let new_person = PersonInsertForm::test_form(inserted_instance.id, "terrylake");
|
||||||
|
|
||||||
|
let inserted_person = Person::create(pool, &new_person).await?;
|
||||||
|
|
||||||
|
let recipient_form = PersonInsertForm::test_form(inserted_instance.id, "terrylakes recipient");
|
||||||
|
|
||||||
|
let inserted_recipient = Person::create(pool, &recipient_form).await?;
|
||||||
|
let recipient_id = inserted_recipient.id;
|
||||||
|
|
||||||
|
let recipient_local_user =
|
||||||
|
LocalUser::create(pool, &LocalUserInsertForm::test_form(recipient_id), vec![]).await?;
|
||||||
|
|
||||||
|
let new_community = CommunityInsertForm::new(
|
||||||
|
inserted_instance.id,
|
||||||
|
"test community lake".to_string(),
|
||||||
|
"nada".to_owned(),
|
||||||
|
"pubkey".to_string(),
|
||||||
|
);
|
||||||
|
let inserted_community = Community::create(pool, &new_community).await?;
|
||||||
|
|
||||||
|
let new_post = PostInsertForm::new(
|
||||||
|
"A test post".into(),
|
||||||
|
inserted_person.id,
|
||||||
|
inserted_community.id,
|
||||||
|
);
|
||||||
|
let inserted_post = Post::create(pool, &new_post).await?;
|
||||||
|
|
||||||
|
let person_post_mention_form = PersonPostMentionInsertForm {
|
||||||
|
recipient_id: inserted_recipient.id,
|
||||||
|
post_id: inserted_post.id,
|
||||||
|
read: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let inserted_mention = PersonPostMention::create(pool, &person_post_mention_form).await?;
|
||||||
|
|
||||||
|
let expected_mention = PersonPostMention {
|
||||||
|
id: inserted_mention.id,
|
||||||
|
recipient_id: inserted_mention.recipient_id,
|
||||||
|
post_id: inserted_mention.post_id,
|
||||||
|
read: false,
|
||||||
|
published: inserted_mention.published,
|
||||||
|
};
|
||||||
|
|
||||||
|
let read_mention = PersonPostMention::read(pool, inserted_mention.id).await?;
|
||||||
|
|
||||||
|
let person_post_mention_update_form = PersonPostMentionUpdateForm { read: Some(false) };
|
||||||
|
let updated_mention =
|
||||||
|
PersonPostMention::update(pool, inserted_mention.id, &person_post_mention_update_form)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Test to make sure counts and blocks work correctly
|
||||||
|
let unread_mentions =
|
||||||
|
PersonPostMentionView::get_unread_count(pool, &recipient_local_user).await?;
|
||||||
|
|
||||||
|
let query = PersonPostMentionQuery {
|
||||||
|
recipient_id: Some(recipient_id),
|
||||||
|
my_person_id: Some(recipient_id),
|
||||||
|
sort: None,
|
||||||
|
unread_only: false,
|
||||||
|
show_bot_accounts: true,
|
||||||
|
page: None,
|
||||||
|
limit: None,
|
||||||
|
};
|
||||||
|
let mentions = query.clone().list(pool).await?;
|
||||||
|
assert_eq!(1, unread_mentions);
|
||||||
|
assert_eq!(1, mentions.len());
|
||||||
|
|
||||||
|
// Block the person, and make sure these counts are now empty
|
||||||
|
let block_form = PersonBlockForm {
|
||||||
|
person_id: recipient_id,
|
||||||
|
target_id: inserted_person.id,
|
||||||
|
};
|
||||||
|
PersonBlock::block(pool, &block_form).await?;
|
||||||
|
|
||||||
|
let unread_mentions_after_block =
|
||||||
|
PersonPostMentionView::get_unread_count(pool, &recipient_local_user).await?;
|
||||||
|
let mentions_after_block = query.clone().list(pool).await?;
|
||||||
|
assert_eq!(0, unread_mentions_after_block);
|
||||||
|
assert_eq!(0, mentions_after_block.len());
|
||||||
|
|
||||||
|
// Unblock user so we can reuse the same person
|
||||||
|
PersonBlock::unblock(pool, &block_form).await?;
|
||||||
|
|
||||||
|
// Turn Terry into a bot account
|
||||||
|
let person_update_form = PersonUpdateForm {
|
||||||
|
bot_account: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
Person::update(pool, inserted_person.id, &person_update_form).await?;
|
||||||
|
|
||||||
|
let recipient_local_user_update_form = LocalUserUpdateForm {
|
||||||
|
show_bot_accounts: Some(false),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
LocalUser::update(
|
||||||
|
pool,
|
||||||
|
recipient_local_user.id,
|
||||||
|
&recipient_local_user_update_form,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let recipient_local_user_view = LocalUserView::read(pool, recipient_local_user.id).await?;
|
||||||
|
|
||||||
|
let unread_mentions_after_hide_bots =
|
||||||
|
PersonPostMentionView::get_unread_count(pool, &recipient_local_user_view.local_user).await?;
|
||||||
|
|
||||||
|
let mut query_without_bots = query.clone();
|
||||||
|
query_without_bots.show_bot_accounts = false;
|
||||||
|
let replies_after_hide_bots = query_without_bots.list(pool).await?;
|
||||||
|
assert_eq!(0, unread_mentions_after_hide_bots);
|
||||||
|
assert_eq!(0, replies_after_hide_bots.len());
|
||||||
|
|
||||||
|
Post::delete(pool, inserted_post.id).await?;
|
||||||
|
Post::delete(pool, inserted_post.id).await?;
|
||||||
|
Community::delete(pool, inserted_community.id).await?;
|
||||||
|
Person::delete(pool, inserted_person.id).await?;
|
||||||
|
Person::delete(pool, inserted_recipient.id).await?;
|
||||||
|
Instance::delete(pool, inserted_instance.id).await?;
|
||||||
|
|
||||||
|
assert_eq!(expected_mention, read_mention);
|
||||||
|
assert_eq!(expected_mention, inserted_mention);
|
||||||
|
assert_eq!(expected_mention, updated_mention);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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_comment_mention::PersonCommentMention,
|
person_comment_mention::PersonCommentMention,
|
||||||
|
person_post_mention::PersonPostMention,
|
||||||
post::Post,
|
post::Post,
|
||||||
},
|
},
|
||||||
SubscribedType,
|
SubscribedType,
|
||||||
|
@ -93,7 +94,7 @@ 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 PersonCommentMentionView {
|
pub struct PersonCommentMentionView {
|
||||||
pub person_comment_mention: PersonCommentMention,
|
pub person_comment_mention: PersonCommentMention,
|
||||||
pub comment: Comment,
|
pub comment: Comment,
|
||||||
|
@ -112,28 +113,28 @@ pub struct PersonCommentMentionView {
|
||||||
pub my_vote: Option<i16>,
|
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))]
|
||||||
// #[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 post mention view.
|
||||||
// pub struct PersonPostMentionView {
|
pub struct PersonPostMentionView {
|
||||||
// pub person_post_mention: PersonPostMention,
|
pub person_post_mention: PersonPostMention,
|
||||||
// pub creator: Person,
|
pub post: Post,
|
||||||
// pub post: Post,
|
pub creator: Person,
|
||||||
// pub community: Community,
|
pub community: Community,
|
||||||
// pub recipient: Person,
|
pub recipient: Person,
|
||||||
// pub counts: CommentAggregates,
|
pub counts: PostAggregates,
|
||||||
// pub creator_banned_from_community: bool,
|
pub creator_banned_from_community: bool,
|
||||||
// pub banned_from_community: bool,
|
pub banned_from_community: bool,
|
||||||
// pub creator_is_moderator: bool,
|
pub creator_is_moderator: bool,
|
||||||
// pub creator_is_admin: bool,
|
pub creator_is_admin: bool,
|
||||||
// pub subscribed: SubscribedType,
|
pub subscribed: SubscribedType,
|
||||||
// pub saved: bool,
|
pub saved: bool,
|
||||||
// pub creator_blocked: bool,
|
pub creator_blocked: bool,
|
||||||
// pub my_vote: Option<i16>,
|
pub my_vote: Option<i16>,
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
|
||||||
|
|
|
@ -18,7 +18,8 @@ use lemmy_db_views::{
|
||||||
use lemmy_db_views_actor::{
|
use lemmy_db_views_actor::{
|
||||||
comment_reply_view::CommentReplyQuery,
|
comment_reply_view::CommentReplyQuery,
|
||||||
person_comment_mention_view::PersonCommentMentionQuery,
|
person_comment_mention_view::PersonCommentMentionQuery,
|
||||||
structs::{CommentReplyView, PersonCommentMentionView},
|
person_post_mention_view::PersonPostMentionQuery,
|
||||||
|
structs::{CommentReplyView, PersonCommentMentionView, PersonPostMentionView},
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
cache_header::cache_1hour,
|
cache_header::cache_1hour,
|
||||||
|
@ -377,37 +378,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 comment_mentions = PersonCommentMentionQuery {
|
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, comment_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(),
|
||||||
|
@ -428,6 +445,7 @@ async fn get_feed_inbox(context: &LemmyContext, jwt: &str) -> LemmyResult<Channe
|
||||||
fn create_reply_and_mention_items(
|
fn create_reply_and_mention_items(
|
||||||
replies: Vec<CommentReplyView>,
|
replies: Vec<CommentReplyView>,
|
||||||
comment_mentions: Vec<PersonCommentMentionView>,
|
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
|
||||||
|
@ -459,6 +477,23 @@ fn create_reply_and_mention_items(
|
||||||
.collect::<LemmyResult<Vec<Item>>>()?;
|
.collect::<LemmyResult<Vec<Item>>>()?;
|
||||||
|
|
||||||
reply_items.append(&mut comment_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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,6 @@ CREATE TABLE person_post_mention (
|
||||||
recipient_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
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,
|
post_id int REFERENCES post ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||||
read boolean DEFAULT FALSE NOT NULL,
|
read boolean DEFAULT FALSE NOT NULL,
|
||||||
published timestamp NOT NULL DEFAULT now(),
|
published timestamptz NOT NULL DEFAULT now(),
|
||||||
UNIQUE (recipient_id, post_id)
|
UNIQUE (recipient_id, post_id)
|
||||||
);
|
);
|
||||||
|
|
|
@ -35,9 +35,11 @@ use lemmy_api::{
|
||||||
logout::logout,
|
logout::logout,
|
||||||
notifications::{
|
notifications::{
|
||||||
list_comment_mentions::list_comment_mentions,
|
list_comment_mentions::list_comment_mentions,
|
||||||
|
list_post_mentions::list_post_mentions,
|
||||||
list_replies::list_replies,
|
list_replies::list_replies,
|
||||||
mark_all_read::mark_all_notifications_read,
|
mark_all_read::mark_all_notifications_read,
|
||||||
mark_comment_mention_read::mark_comment_mention_as_read,
|
mark_comment_mention_read::mark_comment_mention_as_read,
|
||||||
|
mark_post_mention_read::mark_post_mention_as_read,
|
||||||
mark_reply_read::mark_reply_as_read,
|
mark_reply_read::mark_reply_as_read,
|
||||||
unread_count::unread_count,
|
unread_count::unread_count,
|
||||||
},
|
},
|
||||||
|
@ -334,6 +336,11 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
|
||||||
"/comment_mention/mark_as_read",
|
"/comment_mention/mark_as_read",
|
||||||
web::post().to(mark_comment_mention_as_read),
|
web::post().to(mark_comment_mention_as_read),
|
||||||
)
|
)
|
||||||
|
.route("/post_mention", web::get().to(list_post_mentions))
|
||||||
|
.route(
|
||||||
|
"/post_mention/mark_as_read",
|
||||||
|
web::post().to(mark_post_mention_as_read),
|
||||||
|
)
|
||||||
.route("/replies", web::get().to(list_replies))
|
.route("/replies", web::get().to(list_replies))
|
||||||
// Admin action. I don't like that it's in /user
|
// Admin action. I don't like that it's in /user
|
||||||
.route("/ban", web::post().to(ban_from_site))
|
.route("/ban", web::post().to(ban_from_site))
|
||||||
|
|
Loading…
Reference in a new issue