]> Untitled Git - lemmy.git/commitdiff
Implemented follow/accept
authorFelix <me@nutomic.com>
Tue, 14 Apr 2020 15:37:23 +0000 (17:37 +0200)
committerFelix <me@nutomic.com>
Tue, 14 Apr 2020 15:37:23 +0000 (17:37 +0200)
server/src/api/community.rs
server/src/apub/activities.rs
server/src/apub/community.rs
server/src/apub/inbox.rs
server/src/apub/post.rs
server/src/main.rs
server/src/routes/federation.rs

index 3edecb4f561c03f76f38af4b2ad1120db13ba01b..35ca1d260dac8ca1a4028339d2f420bc0496b16c 100644 (file)
@@ -1,4 +1,5 @@
 use super::*;
+use crate::apub::activities::follow_community;
 use crate::apub::{format_community_name, gen_keypair_str, make_apub_endpoint, EndpointType};
 use diesel::PgConnection;
 use std::str::FromStr;
@@ -401,21 +402,29 @@ impl Perform<CommunityResponse> for Oper<FollowCommunity> {
 
     let user_id = claims.id;
 
-    let community_follower_form = CommunityFollowerForm {
-      community_id: data.community_id,
-      user_id,
-    };
-
-    if data.follow {
-      match CommunityFollower::follow(&conn, &community_follower_form) {
-        Ok(user) => user,
-        Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
+    let community = Community::read(conn, data.community_id)?;
+    if community.local {
+      let community_follower_form = CommunityFollowerForm {
+        community_id: data.community_id,
+        user_id,
       };
+
+      if data.follow {
+        match CommunityFollower::follow(&conn, &community_follower_form) {
+          Ok(user) => user,
+          Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
+        };
+      } else {
+        match CommunityFollower::ignore(&conn, &community_follower_form) {
+          Ok(user) => user,
+          Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
+        };
+      }
     } else {
-      match CommunityFollower::ignore(&conn, &community_follower_form) {
-        Ok(user) => user,
-        Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
-      };
+      // TODO: still have to implement unfollow
+      let user = User_::read(conn, user_id)?;
+      follow_community(&community, &user, conn)?;
+      // TODO: this needs to return a "pending" state, until Accept is received from the remote server
     }
 
     let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?;
index 0c1a1901d9543812dec87127d5618b41bb3ed3a6..a1707267c2abda47866870f1a5d5345cc4de450e 100644 (file)
@@ -3,8 +3,9 @@ use crate::db::community::Community;
 use crate::db::post::Post;
 use crate::db::user::User_;
 use crate::db::Crud;
-use activitystreams::activity::{Create, Update};
+use activitystreams::activity::{Accept, Create, Follow, Update};
 use activitystreams::object::properties::ObjectProperties;
+use activitystreams::BaseBox;
 use activitystreams::{context, public};
 use diesel::PgConnection;
 use failure::Error;
@@ -28,19 +29,15 @@ fn populate_object_props(
   Ok(())
 }
 
-fn send_activity<A>(activity: &A) -> Result<(), Error>
+fn send_activity<A>(activity: &A, to: Vec<String>) -> Result<(), Error>
 where
   A: Serialize + Debug,
 {
   let json = serde_json::to_string(&activity)?;
-  for i in get_following_instances() {
-    // TODO: need to send this to the inbox of following users
-    let inbox = format!(
-      "{}://{}/federation/inbox",
-      get_apub_protocol_string(),
-      i.domain
-    );
-    let res = Request::post(inbox)
+  println!("sending data {}", json);
+  for t in to {
+    println!("to: {}", t);
+    let res = Request::post(t)
       .header("Content-Type", "application/json")
       .body(json.to_owned())?
       .send()?;
@@ -49,6 +46,20 @@ where
   Ok(())
 }
 
+fn get_followers(_community: &Community) -> Vec<String> {
+  // TODO: this is wrong, needs to go to the (non-local) followers of the community
+  get_following_instances()
+    .iter()
+    .map(|i| {
+      format!(
+        "{}://{}/federation/inbox",
+        get_apub_protocol_string(),
+        i.domain
+      )
+    })
+    .collect()
+}
+
 pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
   let page = post.as_page(conn)?;
   let community = Community::read(conn, post.community_id)?;
@@ -62,7 +73,7 @@ pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result<
     .create_props
     .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
     .set_object_base_box(page)?;
-  send_activity(&create)?;
+  send_activity(&create, get_followers(&community))?;
   Ok(())
 }
 
@@ -79,6 +90,54 @@ pub fn post_update(post: &Post, creator: &User_, conn: &PgConnection) -> Result<
     .update_props
     .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
     .set_object_base_box(page)?;
-  send_activity(&update)?;
+  send_activity(&update, get_followers(&community))?;
+  Ok(())
+}
+
+pub fn follow_community(
+  community: &Community,
+  user: &User_,
+  _conn: &PgConnection,
+) -> Result<(), Error> {
+  let mut follow = Follow::new();
+  follow
+    .object_props
+    .set_context_xsd_any_uri(context())?
+    // TODO: needs proper id
+    .set_id(user.actor_id.clone())?;
+  follow
+    .follow_props
+    .set_actor_xsd_any_uri(user.actor_id.clone())?
+    .set_object_xsd_any_uri(community.actor_id.clone())?;
+  let to = format!("{}/inbox", community.actor_id);
+  send_activity(&follow, vec![to])?;
+  Ok(())
+}
+
+pub fn accept_follow(follow: &Follow) -> Result<(), Error> {
+  let mut accept = Accept::new();
+  accept
+    .object_props
+    .set_context_xsd_any_uri(context())?
+    // TODO: needs proper id
+    .set_id(
+      follow
+        .follow_props
+        .get_actor_xsd_any_uri()
+        .unwrap()
+        .to_string(),
+    )?;
+  accept
+    .accept_props
+    .set_object_base_box(BaseBox::from_concrete(follow.clone())?)?;
+  let to = format!(
+    "{}/inbox",
+    follow
+      .follow_props
+      .get_actor_xsd_any_uri()
+      .unwrap()
+      .to_string()
+  );
+  send_activity(&accept, vec![to])?;
   Ok(())
 }
index a56d81d0c386be23b3b80588a8885a804174faf7..0bea47055c110712dbf694fcf8f949bb8220d9a7 100644 (file)
@@ -64,7 +64,7 @@ impl Community {
       .set_id(self.actor_id.to_owned())?
       .set_name_xsd_string(self.name.to_owned())?
       .set_published(convert_datetime(self.published))?
-      .set_attributed_to_xsd_any_uri(make_apub_endpoint(EndpointType::User, &creator.name))?;
+      .set_attributed_to_xsd_any_uri(creator.actor_id)?;
 
     if let Some(u) = self.updated.to_owned() {
       oprops.set_updated(convert_datetime(u))?;
@@ -156,7 +156,6 @@ pub async fn get_apub_community_followers(
   db: web::Data<Pool<ConnectionManager<PgConnection>>>,
 ) -> Result<HttpResponse<Body>, Error> {
   let community = Community::read_from_name(&&db.get()?, info.community_name.to_owned())?;
-  let base_url = make_apub_endpoint(EndpointType::Community, &community.name);
 
   let connection = establish_unpooled_connection();
   //As we are an object, we validated that the community id was valid
@@ -167,7 +166,7 @@ pub async fn get_apub_community_followers(
   let oprops: &mut ObjectProperties = collection.as_mut();
   oprops
     .set_context_xsd_any_uri(context())?
-    .set_id(base_url)?;
+    .set_id(community.actor_id)?;
   collection
     .collection_props
     .set_total_items(community_followers.len() as u64)?;
@@ -179,7 +178,6 @@ pub async fn get_apub_community_outbox(
   db: web::Data<Pool<ConnectionManager<PgConnection>>>,
 ) -> Result<HttpResponse<Body>, Error> {
   let community = Community::read_from_name(&&db.get()?, info.community_name.to_owned())?;
-  let base_url = make_apub_endpoint(EndpointType::Community, &community.name);
 
   let conn = establish_unpooled_connection();
   //As we are an object, we validated that the community id was valid
@@ -189,7 +187,7 @@ pub async fn get_apub_community_outbox(
   let oprops: &mut ObjectProperties = collection.as_mut();
   oprops
     .set_context_xsd_any_uri(context())?
-    .set_id(base_url)?;
+    .set_id(community.actor_id)?;
   collection
     .collection_props
     .set_many_items_base_boxes(
index cc844224a2c771eb281323dabcd9f47e89d08226..a2db335a889d7bec2ef884df41544a6e110012cf 100644 (file)
@@ -1,16 +1,16 @@
+use crate::apub::activities::accept_follow;
+use crate::apub::fetcher::fetch_remote_user;
+use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm};
 use crate::db::post::{Post, PostForm};
 use crate::db::Crud;
+use crate::db::Followable;
+use activitystreams::activity::{Accept, Create, Follow, Update};
 use activitystreams::object::Page;
-use activitystreams::{
-  object::{Object, ObjectBox},
-  primitives::XsdAnyUri,
-  Base, BaseBox, PropRefs,
-};
 use actix_web::{web, HttpResponse};
 use diesel::r2d2::{ConnectionManager, Pool};
 use diesel::PgConnection;
 use failure::Error;
-use std::collections::HashMap;
+use url::Url;
 
 // TODO: need a proper actor that has this inbox
 
@@ -18,24 +18,40 @@ pub async fn inbox(
   input: web::Json<AcceptedObjects>,
   db: web::Data<Pool<ConnectionManager<PgConnection>>>,
 ) -> Result<HttpResponse, Error> {
+  // TODO: make sure that things are received in the correct inbox
+  //      (by using seperate handler functions and checking the user/community name in the path)
   let input = input.into_inner();
   let conn = &db.get().unwrap();
-  match input.kind {
-    ValidTypes::Create => handle_create(&input, conn),
-    ValidTypes::Update => handle_update(&input, conn),
+  match input {
+    AcceptedObjects::Create(c) => handle_create(&c, conn),
+    AcceptedObjects::Update(u) => handle_update(&u, conn),
+    AcceptedObjects::Follow(f) => handle_follow(&f, conn),
+    AcceptedObjects::Accept(a) => handle_accept(&a, conn),
   }
 }
 
-fn handle_create(create: &AcceptedObjects, conn: &PgConnection) -> Result<HttpResponse, Error> {
-  let page = create.object.to_owned().to_concrete::<Page>()?;
+fn handle_create(create: &Create, conn: &PgConnection) -> Result<HttpResponse, Error> {
+  let page = create
+    .create_props
+    .get_object_base_box()
+    .to_owned()
+    .unwrap()
+    .to_owned()
+    .to_concrete::<Page>()?;
   let post = PostForm::from_page(&page, conn)?;
   Post::create(conn, &post)?;
   // TODO: send the new post out via websocket
   Ok(HttpResponse::Ok().finish())
 }
 
-fn handle_update(update: &AcceptedObjects, conn: &PgConnection) -> Result<HttpResponse, Error> {
-  let page = update.object.to_owned().to_concrete::<Page>()?;
+fn handle_update(update: &Update, conn: &PgConnection) -> Result<HttpResponse, Error> {
+  let page = update
+    .update_props
+    .get_object_base_box()
+    .to_owned()
+    .unwrap()
+    .to_owned()
+    .to_concrete::<Page>()?;
   let post = PostForm::from_page(&page, conn)?;
   let id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
   Post::update(conn, id, &post)?;
@@ -43,46 +59,43 @@ fn handle_update(update: &AcceptedObjects, conn: &PgConnection) -> Result<HttpRe
   Ok(HttpResponse::Ok().finish())
 }
 
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "camelCase")]
-pub struct AcceptedObjects {
-  pub id: XsdAnyUri,
+fn handle_follow(follow: &Follow, conn: &PgConnection) -> Result<HttpResponse, Error> {
+  println!("received follow: {:?}", &follow);
 
-  #[serde(rename = "type")]
-  pub kind: ValidTypes,
-
-  pub actor: XsdAnyUri,
-
-  pub object: BaseBox,
-
-  #[serde(flatten)]
-  ext: HashMap<String, serde_json::Value>,
+  // TODO: make sure this is a local community
+  let community_uri = follow
+    .follow_props
+    .get_object_xsd_any_uri()
+    .unwrap()
+    .to_string();
+  let community = Community::read_from_actor_id(conn, &community_uri)?;
+  let user_uri = follow
+    .follow_props
+    .get_actor_xsd_any_uri()
+    .unwrap()
+    .to_string();
+  let user = fetch_remote_user(&Url::parse(&user_uri)?, conn)?;
+  // TODO: insert ID of the user into follows of the community
+  let community_follower_form = CommunityFollowerForm {
+    community_id: community.id,
+    user_id: user.id,
+  };
+  CommunityFollower::follow(&conn, &community_follower_form)?;
+  accept_follow(&follow)?;
+  Ok(HttpResponse::Ok().finish())
 }
 
-#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)]
-#[serde(rename_all = "PascalCase")]
-pub enum ValidTypes {
-  Create,
-  Update,
+fn handle_accept(accept: &Accept, _conn: &PgConnection) -> Result<HttpResponse, Error> {
+  println!("received accept: {:?}", &accept);
+  // TODO: at this point, indicate to the user that they are following the community
+  Ok(HttpResponse::Ok().finish())
 }
 
-#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]
 #[serde(untagged)]
-#[serde(rename_all = "camelCase")]
-pub enum ValidObjects {
-  Id(XsdAnyUri),
-  Object(AnyExistingObject),
-}
-
-#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, PropRefs)]
-#[serde(rename_all = "camelCase")]
-#[prop_refs(Object)]
-pub struct AnyExistingObject {
-  pub id: XsdAnyUri,
-
-  #[serde(rename = "type")]
-  pub kind: String,
-
-  #[serde(flatten)]
-  ext: HashMap<String, serde_json::Value>,
+#[derive(serde::Deserialize)]
+pub enum AcceptedObjects {
+  Create(Create),
+  Update(Update),
+  Follow(Follow),
+  Accept(Accept),
 }
index e8f539049eb72941a22d9f6b80e3f34a0f169566..b574d09c0b60eb99b5b5d947d9cc57dd50be7dc9 100644 (file)
@@ -1,5 +1,5 @@
+use crate::apub::create_apub_response;
 use crate::apub::fetcher::{fetch_remote_community, fetch_remote_user};
-use crate::apub::{create_apub_response, make_apub_endpoint, EndpointType};
 use crate::convert_datetime;
 use crate::db::community::Community;
 use crate::db::post::{Post, PostForm};
@@ -31,7 +31,6 @@ pub async fn get_apub_post(
 
 impl Post {
   pub fn as_page(&self, conn: &PgConnection) -> Result<Page, Error> {
-    let base_url = make_apub_endpoint(EndpointType::Post, &self.id.to_string());
     let mut page = Page::default();
     let oprops: &mut ObjectProperties = page.as_mut();
     let creator = User_::read(conn, self.creator_id)?;
@@ -40,13 +39,13 @@ impl Post {
     oprops
       // Not needed when the Post is embedded in a collection (like for community outbox)
       .set_context_xsd_any_uri(context())?
-      .set_id(base_url)?
+      .set_id(self.ap_id.to_owned())?
       // Use summary field to be consistent with mastodon content warning.
       // https://mastodon.xyz/@Louisa/103987265222901387.json
       .set_summary_xsd_string(self.name.to_owned())?
       .set_published(convert_datetime(self.published))?
       .set_to_xsd_any_uri(community.actor_id)?
-      .set_attributed_to_xsd_any_uri(make_apub_endpoint(EndpointType::User, &creator.name))?;
+      .set_attributed_to_xsd_any_uri(creator.actor_id)?;
 
     if let Some(body) = &self.body {
       oprops.set_content_xsd_string(body.to_owned())?;
index 9f78d43e234eb534a14c75088b0808abe0c5953e..59dc2cb7572428c8d353ade1d78fc8ff1e4c8cef 100644 (file)
@@ -39,8 +39,6 @@ async fn main() -> Result<(), Error> {
   // Set up websocket server
   let server = ChatServer::startup(pool.clone()).start();
 
-  // TODO: its probably failing because the other instance is not up yet
-  //       need to make a new thread and wait a bit before fetching
   thread::spawn(move || {
     // some work here
     sleep(Duration::from_secs(5));
index 100e548f67fd45404b8bf894a15fe53ba6e1631e..2798e7a9526a168e0260daf359bc52f9a0447a50 100644 (file)
@@ -12,7 +12,14 @@ pub fn config(cfg: &mut web::ServiceConfig) {
       )
       // TODO: this needs to be moved to the actors (eg /federation/u/{}/inbox)
       .route("/federation/inbox", web::post().to(apub::inbox::inbox))
-      .route("/federation/inbox", web::post().to(apub::inbox::inbox))
+      .route(
+        "/federation/c/{_}/inbox",
+        web::post().to(apub::inbox::inbox),
+      )
+      .route(
+        "/federation/u/{_}/inbox",
+        web::post().to(apub::inbox::inbox),
+      )
       .route(
         "/federation/c/{community_name}",
         web::get().to(apub::community::get_apub_community_http),