From 0203b62a6de969153427c4ac4c4a3efbe2621010 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Wed, 10 Apr 2024 16:03:51 +0200 Subject: [PATCH] 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> --- crates/apub/src/objects/comment.rs | 11 ++++- crates/apub/src/objects/community.rs | 3 +- crates/apub/src/objects/post.rs | 4 +- crates/apub/src/objects/private_message.rs | 4 +- crates/db_schema/src/impls/comment.rs | 36 +++++++++++--- crates/db_schema/src/impls/community.rs | 49 ++++++++++++++----- crates/db_schema/src/impls/post.rs | 30 ++++++++++-- crates/db_schema/src/impls/private_message.rs | 41 +++++++++++----- 8 files changed, 136 insertions(+), 42 deletions(-) diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs index ba7cc914f..1acef5cbf 100644 --- a/crates/apub/src/objects/comment.rs +++ b/crates/apub/src/objects/comment.rs @@ -29,6 +29,7 @@ use lemmy_db_schema::{ post::Post, }, traits::Crud, + utils::naive_now, }; use lemmy_utils::{ error::{LemmyError, LemmyErrorType}, @@ -141,6 +142,7 @@ impl Object for ApubComment { check_apub_id_valid_with_strictness(note.id.inner(), community.local, context).await?; verify_is_remote_object(note.id.inner(), context.settings())?; verify_person_in_community(¬e.attributed_to, &community, context).await?; + let (post, _) = note.get_parents(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) @@ -184,7 +186,14 @@ impl Object for ApubComment { language_id, }; 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 = 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()) } } diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 7630d80b2..83cdc3a49 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -175,7 +175,8 @@ impl Object for ApubCommunity { let languages = 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?; let community: ApubCommunity = community.into(); diff --git a/crates/apub/src/objects/post.rs b/crates/apub/src/objects/post.rs index 0ddc6d17b..a53acee28 100644 --- a/crates/apub/src/objects/post.rs +++ b/crates/apub/src/objects/post.rs @@ -41,6 +41,7 @@ use lemmy_db_schema::{ post::{Post, PostInsertForm, PostUpdateForm}, }, traits::Crud, + utils::naive_now, }; use lemmy_db_views_actor::structs::CommunityModeratorView; use lemmy_utils::{ @@ -270,7 +271,8 @@ impl Object for ApubPost { .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( post.clone(), diff --git a/crates/apub/src/objects/private_message.rs b/crates/apub/src/objects/private_message.rs index 647510802..c32aa6458 100644 --- a/crates/apub/src/objects/private_message.rs +++ b/crates/apub/src/objects/private_message.rs @@ -23,6 +23,7 @@ use lemmy_db_schema::{ private_message::{PrivateMessage, PrivateMessageInsertForm}, }, traits::Crud, + utils::naive_now, }; use lemmy_utils::{ error::{LemmyError, LemmyErrorType}, @@ -142,7 +143,8 @@ impl Object for ApubPrivateMessage { ap_id: Some(note.id.into()), 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()) } } diff --git a/crates/db_schema/src/impls/comment.rs b/crates/db_schema/src/impls/comment.rs index b27d44591..c0dad0ad3 100644 --- a/crates/db_schema/src/impls/comment.rs +++ b/crates/db_schema/src/impls/comment.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::DecoratableTarget, newtypes::{CommentId, DbUrl, PersonId}, schema::comment, source::comment::{ @@ -11,8 +12,9 @@ use crate::{ CommentUpdateForm, }, 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::{ dsl::{insert_into, sql_query}, result::Error, @@ -59,6 +61,15 @@ impl Comment { pool: &mut DbPool<'_>, comment_form: &CommentInsertForm, parent_path: Option<&Ltree>, + ) -> Result { + Self::insert_apub(pool, None, comment_form, parent_path).await + } + + pub async fn insert_apub( + pool: &mut DbPool<'_>, + timestamp: Option>, + comment_form: &CommentInsertForm, + parent_path: Option<&Ltree>, ) -> Result { let conn = &mut get_conn(pool).await?; @@ -67,13 +78,21 @@ impl Comment { .run(|conn| { Box::pin(async move { // Insert, to get the id - let inserted_comment = insert_into(comment::table) - .values(comment_form) - .on_conflict(comment::ap_id) - .do_update() - .set(comment_form) - .get_result::(conn) - .await?; + let inserted_comment = if let Some(timestamp) = timestamp { + insert_into(comment::table) + .values(comment_form) + .on_conflict(comment::ap_id) + .filter_target(coalesce(comment::updated, comment::published).lt(timestamp)) + .do_update() + .set(comment_form) + .get_result::(conn) + .await? + } else { + insert_into(comment::table) + .values(comment_form) + .get_result::(conn) + .await? + }; let comment_id = inserted_comment.id; @@ -129,6 +148,7 @@ where ca.comment_id = c.id" }) .await } + pub async fn read_from_apub_id( pool: &mut DbPool<'_>, object_id: Url, diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index af3bf9bcf..4dc45c09b 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -1,4 +1,5 @@ use crate::{ + diesel::DecoratableTarget, newtypes::{CommunityId, DbUrl, PersonId}, schema::{community, community_follower, instance}, source::{ @@ -17,9 +18,14 @@ use crate::{ post::Post, }, traits::{ApubActor, Bannable, Crud, Followable, Joinable}, - utils::{functions::lower, get_conn, DbPool}, + utils::{ + functions::{coalesce, lower}, + get_conn, + DbPool, + }, SubscribedType, }; +use chrono::{DateTime, Utc}; use diesel::{ deserialize, dsl, @@ -44,25 +50,15 @@ impl Crud for Community { type IdType = CommunityId; async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result { - 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) - .do_update() - .set(form) .get_result::(conn) .await?; // 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_) } @@ -116,6 +112,35 @@ pub enum CollectionType { } impl Community { + pub async fn insert_apub( + pool: &mut DbPool<'_>, + timestamp: DateTime, + form: &CommunityInsertForm, + ) -> Result { + 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::(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 pub async fn get_by_collection_url( pool: &mut DbPool<'_>, diff --git a/crates/db_schema/src/impls/post.rs b/crates/db_schema/src/impls/post.rs index 8db871ad5..596eb62bf 100644 --- a/crates/db_schema/src/impls/post.rs +++ b/crates/db_schema/src/impls/post.rs @@ -27,8 +27,15 @@ use crate::{ }, }; use ::url::Url; -use chrono::Utc; -use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl, TextExpressionMethods}; +use chrono::{DateTime, Utc}; +use diesel::{ + dsl::insert_into, + result::Error, + DecoratableTarget, + ExpressionMethods, + QueryDsl, + TextExpressionMethods, +}; use diesel_async::RunQueryDsl; use std::collections::HashSet; @@ -42,9 +49,6 @@ impl Crud for Post { let conn = &mut get_conn(pool).await?; insert_into(post::table) .values(form) - .on_conflict(post::ap_id) - .do_update() - .set(form) .get_result::(conn) .await } @@ -63,6 +67,22 @@ impl Crud for Post { } impl Post { + pub async fn insert_apub( + pool: &mut DbPool<'_>, + timestamp: DateTime, + form: &PostInsertForm, + ) -> Result { + 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::(conn) + .await + } + pub async fn list_for_community( pool: &mut DbPool<'_>, the_community_id: CommunityId, diff --git a/crates/db_schema/src/impls/private_message.rs b/crates/db_schema/src/impls/private_message.rs index ee0009189..8370d5d08 100644 --- a/crates/db_schema/src/impls/private_message.rs +++ b/crates/db_schema/src/impls/private_message.rs @@ -1,10 +1,12 @@ use crate::{ + diesel::DecoratableTarget, newtypes::{DbUrl, PersonId, PrivateMessageId}, - schema::private_message::dsl::{ap_id, private_message, read, recipient_id}, + schema::private_message, source::private_message::{PrivateMessage, PrivateMessageInsertForm, PrivateMessageUpdateForm}, 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_async::RunQueryDsl; use lemmy_utils::error::LemmyError; @@ -18,11 +20,8 @@ impl Crud for PrivateMessage { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result { let conn = &mut get_conn(pool).await?; - insert_into(private_message) + insert_into(private_message::table) .values(form) - .on_conflict(ap_id) - .do_update() - .set(form) .get_result::(conn) .await } @@ -33,7 +32,7 @@ impl Crud for PrivateMessage { form: &Self::UpdateForm, ) -> Result { 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) .get_result::(conn) .await @@ -41,17 +40,33 @@ impl Crud for PrivateMessage { } impl PrivateMessage { + pub async fn insert_apub( + pool: &mut DbPool<'_>, + timestamp: DateTime, + form: &PrivateMessageInsertForm, + ) -> Result { + 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::(conn) + .await + } + pub async fn mark_all_as_read( pool: &mut DbPool<'_>, for_recipient_id: PersonId, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; diesel::update( - private_message - .filter(recipient_id.eq(for_recipient_id)) - .filter(read.eq(false)), + private_message::table + .filter(private_message::recipient_id.eq(for_recipient_id)) + .filter(private_message::read.eq(false)), ) - .set(read.eq(true)) + .set(private_message::read.eq(true)) .get_results::(conn) .await } @@ -63,8 +78,8 @@ impl PrivateMessage { let conn = &mut get_conn(pool).await?; let object_id: DbUrl = object_id.into(); Ok( - private_message - .filter(ap_id.eq(object_id)) + private_message::table + .filter(private_message::ap_id.eq(object_id)) .first::(conn) .await .ok()