From 88b17e90ac02ed6d2fbcb7f3223328f4a6ef6b25 Mon Sep 17 00:00:00 2001 From: Felix Date: Mon, 13 Apr 2020 15:06:41 +0200 Subject: [PATCH] Get inbox working properly --- docker/federation/docker-compose.yml | 2 + docker/federation/run-federation-test.bash | 8 +- server/src/api/post.rs | 15 ++-- server/src/apub/activities.rs | 76 ++++++++++++++----- server/src/apub/community.rs | 16 +++- server/src/apub/fetcher.rs | 3 +- server/src/apub/inbox.rs | 85 +++++++++++++++++++--- server/src/apub/post.rs | 2 +- server/src/routes/federation.rs | 6 +- 9 files changed, 164 insertions(+), 49 deletions(-) diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml index f251a4f9..fa95dc20 100644 --- a/docker/federation/docker-compose.yml +++ b/docker/federation/docker-compose.yml @@ -28,6 +28,7 @@ services: - LEMMY_FEDERATION__TLS_ENABLED=false - LEMMY_PORT=8540 - RUST_BACKTRACE=1 + - RUST_LOG=actix_web=debug restart: always depends_on: - postgres_alpha @@ -58,6 +59,7 @@ services: - LEMMY_FEDERATION__TLS_ENABLED=false - LEMMY_PORT=8550 - RUST_BACKTRACE=1 + - RUST_LOG=actix_web=debug restart: always depends_on: - postgres_beta diff --git a/docker/federation/run-federation-test.bash b/docker/federation/run-federation-test.bash index 7cf26206..62bc1e8b 100755 --- a/docker/federation/run-federation-test.bash +++ b/docker/federation/run-federation-test.bash @@ -1,9 +1,11 @@ #!/bin/bash set -e -pushd ../../ui/ || exit -yarn build -popd || exit +if [ "$1" = "-yarn" ]; then + pushd ../../ui/ || exit + yarn build + popd || exit +fi pushd ../../server/ || exit cargo build diff --git a/server/src/api/post.rs b/server/src/api/post.rs index e0053fe8..eb8909b2 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -1,9 +1,9 @@ use super::*; -use crate::apub::activities::post_create; +use crate::apub::activities::{post_create, post_update}; use diesel::PgConnection; use std::str::FromStr; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct CreatePost { name: String, url: Option, @@ -150,12 +150,12 @@ impl Perform for Oper { } }; - match Post::update_ap_id(&conn, inserted_post.id) { + let updated_post = match Post::update_ap_id(&conn, inserted_post.id) { Ok(post) => post, Err(_e) => return Err(APIError::err("couldnt_create_post").into()), }; - post_create(&inserted_post, &user, conn)?; + post_create(&updated_post, &user, conn)?; // They like their own post by default let like_form = PostLikeForm { @@ -369,7 +369,8 @@ impl Perform for Oper { } // Check for a site ban - if UserView::read(&conn, user_id)?.banned { + let user = User_::read(&conn, user_id)?; + if user.banned { return Err(APIError::err("site_ban").into()); } @@ -400,7 +401,7 @@ impl Perform for Oper { published: None, }; - let _updated_post = match Post::update(&conn, data.edit_id, &post_form) { + let updated_post = match Post::update(&conn, data.edit_id, &post_form) { Ok(post) => post, Err(e) => { let err_type = if e.to_string() == "value too long for type character varying(200)" { @@ -442,6 +443,8 @@ impl Perform for Oper { ModStickyPost::create(&conn, &form)?; } + post_update(&updated_post, &user, conn)?; + let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?; Ok(PostResponse { post: post_view }) diff --git a/server/src/apub/activities.rs b/server/src/apub/activities.rs index ff0a4fc1..0c1a1901 100644 --- a/server/src/apub/activities.rs +++ b/server/src/apub/activities.rs @@ -3,30 +3,36 @@ use crate::db::community::Community; use crate::db::post::Post; use crate::db::user::User_; use crate::db::Crud; -use activitystreams::activity::Create; -use activitystreams::context; +use activitystreams::activity::{Create, Update}; +use activitystreams::object::properties::ObjectProperties; +use activitystreams::{context, public}; use diesel::PgConnection; use failure::Error; +use failure::_core::fmt::Debug; use isahc::prelude::*; +use serde::Serialize; -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)?; - let mut create = Create::new(); - create.object_props.set_context_xsd_any_uri(context())?; - create - .object_props - // TODO: seems like the create activity needs its own id (and be fetchable there) - .set_id(page.object_props.get_id().unwrap().to_string())? +fn populate_object_props( + props: &mut ObjectProperties, + addressed_to: &str, + object_id: &str, +) -> Result<(), Error> { + props + .set_context_xsd_any_uri(context())? + // TODO: the activity needs a seperate id from the object + .set_id(object_id)? // TODO: should to/cc go on the Create, or on the Post? or on both? // TODO: handle privacy on the receiving side (at least ignore anything thats not public) - .set_to_xsd_any_uri("https://www.w3.org/ns/activitystreams#Public")? - .set_cc_xsd_any_uri(format!("{}/followers", community.actor_id))?; - create - .create_props - .set_actor_xsd_any_uri(creator.actor_id.to_owned())?; - create.create_props.set_object_base_box(page)?; - let json = serde_json::to_string(&create)?; + .set_to_xsd_any_uri(public())? + .set_cc_xsd_any_uri(addressed_to)?; + Ok(()) +} + +fn send_activity(activity: &A) -> 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!( @@ -42,3 +48,37 @@ pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result< } Ok(()) } + +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)?; + let mut create = Create::new(); + populate_object_props( + &mut create.object_props, + &community.get_followers_url(), + &post.ap_id, + )?; + create + .create_props + .set_actor_xsd_any_uri(creator.actor_id.to_owned())? + .set_object_base_box(page)?; + send_activity(&create)?; + Ok(()) +} + +pub fn post_update(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> { + let page = post.as_page(conn)?; + let community = Community::read(conn, post.community_id)?; + let mut update = Update::new(); + populate_object_props( + &mut update.object_props, + &community.get_followers_url(), + &post.ap_id, + )?; + update + .update_props + .set_actor_xsd_any_uri(creator.actor_id.to_owned())? + .set_object_base_box(page)?; + send_activity(&update)?; + Ok(()) +} diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs index 36b9c703..a56d81d0 100644 --- a/server/src/apub/community.rs +++ b/server/src/apub/community.rs @@ -79,9 +79,9 @@ impl Community { actor_props .set_preferred_username(self.title.to_owned())? - .set_inbox(format!("{}/inbox", &self.actor_id))? - .set_outbox(format!("{}/outbox", &self.actor_id))? - .set_followers(format!("{}/followers", &self.actor_id))?; + .set_inbox(self.get_inbox_url())? + .set_outbox(self.get_outbox_url())? + .set_followers(self.get_followers_url())?; let public_key = PublicKey { id: format!("{}#main-key", self.actor_id), @@ -91,6 +91,16 @@ impl Community { Ok(group.extend(actor_props).extend(public_key.to_ext())) } + + pub fn get_followers_url(&self) -> String { + format!("{}/followers", &self.actor_id) + } + pub fn get_inbox_url(&self) -> String { + format!("{}/inbox", &self.actor_id) + } + pub fn get_outbox_url(&self) -> String { + format!("{}/outbox", &self.actor_id) + } } impl CommunityForm { diff --git a/server/src/apub/fetcher.rs b/server/src/apub/fetcher.rs index 3e9b6a9d..53c97b69 100644 --- a/server/src/apub/fetcher.rs +++ b/server/src/apub/fetcher.rs @@ -78,8 +78,7 @@ fn fetch_remote_community_posts( community: &Community, conn: &PgConnection, ) -> Result, Error> { - // TODO: need to add outbox field to Community - let outbox_url = Url::parse(&format!("{}/outbox", community.actor_id))?; + let outbox_url = Url::parse(&community.get_outbox_url())?; let outbox = fetch_remote_object::(&outbox_url)?; let items = outbox.collection_props.get_many_items_base_boxes(); diff --git a/server/src/apub/inbox.rs b/server/src/apub/inbox.rs index 8b6504a7..cc844224 100644 --- a/server/src/apub/inbox.rs +++ b/server/src/apub/inbox.rs @@ -1,27 +1,88 @@ use crate::db::post::{Post, PostForm}; use crate::db::Crud; -use activitystreams::activity::Create; 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; // TODO: need a proper actor that has this inbox -pub async fn create_inbox( - create: web::Json, +pub async fn inbox( + input: web::Json, db: web::Data>>, ) -> Result { - let page = create - .create_props - .get_object_base_box() - .unwrap() - .to_owned() - .to_concrete::()?; - let post = PostForm::from_page(&page, &db.get().unwrap())?; - Post::create(&db.get().unwrap(), &post)?; + 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), + } +} + +fn handle_create(create: &AcceptedObjects, conn: &PgConnection) -> Result { + let page = create.object.to_owned().to_concrete::()?; + let post = PostForm::from_page(&page, conn)?; + Post::create(conn, &post)?; // TODO: send the new post out via websocket - dbg!(&post); Ok(HttpResponse::Ok().finish()) } + +fn handle_update(update: &AcceptedObjects, conn: &PgConnection) -> Result { + let page = update.object.to_owned().to_concrete::()?; + let post = PostForm::from_page(&page, conn)?; + let id = Post::read_from_apub_id(conn, &post.ap_id)?.id; + Post::update(conn, id, &post)?; + // TODO: send the new post out via websocket + Ok(HttpResponse::Ok().finish()) +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AcceptedObjects { + pub id: XsdAnyUri, + + #[serde(rename = "type")] + pub kind: ValidTypes, + + pub actor: XsdAnyUri, + + pub object: BaseBox, + + #[serde(flatten)] + ext: HashMap, +} + +#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "PascalCase")] +pub enum ValidTypes { + Create, + Update, +} + +#[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, +} diff --git a/server/src/apub/post.rs b/server/src/apub/post.rs index e45a7d21..e8f53904 100644 --- a/server/src/apub/post.rs +++ b/server/src/apub/post.rs @@ -41,7 +41,7 @@ impl Post { // Not needed when the Post is embedded in a collection (like for community outbox) .set_context_xsd_any_uri(context())? .set_id(base_url)? - // Use summary field to be consistent with mastodon content warning. + // 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))? diff --git a/server/src/routes/federation.rs b/server/src/routes/federation.rs index f4fffdad..100e548f 100644 --- a/server/src/routes/federation.rs +++ b/server/src/routes/federation.rs @@ -11,10 +11,8 @@ pub fn config(cfg: &mut web::ServiceConfig) { web::get().to(apub::community::get_apub_community_list), ) // TODO: this needs to be moved to the actors (eg /federation/u/{}/inbox) - .route( - "/federation/inbox", - web::post().to(apub::inbox::create_inbox), - ) + .route("/federation/inbox", web::post().to(apub::inbox::inbox)) + .route("/federation/inbox", web::post().to(apub::inbox::inbox)) .route( "/federation/c/{community_name}", web::get().to(apub::community::get_apub_community_http),