]> Untitled Git - lemmy.git/commitdiff
Adding undo follow community.
authorDessalines <tyhou13@gmx.com>
Mon, 4 May 2020 02:41:45 +0000 (22:41 -0400)
committerDessalines <tyhou13@gmx.com>
Mon, 4 May 2020 02:41:45 +0000 (22:41 -0400)
server/src/api/community.rs
server/src/apub/community.rs
server/src/apub/community_inbox.rs
server/src/apub/mod.rs
server/src/apub/user.rs
ui/src/api_tests/api.spec.ts

index 9659469b9e33ed80cf70ccdfb28a5145bd896e92..9855b78871b5607df76f4d60ad7a7bcd7314601e 100644 (file)
@@ -483,12 +483,12 @@ impl Perform for Oper<FollowCommunity> {
     let conn = pool.get()?;
 
     let community = Community::read(&conn, data.community_id)?;
-    if community.local {
-      let community_follower_form = CommunityFollowerForm {
-        community_id: data.community_id,
-        user_id,
-      };
+    let community_follower_form = CommunityFollowerForm {
+      community_id: data.community_id,
+      user_id,
+    };
 
+    if community.local {
       if data.follow {
         match CommunityFollower::follow(&conn, &community_follower_form) {
           Ok(user) => user,
@@ -501,9 +501,19 @@ impl Perform for Oper<FollowCommunity> {
         };
       }
     } else {
-      // TODO: still have to implement unfollow
       let user = User_::read(&conn, user_id)?;
-      user.send_follow(&community.actor_id, &conn)?;
+
+      if data.follow {
+        // Dont actually add to the community followers here, because you need
+        // to wait for the accept
+        user.send_follow(&community.actor_id, &conn)?;
+      } else {
+        user.send_unfollow(&community.actor_id, &conn)?;
+        match CommunityFollower::ignore(&conn, &community_follower_form) {
+          Ok(user) => user,
+          Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
+        };
+      }
       // TODO: this needs to return a "pending" state, until Accept is received from the remote server
     }
 
index a05d1ce0c9a016aba1b08ecf5869309442560805..feffa70ef9b089819853a4cb5b9caa7baa57fa7f 100644 (file)
@@ -289,6 +289,14 @@ impl ActorType for Community {
         .collect(),
     )
   }
+
+  fn send_follow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> {
+    unimplemented!()
+  }
+
+  fn send_unfollow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> {
+    unimplemented!()
+  }
 }
 
 impl FromApub for CommunityForm {
index 92422cbbde45447a1dd8623cab183e3c7802446a..0aec5ff6e367c206641eeca9d4fe6cda01840161 100644 (file)
@@ -4,6 +4,7 @@ use super::*;
 #[derive(Deserialize, Debug)]
 pub enum CommunityAcceptedObjects {
   Follow(Follow),
+  Undo(Undo),
 }
 
 // TODO Consolidate community and user inboxes into a single shared one
@@ -25,6 +26,9 @@ pub async fn community_inbox(
     CommunityAcceptedObjects::Follow(f) => {
       handle_follow(&f, &request, &community_name, db, chat_server)
     }
+    CommunityAcceptedObjects::Undo(u) => {
+      handle_undo_follow(&u, &request, &community_name, db, chat_server)
+    }
   }
 }
 
@@ -76,3 +80,56 @@ fn handle_follow(
 
   Ok(HttpResponse::Ok().finish())
 }
+
+fn handle_undo_follow(
+  undo: &Undo,
+  request: &HttpRequest,
+  community_name: &str,
+  db: DbPoolParam,
+  _chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+  let follow = undo
+    .undo_props
+    .get_object_base_box()
+    .to_owned()
+    .unwrap()
+    .to_owned()
+    .into_concrete::<Follow>()?;
+
+  let user_uri = follow
+    .follow_props
+    .get_actor_xsd_any_uri()
+    .unwrap()
+    .to_string();
+
+  let _community_uri = follow
+    .follow_props
+    .get_object_xsd_any_uri()
+    .unwrap()
+    .to_string();
+
+  let conn = db.get()?;
+
+  let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+  let community = Community::read_from_name(&conn, &community_name)?;
+
+  verify(&request, &user.public_key.unwrap())?;
+
+  // Insert the received activity into the activity table
+  let activity_form = activity::ActivityForm {
+    user_id: user.id,
+    data: serde_json::to_value(&follow)?,
+    local: false,
+    updated: None,
+  };
+  activity::Activity::create(&conn, &activity_form)?;
+
+  let community_follower_form = CommunityFollowerForm {
+    community_id: community.id,
+    user_id: user.id,
+  };
+
+  CommunityFollower::ignore(&conn, &community_follower_form).ok();
+
+  Ok(HttpResponse::Ok().finish())
+}
index c5bd2ea4388a2882ffc07e48b46f3c1b7c6ea80e..40f4322ea16eb74dfebf259c4e50a7f63eb85c50 100644 (file)
@@ -233,14 +233,11 @@ pub trait ActorType {
   // These two have default impls, since currently a community can't follow anything,
   // and a user can't be followed (yet)
   #[allow(unused_variables)]
-  fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> {
-    Err(format_err!("Follow not implemented."))
-  }
+  fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>;
+  fn send_unfollow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>;
 
   #[allow(unused_variables)]
-  fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error> {
-    Err(format_err!("Accept not implemented."))
-  }
+  fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error>;
 
   fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
   fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
@@ -248,12 +245,8 @@ pub trait ActorType {
   fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
   fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
 
-  // TODO default because there is no user following yet.
-  #[allow(unused_variables)]
   /// For a given community, returns the inboxes of all followers.
-  fn get_follower_inboxes(&self, conn: &PgConnection) -> Result<Vec<String>, Error> {
-    Ok(vec![])
-  }
+  fn get_follower_inboxes(&self, conn: &PgConnection) -> Result<Vec<String>, Error>;
 
   // TODO move these to the db rows
   fn get_inbox_url(&self) -> String {
index d9c7e86a00ceb568754b6c5b633d703e0f77c78b..71f6f5c93d19f7d37f51f7ca5d7b4fb0c83e72d0 100644 (file)
@@ -91,6 +91,54 @@ impl ActorType for User_ {
     Ok(())
   }
 
+  fn send_unfollow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> {
+    let mut follow = Follow::new();
+
+    let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
+
+    follow
+      .object_props
+      .set_context_xsd_any_uri(context())?
+      .set_id(id)?;
+    follow
+      .follow_props
+      .set_actor_xsd_any_uri(self.actor_id.to_owned())?
+      .set_object_xsd_any_uri(follow_actor_id)?;
+    let to = format!("{}/inbox", follow_actor_id);
+
+    // TODO
+    // Undo that fake activity
+    let undo_id = format!("{}/undo/follow/{}", self.actor_id, uuid::Uuid::new_v4());
+    let mut undo = Undo::default();
+
+    undo
+      .object_props
+      .set_context_xsd_any_uri(context())?
+      .set_id(undo_id)?;
+
+    undo
+      .undo_props
+      .set_actor_xsd_any_uri(self.actor_id.to_owned())?
+      .set_object_base_box(follow)?;
+
+    // Insert the sent activity into the activity table
+    let activity_form = activity::ActivityForm {
+      user_id: self.id,
+      data: serde_json::to_value(&undo)?,
+      local: true,
+      updated: None,
+    };
+    activity::Activity::create(&conn, &activity_form)?;
+
+    send_activity(
+      &undo,
+      &self.private_key.as_ref().unwrap(),
+      &follow_actor_id,
+      vec![to],
+    )?;
+    Ok(())
+  }
+
   fn send_delete(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
     unimplemented!()
   }
@@ -106,6 +154,14 @@ impl ActorType for User_ {
   fn send_undo_remove(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
     unimplemented!()
   }
+
+  fn send_accept_follow(&self, _follow: &Follow, _conn: &PgConnection) -> Result<(), Error> {
+    unimplemented!()
+  }
+
+  fn get_follower_inboxes(&self, _conn: &PgConnection) -> Result<Vec<String>, Error> {
+    unimplemented!()
+  }
 }
 
 impl FromApub for UserForm {
index b25c8df5fe64c483a77a8ca83ce919e685046a85..a3826504a4bd3777655dd93ab87531f2822275f9 100644 (file)
@@ -140,6 +140,50 @@ describe('main', () => {
       ).then(d => d.json());
 
       expect(followedCommunitiesRes.communities[1].community_local).toBe(false);
+
+      // Test out unfollowing
+      let unfollowForm: FollowCommunityForm = {
+        community_id: searchResponse.communities[0].id,
+        follow: false,
+        auth: lemmyAlphaAuth,
+      };
+
+      let unfollowRes: CommunityResponse = await fetch(
+        `${lemmyAlphaApiUrl}/community/follow`,
+        {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+          },
+          body: wrapper(unfollowForm),
+        }
+      ).then(d => d.json());
+
+      // Check that you are unsubscribed to it locally
+      let followedCommunitiesResAgain: GetFollowedCommunitiesResponse = await fetch(
+        followedCommunitiesUrl,
+        {
+          method: 'GET',
+        }
+      ).then(d => d.json());
+
+      expect(followedCommunitiesResAgain.communities.length).toBe(1);
+
+      // Follow again, for other tests
+      let followResAgain: CommunityResponse = await fetch(
+        `${lemmyAlphaApiUrl}/community/follow`,
+        {
+          method: 'POST',
+          headers: {
+            'Content-Type': 'application/json',
+          },
+          body: wrapper(followForm),
+        }
+      ).then(d => d.json());
+
+      // Make sure the follow response went through
+      expect(followResAgain.community.local).toBe(false);
+      expect(followResAgain.community.name).toBe('main');
     });
   });