From 7316dd281a53501010b6d647b09da5095b3a6c31 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Mon, 26 Feb 2024 15:45:23 +0100 Subject: [PATCH] Correctly synchronize collection of community featured posts (fixes #3867) (#4475) --- .../src/collections/community_featured.rs | 36 ++++++++++-------- crates/apub/src/objects/community.rs | 5 ++- crates/db_schema/src/impls/community.rs | 38 +++++++++++++++++++ 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/crates/apub/src/collections/community_featured.rs b/crates/apub/src/collections/community_featured.rs index 8fa86bbdb..eb19d64ea 100644 --- a/crates/apub/src/collections/community_featured.rs +++ b/crates/apub/src/collections/community_featured.rs @@ -6,11 +6,14 @@ use activitypub_federation::{ config::Data, kinds::collection::OrderedCollectionType, protocol::verification::verify_domains_match, - traits::{ActivityHandler, Collection, Object}, + traits::{Collection, Object}, }; use futures::future::{join_all, try_join_all}; use lemmy_api_common::{context::LemmyContext, utils::generate_featured_url}; -use lemmy_db_schema::{source::post::Post, utils::FETCH_LIMIT_MAX}; +use lemmy_db_schema::{ + source::{community::Community, post::Post}, + utils::FETCH_LIMIT_MAX, +}; use lemmy_utils::error::LemmyError; use url::Url; @@ -55,35 +58,36 @@ impl Collection for ApubCommunityFeatured { async fn from_json( apub: Self::Kind, - _owner: &Self::Owner, - data: &Data, + owner: &Self::Owner, + context: &Data, ) -> Result where Self: Sized, { - let mut posts = apub.ordered_items; - if posts.len() as i64 > FETCH_LIMIT_MAX { - posts = posts + let mut pages = apub.ordered_items; + if pages.len() as i64 > FETCH_LIMIT_MAX { + pages = pages .get(0..(FETCH_LIMIT_MAX as usize)) .unwrap_or_default() .to_vec(); } - // We intentionally ignore errors here. This is because the outbox might contain posts from old - // Lemmy versions, or from other software which we cant parse. In that case, we simply skip the - // item and only parse the ones that work. // process items in parallel, to avoid long delay from fetch_site_metadata() and other processing - join_all(posts.into_iter().map(|post| { + let stickied_posts: Vec = join_all(pages.into_iter().map(|page| { async { // use separate request counter for each item, otherwise there will be problems with // parallel processing - let verify = post.verify(data).await; - if verify.is_ok() { - post.receive(data).await.ok(); - } + ApubPost::verify(&page, &apub.id, context).await?; + ApubPost::from_json(page, context).await } })) - .await; + .await + // ignore any failed or unparseable items + .into_iter() + .filter_map(|p| p.ok().map(|p| p.0)) + .collect(); + + Community::set_featured_posts(owner.id, stickied_posts, &mut context.pool()).await?; // This return value is unused, so just set an empty vec Ok(ApubCommunityFeatured(())) diff --git a/crates/apub/src/objects/community.rs b/crates/apub/src/objects/community.rs index 35e445b47..36f26a9f4 100644 --- a/crates/apub/src/objects/community.rs +++ b/crates/apub/src/objects/community.rs @@ -166,7 +166,7 @@ impl Object for ApubCommunity { moderators_url: group.attributed_to.clone().map(Into::into), posting_restricted_to_mods: group.posting_restricted_to_mods, instance_id, - featured_url: group.featured.map(Into::into), + featured_url: group.featured.clone().map(Into::into), ..Default::default() }; let languages = @@ -184,6 +184,9 @@ impl Object for ApubCommunity { spawn_try_task(async move { group.outbox.dereference(&community_, &context_).await?; group.followers.dereference(&community_, &context_).await?; + if let Some(featured) = group.featured { + featured.dereference(&community_, &context_).await?; + } if let Some(moderators) = group.attributed_to { moderators.dereference(&community_, &context_).await?; } diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 3298da894..88b0ef46d 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -14,6 +14,7 @@ use crate::{ CommunityPersonBanForm, CommunityUpdateForm, }, + post::Post, }, traits::{ApubActor, Bannable, Crud, Followable, Joinable}, utils::{functions::lower, get_conn, DbPool}, @@ -27,6 +28,7 @@ use diesel::{ result::Error, select, sql_types, + update, ExpressionMethods, NullableExpressionMethods, QueryDsl, @@ -137,6 +139,42 @@ impl Community { } Err(diesel::NotFound) } + + pub async fn set_featured_posts( + community_id: CommunityId, + posts: Vec, + pool: &mut DbPool<'_>, + ) -> Result<(), Error> { + use crate::schema::post; + let conn = &mut get_conn(pool).await?; + for p in &posts { + debug_assert!(p.community_id == community_id); + } + conn + .build_transaction() + .run(|conn| { + Box::pin(async move { + update( + // first remove all existing featured posts + post::table, + ) + .filter(post::dsl::community_id.eq(community_id)) + .set(post::dsl::featured_community.eq(false)) + .execute(conn) + .await?; + + // then mark the given posts as featured + let post_ids: Vec<_> = posts.iter().map(|p| p.id).collect(); + update(post::table) + .filter(post::dsl::id.eq_any(post_ids)) + .set(post::dsl::featured_community.eq(true)) + .execute(conn) + .await?; + Ok(()) + }) as _ + }) + .await + } } impl CommunityModerator {