Ignore old federated post edits (ref #4529) (#4586)

* Ignore old federated post edits (ref #4529)

* use filter on insert

* coalesce(updated, published)

* avoid comment conflict clause

---------

Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
This commit is contained in:
Nutomic 2024-04-10 16:03:51 +02:00 committed by GitHub
parent 99d585b7be
commit 0203b62a6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 136 additions and 42 deletions

View file

@ -29,6 +29,7 @@ use lemmy_db_schema::{
post::Post, post::Post,
}, },
traits::Crud, traits::Crud,
utils::naive_now,
}; };
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyError, LemmyErrorType}, error::{LemmyError, LemmyErrorType},
@ -141,6 +142,7 @@ impl Object for ApubComment {
check_apub_id_valid_with_strictness(note.id.inner(), community.local, context).await?; check_apub_id_valid_with_strictness(note.id.inner(), community.local, context).await?;
verify_is_remote_object(note.id.inner(), context.settings())?; verify_is_remote_object(note.id.inner(), context.settings())?;
verify_person_in_community(&note.attributed_to, &community, context).await?; verify_person_in_community(&note.attributed_to, &community, context).await?;
let (post, _) = note.get_parents(context).await?; let (post, _) = note.get_parents(context).await?;
let creator = note.attributed_to.dereference(context).await?; let creator = note.attributed_to.dereference(context).await?;
let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &creator, community.id) let is_mod_or_admin = is_mod_or_admin(&mut context.pool(), &creator, community.id)
@ -184,7 +186,14 @@ impl Object for ApubComment {
language_id, language_id,
}; };
let parent_comment_path = parent_comment.map(|t| t.0.path); let parent_comment_path = parent_comment.map(|t| t.0.path);
let comment = Comment::create(&mut context.pool(), &form, parent_comment_path.as_ref()).await?; let timestamp: DateTime<Utc> = note.updated.or(note.published).unwrap_or_else(naive_now);
let comment = Comment::insert_apub(
&mut context.pool(),
Some(timestamp),
&form,
parent_comment_path.as_ref(),
)
.await?;
Ok(comment.into()) Ok(comment.into())
} }
} }

View file

@ -175,7 +175,8 @@ impl Object for ApubCommunity {
let languages = let languages =
LanguageTag::to_language_id_multiple(group.language, &mut context.pool()).await?; LanguageTag::to_language_id_multiple(group.language, &mut context.pool()).await?;
let community = Community::create(&mut context.pool(), &form).await?; let timestamp = group.updated.or(group.published).unwrap_or_else(naive_now);
let community = Community::insert_apub(&mut context.pool(), timestamp, &form).await?;
CommunityLanguage::update(&mut context.pool(), languages, community.id).await?; CommunityLanguage::update(&mut context.pool(), languages, community.id).await?;
let community: ApubCommunity = community.into(); let community: ApubCommunity = community.into();

View file

@ -41,6 +41,7 @@ use lemmy_db_schema::{
post::{Post, PostInsertForm, PostUpdateForm}, post::{Post, PostInsertForm, PostUpdateForm},
}, },
traits::Crud, traits::Crud,
utils::naive_now,
}; };
use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_db_views_actor::structs::CommunityModeratorView;
use lemmy_utils::{ use lemmy_utils::{
@ -270,7 +271,8 @@ impl Object for ApubPost {
.build() .build()
}; };
let post = Post::create(&mut context.pool(), &form).await?; let timestamp = page.updated.or(page.published).unwrap_or_else(naive_now);
let post = Post::insert_apub(&mut context.pool(), timestamp, &form).await?;
generate_post_link_metadata( generate_post_link_metadata(
post.clone(), post.clone(),

View file

@ -23,6 +23,7 @@ use lemmy_db_schema::{
private_message::{PrivateMessage, PrivateMessageInsertForm}, private_message::{PrivateMessage, PrivateMessageInsertForm},
}, },
traits::Crud, traits::Crud,
utils::naive_now,
}; };
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyError, LemmyErrorType}, error::{LemmyError, LemmyErrorType},
@ -142,7 +143,8 @@ impl Object for ApubPrivateMessage {
ap_id: Some(note.id.into()), ap_id: Some(note.id.into()),
local: Some(false), local: Some(false),
}; };
let pm = PrivateMessage::create(&mut context.pool(), &form).await?; let timestamp = note.updated.or(note.published).unwrap_or_else(naive_now);
let pm = PrivateMessage::insert_apub(&mut context.pool(), timestamp, &form).await?;
Ok(pm.into()) Ok(pm.into())
} }
} }

View file

