From: Felix Ableitner <me@nutomic.com>
Date: Thu, 5 Aug 2021 11:00:29 +0000 (+0200)
Subject: Migrate comment inReplyTo field to single value (ref #1454)
X-Git-Url: http://these/git/%7B%60https:/%22https:/lemmy.ml/u/Liwott/%22?a=commitdiff_plain;h=b2937223dfea9332f87f91bbbcc4780d6bfa4a7c;p=lemmy.git

Migrate comment inReplyTo field to single value (ref #1454)
---

diff --git a/crates/apub/src/lib.rs b/crates/apub/src/lib.rs
index 60d7c487..75f97aad 100644
--- a/crates/apub/src/lib.rs
+++ b/crates/apub/src/lib.rs
@@ -6,6 +6,7 @@ pub mod activity_queue;
 pub mod extensions;
 pub mod fetcher;
 pub mod http;
+pub mod migrations;
 pub mod objects;
 
 use crate::{
diff --git a/crates/apub/src/migrations.rs b/crates/apub/src/migrations.rs
new file mode 100644
index 00000000..0f18d2f3
--- /dev/null
+++ b/crates/apub/src/migrations.rs
@@ -0,0 +1,17 @@
+use serde::{Deserialize, Serialize};
+use url::Url;
+
+/// Migrate comment.in_reply_to field from containing post and parent comment ID, to only containing
+/// the direct parent (whether its a post or comment). This is for compatibility with Pleroma and
+/// Smithereen.
+/// [https://github.com/LemmyNet/lemmy/issues/1454]
+///
+/// v0.12: receive both, send old (compatible with v0.11)
+/// v0.13 receive both, send new (compatible with v0.12+, incompatible with v0.11)
+/// v0.14: only send and receive new, remove migration (compatible with v0.13+)
+#[derive(Serialize, Deserialize, Debug, Clone)]
+#[serde(untagged)]
+pub enum CommentInReplyToMigration {
+  Old(Vec<Url>),
+  New(Url),
+}
diff --git a/crates/apub/src/objects/comment.rs b/crates/apub/src/objects/comment.rs
index c97ea994..d402613a 100644
--- a/crates/apub/src/objects/comment.rs
+++ b/crates/apub/src/objects/comment.rs
@@ -1,9 +1,15 @@
 use crate::{
   activities::verify_person_in_community,
   extensions::context::lemmy_context,
-  fetcher::objects::{get_or_fetch_and_insert_comment, get_or_fetch_and_insert_post},
+  fetcher::objects::{
+    get_or_fetch_and_insert_comment,
+    get_or_fetch_and_insert_post,
+    get_or_fetch_and_insert_post_or_comment,
+  },
+  migrations::CommentInReplyToMigration,
   objects::{create_tombstone, get_or_fetch_and_upsert_person, FromApub, Source, ToApub},
   ActorType,
+  PostOrComment,
 };
 use activitystreams::{
   base::AnyBase,
@@ -35,6 +41,7 @@ use lemmy_utils::{
 };
 use lemmy_websocket::LemmyContext;
 use serde::{Deserialize, Serialize};
+use std::ops::Deref;
 use url::Url;
 
 #[derive(Clone, Debug, Deserialize, Serialize)]
@@ -52,7 +59,7 @@ pub struct Note {
   content: String,
   media_type: MediaTypeHtml,
   source: Source,
-  in_reply_to: Vec<Url>,
+  in_reply_to: CommentInReplyToMigration,
   published: DateTime<FixedOffset>,
   updated: Option<DateTime<FixedOffset>>,
   #[serde(flatten)]
@@ -65,32 +72,55 @@ impl Note {
     context: &LemmyContext,
     request_counter: &mut i32,
   ) -> Result<(Post, Option<CommentId>), LemmyError> {
-    // This post, or the parent comment might not yet exist on this server yet, fetch them.
-    let post_id = self.in_reply_to.get(0).context(location_info!())?;
-    let post = Box::pin(get_or_fetch_and_insert_post(
-      post_id,
-      context,
-      request_counter,
-    ))
-    .await?;
-
-    // The 2nd item, if it exists, is the parent comment apub_id
-    // Nested comments will automatically get fetched recursively
-    let parent_id: Option<CommentId> = match self.in_reply_to.get(1) {
-      Some(parent_comment_uri) => {
-        let parent_comment = Box::pin(get_or_fetch_and_insert_comment(
-          parent_comment_uri,
+    match &self.in_reply_to {
+      CommentInReplyToMigration::Old(in_reply_to) => {
+        // This post, or the parent comment might not yet exist on this server yet, fetch them.
+        let post_id = in_reply_to.get(0).context(location_info!())?;
+        let post = Box::pin(get_or_fetch_and_insert_post(
+          post_id,
           context,
           request_counter,
         ))
         .await?;
 
-        Some(parent_comment.id)
+        // The 2nd item, if it exists, is the parent comment apub_id
+        // Nested comments will automatically get fetched recursively
+        let parent_id: Option<CommentId> = match in_reply_to.get(1) {
+          Some(parent_comment_uri) => {
+            let parent_comment = Box::pin(get_or_fetch_and_insert_comment(
+              parent_comment_uri,
+              context,
+              request_counter,
+            ))
+            .await?;
+
+            Some(parent_comment.id)
+          }
+          None => None,
+        };
+
+        Ok((post, parent_id))
       }
-      None => None,
-    };
-
-    Ok((post, parent_id))
+      CommentInReplyToMigration::New(in_reply_to) => {
+        let parent = Box::pin(
+          get_or_fetch_and_insert_post_or_comment(in_reply_to, context, request_counter).await?,
+        );
+        match parent.deref() {
+          PostOrComment::Post(p) => {
+            // Workaround because I cant figure ut how to get the post out of the box (and we dont
+            // want to stackoverflow in a deep comment hierarchy).
+            let post_id = p.id;
+            let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
+            Ok((post, None))
+          }
+          PostOrComment::Comment(c) => {
+            let post_id = c.post_id;
+            let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
+            Ok((post, Some(c.id)))
+          }
+        }
+      }
+    }
   }
 
   pub(crate) async fn verify(
@@ -153,7 +183,7 @@ impl ToApub for Comment {
         content: self.content.clone(),
         media_type: MediaTypeMarkdown::Markdown,
       },
-      in_reply_to: in_reply_to_vec,
+      in_reply_to: CommentInReplyToMigration::Old(in_reply_to_vec),
       published: convert_datetime(self.published),
       updated: self.updated.map(convert_datetime),
       unparsed: Default::default(),