@ -1,4 +1,5 @@
use crate::{ use crate::{
diesel::DecoratableTarget,
newtypes::{CommentId, DbUrl, PersonId}, newtypes::{CommentId, DbUrl, PersonId},
schema::comment, schema::comment,
source::comment::{ source::comment::{
@ -11,8 +12,9 @@ use crate::{
CommentUpdateForm, CommentUpdateForm,
}, },
traits::{Crud, Likeable, Saveable}, traits::{Crud, Likeable, Saveable},
utils::{get_conn, naive_now, DbPool, DELETED_REPLACEMENT_TEXT}, utils::{functions::coalesce, get_conn, naive_now, DbPool, DELETED_REPLACEMENT_TEXT},
}; };
use chrono::{DateTime, Utc};
use diesel::{ use diesel::{
dsl::{insert_into, sql_query}, dsl::{insert_into, sql_query},
result::Error, result::Error,
@ -59,6 +61,15 @@ impl Comment {
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
comment_form: &CommentInsertForm, comment_form: &CommentInsertForm,
parent_path: Option<&Ltree>, parent_path: Option<&Ltree>,
) -> Result<Comment, Error> {
Self::insert_apub(pool, None, comment_form, parent_path).await
}
pub async fn insert_apub(
pool: &mut DbPool<'_>,
timestamp: Option<DateTime<Utc>>,
comment_form: &CommentInsertForm,
parent_path: Option<&Ltree>,
) -> Result<Comment, Error> { ) -> Result<Comment, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
@ -67,13 +78,21 @@ impl Comment {
.run(|conn| { .run(|conn| {
Box::pin(async move { Box::pin(async move {
// Insert, to get the id // Insert, to get the id
let inserted_comment = insert_into(comment::table) let inserted_comment = if let Some(timestamp) = timestamp {
.values(comment_form) insert_into(comment::table)
.on_conflict(comment::ap_id) .values(comment_form)
.do_update() .on_conflict(comment::ap_id)
.set(comment_form) .filter_target(coalesce(comment::updated, comment::published).lt(timestamp))
.get_result::<Self>(conn) .do_update()
.await?; .set(comment_form)
.get_result::<Self>(conn)
.await?
} else {
insert_into(comment::table)
.values(comment_form)
.get_result::<Self>(conn)
.await?
};
let comment_id = inserted_comment.id; let comment_id = inserted_comment.id;
@ -129,6 +148,7 @@ where ca.comment_id = c.id"
}) })
.await .await
} }
pub async fn read_from_apub_id( pub async fn read_from_apub_id(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
object_id: Url, object_id: Url,

View file

@ -1,4 +1,5 @@
use crate::{ use crate::{
diesel::DecoratableTarget,
newtypes::{CommunityId, DbUrl, PersonId}, newtypes::{CommunityId, DbUrl, PersonId},
schema::{community, community_follower, instance}, schema::{community, community_follower, instance},
source::{ source::{
@ -17,9 +18,14 @@ use crate::{
post::Post, post::Post,
}, },
traits::{ApubActor, Bannable, Crud, Followable, Joinable}, traits::{ApubActor, Bannable, Crud, Followable, Joinable},
utils::{functions::lower, get_conn, DbPool}, utils::{
functions::{coalesce, lower},
get_conn,
DbPool,
},
SubscribedType, SubscribedType,
}; };
use chrono::{DateTime, Utc};
use diesel::{ use diesel::{
deserialize, deserialize,
dsl, dsl,
@ -44,25 +50,15 @@ impl Crud for Community {
type IdType = CommunityId; type IdType = CommunityId;
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let is_new_community = match &form.actor_id {
Some(id) => Community::read_from_apub_id(pool, id).await?.is_none(),
None => true,
};
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
// Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible
let community_ = insert_into(community::table) let community_ = insert_into(community::table)
.values(form) .values(form)
.on_conflict(community::actor_id)
.do_update()
.set(form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await?; .await?;
// Initialize languages for new community // Initialize languages for new community
if is_new_community { CommunityLanguage::update(pool, vec![], community_.id).await?;
CommunityLanguage::update(pool, vec![], community_.id).await?;
}
Ok(community_) Ok(community_)
} }
@ -116,6 +112,35 @@ pub enum CollectionType {
} }
impl Community { impl Community {
pub async fn insert_apub(
pool: &mut DbPool<'_>,
timestamp: DateTime<Utc>,
form: &CommunityInsertForm,
) -> Result<Self, Error> {
let is_new_community = match &form.actor_id {
Some(id) => Community::read_from_apub_id(pool, id).await?.is_none(),
None => true,
};
let conn = &mut get_conn(pool).await?;
// Can't do separate insert/update commands because InsertForm/UpdateForm aren't convertible
let community_ = insert_into(community::table)
.values(form)
.on_conflict(community::actor_id)
.filter_target(coalesce(community::updated, community::published).lt(timestamp))
.do_update()
.set(form)
.get_result::<Self>(conn)
.await?;
// Initialize languages for new community
if is_new_community {
CommunityLanguage::update(pool, vec![], community_.id).await?;
}
Ok(community_)
}
/// Get the community which has a given moderators or featured url, also return the collection type /// Get the community which has a given moderators or featured url, also return the collection type
pub async fn get_by_collection_url( pub async fn get_by_collection_url(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,

View file

@ -27,8 +27,15 @@ use crate::{
}, },
}; };
use ::url::Url; use ::url::Url;
use chrono::Utc; use chrono::{DateTime, Utc};
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl, TextExpressionMethods}; use diesel::{
dsl::insert_into,
result::Error,
DecoratableTarget,
ExpressionMethods,
QueryDsl,
TextExpressionMethods,
};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use std::collections::HashSet; use std::collections::HashSet;
@ -42,9 +49,6 @@ impl Crud for Post {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(post::table) insert_into(post::table)
.values(form) .values(form)
.on_conflict(post::ap_id)
.do_update()
.set(form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
@ -63,6 +67,22 @@ impl Crud for Post {
} }
impl Post { impl Post {
pub async fn insert_apub(
pool: &mut DbPool<'_>,
timestamp: DateTime<Utc>,
form: &PostInsertForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(post::table)
.values(form)
.on_conflict(post::ap_id)
.filter_target(coalesce(post::updated, post::published).lt(timestamp))
.do_update()
.set(form)
.get_result::<Self>(conn)
.await
}
pub async fn list_for_community( pub async fn list_for_community(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
the_community_id: CommunityId, the_community_id: CommunityId,

View file

@ -1,10 +1,12 @@
use crate::{ use crate::{
diesel::DecoratableTarget,
newtypes::{DbUrl, PersonId, PrivateMessageId}, newtypes::{DbUrl, PersonId, PrivateMessageId},
schema::private_message::dsl::{ap_id, private_message, read, recipient_id}, schema::private_message,
source::private_message::{PrivateMessage, PrivateMessageInsertForm, PrivateMessageUpdateForm}, source::private_message::{PrivateMessage, PrivateMessageInsertForm, PrivateMessageUpdateForm},
traits::Crud, traits::Crud,
utils::{get_conn, DbPool}, utils::{functions::coalesce, get_conn, DbPool},
}; };
use chrono::{DateTime, Utc};
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_utils::error::LemmyError; use lemmy_utils::error::LemmyError;
@ -18,11 +20,8 @@ impl Crud for PrivateMessage {
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(private_message) insert_into(private_message::table)
.values(form) .values(form)
.on_conflict(ap_id)
.do_update()
.set(form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
@ -33,7 +32,7 @@ impl Crud for PrivateMessage {
form: &Self::UpdateForm, form: &Self::UpdateForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::update(private_message.find(private_message_id)) diesel::update(private_message::table.find(private_message_id))
.set(form) .set(form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
@ -41,17 +40,33 @@ impl Crud for PrivateMessage {
} }
impl PrivateMessage { impl PrivateMessage {
pub async fn insert_apub(
pool: &mut DbPool<'_>,
timestamp: DateTime<Utc>,
form: &PrivateMessageInsertForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
insert_into(private_message::table)
.values(form)
.on_conflict(private_message::ap_id)
.filter_target(coalesce(private_message::updated, private_message::published).lt(timestamp))
.do_update()
.set(form)
.get_result::<Self>(conn)
.await
}
pub async fn mark_all_as_read( pub async fn mark_all_as_read(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
for_recipient_id: PersonId, for_recipient_id: PersonId,
) -> Result<Vec<PrivateMessage>, Error> { ) -> Result<Vec<PrivateMessage>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::update( diesel::update(
private_message private_message::table
.filter(recipient_id.eq(for_recipient_id)) .filter(private_message::recipient_id.eq(for_recipient_id))
.filter(read.eq(false)), .filter(private_message::read.eq(false)),
) )
.set(read.eq(true)) .set(private_message::read.eq(true))
.get_results::<Self>(conn) .get_results::<Self>(conn)
.await .await
} }
@ -63,8 +78,8 @@ impl PrivateMessage {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let object_id: DbUrl = object_id.into(); let object_id: DbUrl = object_id.into();
Ok( Ok(
private_message private_message::table
.filter(ap_id.eq(object_id)) .filter(private_message::ap_id.eq(object_id))
.first::<PrivateMessage>(conn) .first::<PrivateMessage>(conn)
.await .await
.ok() .ok()