diff options
author | Dessalines <tyhou13@gmx.com> | 2020-04-27 12:57:00 -0400 |
---|---|---|
committer | Dessalines <tyhou13@gmx.com> | 2020-04-27 12:57:00 -0400 |
commit | 22abbebd41d586298c62bb6a45efa7a96d998049 (patch) | |
tree | 60c31f49e9cee360cd06edc18eb53f93e953d6b7 | |
parent | 3ce061836242813730ec0b63240097347647dfc4 (diff) |
Lots of additions to federation.
- Added a shared inbox.
- Added federated comments, comment updates, and tests.
- Abstracted ap object sends into a common trait.
-rwxr-xr-x | docker/federation-test/run-tests.sh | 3 | ||||
-rw-r--r-- | server/src/api/comment.rs | 17 | ||||
-rw-r--r-- | server/src/api/mod.rs | 6 | ||||
-rw-r--r-- | server/src/api/post.rs | 4 | ||||
-rw-r--r-- | server/src/apub/activities.rs | 62 | ||||
-rw-r--r-- | server/src/apub/comment.rs | 139 | ||||
-rw-r--r-- | server/src/apub/community.rs | 26 | ||||
-rw-r--r-- | server/src/apub/mod.rs | 56 | ||||
-rw-r--r-- | server/src/apub/post.rs | 48 | ||||
-rw-r--r-- | server/src/apub/shared_inbox.rs | 242 | ||||
-rw-r--r-- | server/src/apub/user_inbox.rs | 65 | ||||
-rw-r--r-- | server/src/db/comment.rs | 8 | ||||
-rw-r--r-- | server/src/db/comment_view.rs | 1 | ||||
-rw-r--r-- | server/src/db/moderator.rs | 1 | ||||
-rw-r--r-- | server/src/db/user_mention.rs | 1 | ||||
-rw-r--r-- | server/src/routes/federation.rs | 4 | ||||
-rw-r--r-- | ui/jest.config.js | 1 | ||||
-rw-r--r-- | ui/src/api_tests/api.spec.ts | 300 |
18 files changed, 810 insertions, 174 deletions
diff --git a/docker/federation-test/run-tests.sh b/docker/federation-test/run-tests.sh index 4206f060..9d8a7e58 100755 --- a/docker/federation-test/run-tests.sh +++ b/docker/federation-test/run-tests.sh @@ -12,7 +12,8 @@ sudo docker-compose --file ../federation/docker-compose.yml --project-directory pushd ../../ui yarn echo "Waiting for Lemmy to start..." -while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 5; done +while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done +while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done yarn api-test || true popd diff --git a/server/src/api/comment.rs b/server/src/api/comment.rs index eb67d8f2..fddb42ab 100644 --- a/server/src/api/comment.rs +++ b/server/src/api/comment.rs @@ -87,7 +87,8 @@ impl Perform for Oper<CreateComment> { } // 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()); } @@ -101,6 +102,7 @@ impl Perform for Oper<CreateComment> { removed: None, deleted: None, read: None, + published: None, updated: None, ap_id: "changeme".into(), local: true, @@ -111,11 +113,13 @@ impl Perform for Oper<CreateComment> { Err(_e) => return Err(APIError::err("couldnt_create_comment").into()), }; - match Comment::update_ap_id(&conn, inserted_comment.id) { + let updated_comment = match Comment::update_ap_id(&conn, inserted_comment.id) { Ok(comment) => comment, Err(_e) => return Err(APIError::err("couldnt_create_comment").into()), }; + updated_comment.send_create(&user, &conn)?; + let mut recipient_ids = Vec::new(); // Scan the comment for user mentions, add those rows @@ -273,6 +277,8 @@ impl Perform for Oper<EditComment> { let conn = pool.get()?; + let user = User_::read(&conn, user_id)?; + let orig_comment = CommentView::read(&conn, data.edit_id, None)?; // You are allowed to mark the comment as read even if you're banned. @@ -297,7 +303,7 @@ impl Perform for Oper<EditComment> { } // Check for a site ban - if UserView::read(&conn, user_id)?.banned { + if user.banned { return Err(APIError::err("site_ban").into()); } } @@ -314,6 +320,7 @@ impl Perform for Oper<EditComment> { removed: data.removed.to_owned(), deleted: data.deleted.to_owned(), read: data.read.to_owned(), + published: None, updated: if data.read.is_some() { orig_comment.updated } else { @@ -323,11 +330,13 @@ impl Perform for Oper<EditComment> { local: read_comment.local, }; - let _updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) { + let updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) { Ok(comment) => comment, Err(_e) => return Err(APIError::err("couldnt_update_comment").into()), }; + updated_comment.send_update(&user, &conn)?; + let mut recipient_ids = Vec::new(); // Scan the comment for user mentions, add those rows diff --git a/server/src/api/mod.rs b/server/src/api/mod.rs index 0595f2a4..70ff2bfe 100644 --- a/server/src/api/mod.rs +++ b/server/src/api/mod.rs @@ -23,19 +23,17 @@ use crate::{ }; use crate::apub::{ - activities::{send_post_create, send_post_update}, fetcher::search_by_apub_id, signatures::generate_actor_keypair, - {make_apub_endpoint, ActorType, EndpointType}, + {make_apub_endpoint, ActorType, ApubObjectType, EndpointType}, }; use crate::settings::Settings; -use crate::websocket::UserOperation; use crate::websocket::{ server::{ JoinCommunityRoom, JoinPostRoom, JoinUserRoom, SendAllMessage, SendComment, SendCommunityRoomMessage, SendPost, SendUserRoomMessage, }, - WebsocketInfo, + UserOperation, WebsocketInfo, }; use diesel::r2d2::{ConnectionManager, Pool}; use diesel::PgConnection; diff --git a/server/src/api/post.rs b/server/src/api/post.rs index 89f1dd1d..5be227d8 100644 --- a/server/src/api/post.rs +++ b/server/src/api/post.rs @@ -160,7 +160,7 @@ impl Perform for Oper<CreatePost> { Err(_e) => return Err(APIError::err("couldnt_create_post").into()), }; - send_post_create(&updated_post, &user, &conn)?; + updated_post.send_create(&user, &conn)?; // They like their own post by default let like_form = PostLikeForm { @@ -531,7 +531,7 @@ impl Perform for Oper<EditPost> { ModStickyPost::create(&conn, &form)?; } - send_post_update(&updated_post, &user, &conn)?; + updated_post.send_update(&user, &conn)?; let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?; diff --git a/server/src/apub/activities.rs b/server/src/apub/activities.rs index cb98e973..517fd248 100644 --- a/server/src/apub/activities.rs +++ b/server/src/apub/activities.rs @@ -1,6 +1,6 @@ use super::*; -fn populate_object_props( +pub fn populate_object_props( props: &mut ObjectProperties, addressed_to: &str, object_id: &str, @@ -47,63 +47,3 @@ where } Ok(()) } - -/// For a given community, returns the inboxes of all followers. -fn get_follower_inboxes(conn: &PgConnection, community: &Community) -> Result<Vec<String>, Error> { - Ok( - CommunityFollowerView::for_community(conn, community.id)? - .into_iter() - .filter(|c| !c.user_local) - // TODO eventually this will have to use the inbox or shared_inbox column, meaning that view - // will have to change - .map(|c| format!("{}/inbox", c.user_actor_id.to_owned())) - .unique() - .collect(), - ) -} - -/// Send out information about a newly created post, to the followers of the community. -pub fn send_post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let page = post.to_apub(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, - &creator.private_key.as_ref().unwrap(), - &creator.actor_id, - get_follower_inboxes(conn, &community)?, - )?; - Ok(()) -} - -/// Send out information about an edited post, to the followers of the community. -pub fn send_post_update(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> { - let page = post.to_apub(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, - &creator.private_key.as_ref().unwrap(), - &creator.actor_id, - get_follower_inboxes(conn, &community)?, - )?; - Ok(()) -} diff --git a/server/src/apub/comment.rs b/server/src/apub/comment.rs new file mode 100644 index 00000000..3b7c0dfe --- /dev/null +++ b/server/src/apub/comment.rs @@ -0,0 +1,139 @@ +use super::*; + +impl ToApub for Comment { + type Response = Note; + + fn to_apub(&self, conn: &PgConnection) -> Result<Note, Error> { + let mut comment = Note::default(); + let oprops: &mut ObjectProperties = comment.as_mut(); + let creator = User_::read(&conn, self.creator_id)?; + let post = Post::read(&conn, self.post_id)?; + let community = Community::read(&conn, post.community_id)?; + + // Add a vector containing some important info to the "in_reply_to" field + // [post_ap_id, Option(parent_comment_ap_id)] + let mut in_reply_to_vec = vec![post.ap_id]; + + if let Some(parent_id) = self.parent_id { + let parent_comment = Comment::read(&conn, parent_id)?; + in_reply_to_vec.push(parent_comment.ap_id); + } + + oprops + // Not needed when the Post is embedded in a collection (like for community outbox) + .set_context_xsd_any_uri(context())? + .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_many_in_reply_to_xsd_any_uris(in_reply_to_vec)? + .set_content_xsd_string(self.content.to_owned())? + .set_attributed_to_xsd_any_uri(creator.actor_id)?; + + if let Some(u) = self.updated { + oprops.set_updated(convert_datetime(u))?; + } + + Ok(comment) + } +} + +impl FromApub for CommentForm { + type ApubType = Note; + + /// Parse an ActivityPub note received from another instance into a Lemmy comment + fn from_apub(note: &Note, conn: &PgConnection) -> Result<CommentForm, Error> { + let oprops = ¬e.object_props; + let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string(); + let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?; + + let mut in_reply_tos = oprops.get_many_in_reply_to_xsd_any_uris().unwrap(); + let post_ap_id = in_reply_tos.next().unwrap().to_string(); + + // The 2nd item, if it exists, is the parent comment apub_id + let parent_id: Option<i32> = match in_reply_tos.next() { + Some(parent_comment_uri) => { + let parent_comment_uri_str = &parent_comment_uri.to_string(); + let parent_comment = Comment::read_from_apub_id(&conn, &parent_comment_uri_str)?; + + Some(parent_comment.id) + } + None => None, + }; + + let post = Post::read_from_apub_id(&conn, &post_ap_id)?; + + Ok(CommentForm { + creator_id: creator.id, + post_id: post.id, + parent_id, + content: oprops + .get_content_xsd_string() + .map(|c| c.to_string()) + .unwrap(), + removed: None, + read: None, + published: oprops + .get_published() + .map(|u| u.as_ref().to_owned().naive_local()), + updated: oprops + .get_updated() + .map(|u| u.as_ref().to_owned().naive_local()), + deleted: None, + ap_id: oprops.get_id().unwrap().to_string(), + local: false, + }) + } +} + +impl ApubObjectType for Comment { + /// Send out information about a newly created comment, to the followers of the community. + fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { + let note = self.to_apub(conn)?; + let post = Post::read(&conn, self.post_id)?; + let community = Community::read(conn, post.community_id)?; + let mut create = Create::new(); + populate_object_props( + &mut create.object_props, + &community.get_followers_url(), + &self.ap_id, + )?; + create + .create_props + .set_actor_xsd_any_uri(creator.actor_id.to_owned())? + .set_object_base_box(note)?; + send_activity( + &create, + &creator.private_key.as_ref().unwrap(), + &creator.actor_id, + community.get_follower_inboxes(&conn)?, + )?; + Ok(()) + } + + /// Send out information about an edited post, to the followers of the community. + fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { + let note = self.to_apub(&conn)?; + let post = Post::read(&conn, self.post_id)?; + let community = Community::read(&conn, post.community_id)?; + let mut update = Update::new(); + populate_object_props( + &mut update.object_props, + &community.get_followers_url(), + &self.ap_id, + )?; + update + .update_props + .set_actor_xsd_any_uri(creator.actor_id.to_owned())? + .set_object_base_box(note)?; + send_activity( + &update, + &creator.private_key.as_ref().unwrap(), + &creator.actor_id, + community.get_follower_inboxes(&conn)?, + )?; + Ok(()) + } +} diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs index bc984b25..d66bbc01 100644 --- a/server/src/apub/community.rs +++ b/server/src/apub/community.rs @@ -89,6 +89,32 @@ impl ActorType for Community { )?; Ok(()) } + + /// For a given community, returns the inboxes of all followers. + fn get_follower_inboxes(&self, conn: &PgConnection) -> Result<Vec<String>, Error> { + debug!("got here."); + + Ok( + CommunityFollowerView::for_community(conn, self.id)? + .into_iter() + // TODO eventually this will have to use the inbox or shared_inbox column, meaning that view + // will have to change + .map(|c| { + // If the user is local, but the community isn't, get the community shared inbox + // and vice versa + if c.user_local && !c.community_local { + get_shared_inbox(&c.community_actor_id) + } else if !c.user_local && c.community_local { + get_shared_inbox(&c.user_actor_id) + } else { + "".to_string() + } + }) + .filter(|s| !s.is_empty()) + .unique() + .collect(), + ) + } } impl FromApub for CommunityForm { diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs index 5c585299..a861156f 100644 --- a/server/src/apub/mod.rs +++ b/server/src/apub/mod.rs @@ -1,4 +1,5 @@ pub mod activities; +pub mod comment; pub mod community; pub mod community_inbox; pub mod fetcher; @@ -15,7 +16,11 @@ use activitystreams::{ context, endpoint::EndpointProperties, ext::{Ext, Extensible, Extension}, - object::{properties::ObjectProperties, Page}, + object::{ + kind::{NoteType, PageType}, + properties::ObjectProperties, + Note, Page, + }, public, BaseBox, }; use actix_web::body::Body; @@ -38,7 +43,11 @@ use std::collections::BTreeMap; use std::time::Duration; use url::Url; +use crate::api::comment::CommentResponse; +use crate::api::post::PostResponse; use crate::api::site::SearchResponse; +use crate::db::comment::{Comment, CommentForm}; +use crate::db::comment_view::CommentView; use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm}; use crate::db::community_view::{CommunityFollowerView, CommunityView}; use crate::db::post::{Post, PostForm}; @@ -48,9 +57,13 @@ use crate::db::user_view::UserView; use crate::db::{Crud, Followable, SearchType}; use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown}; use crate::routes::{ChatServerParam, DbPoolParam}; +use crate::websocket::{ + server::{SendComment, SendPost}, + UserOperation, +}; use crate::{convert_datetime, naive_now, Settings}; -use activities::send_activity; +use activities::{populate_object_props, send_activity}; use fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user}; use signatures::verify; use signatures::{sign, PublicKey, PublicKeyExtension}; @@ -142,6 +155,25 @@ pub trait FromApub { Self: Sized; } +pub trait ApubObjectType { + fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; + fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>; +} + +pub fn get_shared_inbox(actor_id: &str) -> String { + let url = Url::parse(actor_id).unwrap(); + format!( + "{}://{}{}/inbox", + &url.scheme(), + &url.host_str().unwrap(), + if let Some(port) = url.port() { + format!(":{}", port) + } else { + "".to_string() + }, + ) +} + pub trait ActorType { fn actor_id(&self) -> String; @@ -159,24 +191,20 @@ pub trait ActorType { Ok(()) } + // 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![]) + } + // TODO move these to the db rows fn get_inbox_url(&self) -> String { format!("{}/inbox", &self.actor_id()) } fn get_shared_inbox_url(&self) -> String { - let url = Url::parse(&self.actor_id()).unwrap(); - let url_str = format!( - "{}://{}{}/inbox", - &url.scheme(), - &url.host_str().unwrap(), - if let Some(port) = url.port() { - format!(":{}", port) - } else { - "".to_string() - }, - ); - format!("{}/inbox", &url_str) + get_shared_inbox(&self.actor_id()) } fn get_outbox_url(&self) -> String { diff --git a/server/src/apub/post.rs b/server/src/apub/post.rs index 51ba861e..0a054431 100644 --- a/server/src/apub/post.rs +++ b/server/src/apub/post.rs @@ -92,3 +92,51 @@ impl FromApub for PostForm { }) } } + +impl ApubObjectType for Post { + /// Send out information about a newly created post, to the followers of the community. + fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { + let page = self.to_apub(conn)?; + let community = Community::read(conn, self.community_id)?; + let mut create = Create::new(); + populate_object_props( + &mut create.object_props, + &community.get_followers_url(), + &self.ap_id, + )?; + create + .create_props + .set_actor_xsd_any_uri(creator.actor_id.to_owned())? + .set_object_base_box(page)?; + send_activity( + &create, + &creator.private_key.as_ref().unwrap(), + &creator.actor_id, + community.get_follower_inboxes(&conn)?, + )?; + Ok(()) + } + + /// Send out information about an edited post, to the followers of the community. + fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> { + let page = self.to_apub(conn)?; + let community = Community::read(conn, self.community_id)?; + let mut update = Update::new(); + populate_object_props( + &mut update.object_props, + &community.get_followers_url(), + &self.ap_id, + )?; + update + .update_props + .set_actor_xsd_any_uri(creator.actor_id.to_owned())? + .set_object_base_box(page)?; + send_activity( + &update, + &creator.private_key.as_ref().unwrap(), + &creator.actor_id, + community.get_follower_inboxes(&conn)?, + )?; + Ok(()) + } +} diff --git a/server/src/apub/shared_inbox.rs b/server/src/apub/shared_inbox.rs index 35ba3908..f0cfc990 100644 --- a/server/src/apub/shared_inbox.rs +++ b/server/src/apub/shared_inbox.rs @@ -1 +1,241 @@ -// use super::*; +use super::*; + +#[serde(untagged)] +#[derive(Serialize, Deserialize, Debug)] +pub enum SharedAcceptedObjects { + Create(Create), + Update(Update), +} + +/// Handler for all incoming activities to user inboxes. +pub async fn shared_inbox( + request: HttpRequest, + input: web::Json<SharedAcceptedObjects>, + db: DbPoolParam, + chat_server: ChatServerParam, +) -> Result<HttpResponse, Error> { + // TODO: would be nice if we could do the signature check here, but we cant access the actor property + let input = input.into_inner(); + let conn = &db.get().unwrap(); + + let json = serde_json::to_string(&input)?; + debug!("Shared inbox received activity: {:?}", &json); + + match input { + SharedAcceptedObjects::Create(c) => handle_create(&c, &request, &conn, chat_server), + SharedAcceptedObjects::Update(u) => handle_update(&u, &request, &conn, chat_server), + } +} + +/// Handle create activities and insert them in the database. +fn handle_create( + create: &Create, + request: &HttpRequest, + conn: &PgConnection, + chat_server: ChatServerParam, +) -> Result<HttpResponse, Error> { + let base_box = create.create_props.get_object_base_box().unwrap(); + + if base_box.is_kind(PageType) { + let page = create + .create_props + .get_object_base_box() + .to_owned() + .unwrap() + .to_owned() + .to_concrete::<Page>()?; + receive_create_post(&create, &page, &request, &conn, chat_server)?; + } else if base_box.is_kind(NoteType) { + let note = create + .create_props + .get_object_base_box() + .to_owned() + .unwrap() + .to_owned() + .to_concrete::<Note>()?; + receive_create_comment(&create, ¬e, &request, &conn, chat_server)?; + } else { + return Err(format_err!("Unknown base box type")); + } + + Ok(HttpResponse::Ok().finish()) +} + +fn receive_create_post( + create: &Create, + page: &Page, + request: &HttpRequest, + conn: &PgConnection, + chat_server: ChatServerParam, +) -> Result<(), Error> { + let user_uri = create + .create_props + .get_actor_xsd_any_uri() + .unwrap() + .to_string(); + + let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + verify(request, &user.public_key.unwrap())?; + + let post = PostForm::from_apub(&page, &conn)?; + let inserted_post = Post::create(conn, &post)?; + + // Refetch the view + let post_view = PostView::read(&conn, inserted_post.id, None)?; + + let res = PostResponse { post: post_view }; + + chat_server.do_send(SendPost { + op: UserOperation::CreatePost, + post: res, + my_id: None, + }); + + Ok(()) +} + +fn receive_create_comment( + create: &Create, + note: &Note, + request: &HttpRequest, + conn: &PgConnection, + chat_server: ChatServerParam, +) -> Result<(), Error> { + let user_uri = create + .create_props + .get_actor_xsd_any_uri() + .unwrap() + .to_string(); + + let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + verify(request, &user.public_key.unwrap())?; + + let comment = CommentForm::from_apub(¬e, &conn)?; + let inserted_comment = Comment::create(conn, &comment)?; + + // Refetch the view + let comment_view = CommentView::read(&conn, inserted_comment.id, None)?; + + // TODO get those recipient actor ids from somewhere + let recipient_ids = vec![]; + let res = CommentResponse { + comment: comment_view, + recipient_ids, + }; + + chat_server.do_send(SendComment { + op: UserOperation::CreateComment, + comment: res, + my_id: None, + }); + + Ok(()) +} + +/// Handle create activities and insert them in the database. +fn handle_update( + update: &Update, + request: &HttpRequest, + conn: &PgConnection, + chat_server: ChatServerParam, +) -> Result<HttpResponse, Error> { + let base_box = update.update_props.get_object_base_box().unwrap(); + + if base_box.is_kind(PageType) { + let page = update + .update_props + .get_object_base_box() + .to_owned() + .unwrap() + .to_owned() + .to_concrete::<Page>()?; + + receive_update_post(&update, &page, &request, &conn, chat_server)?; + } else if base_box.is_kind(NoteType) { + let note = update + .update_props + .get_object_base_box() + .to_owned() + .unwrap() + .to_owned() + .to_concrete::<Note>()?; + receive_update_comment(&update, ¬e, &request, &conn, chat_server)?; + } else { + return Err(format_err!("Unknown base box type")); + } + + Ok(HttpResponse::Ok().finish()) +} + +fn receive_update_post( + update: &Update, + page: &Page, + request: &HttpRequest, + conn: &PgConnection, + chat_server: ChatServerParam, +) -> Result<(), Error> { + let user_uri = update + .update_props + .get_actor_xsd_any_uri() + .unwrap() + .to_string(); + + let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + verify(request, &user.public_key.unwrap())?; + + let post = PostForm::from_apub(&page, conn)?; + let post_id = Post::read_from_apub_id(conn, &post.ap_id)?.id; + Post::update(conn, post_id, &post)?; + + // Refetch the view + let post_view = PostView::read(&conn, post_id, None)?; + + let res = PostResponse { post: post_view }; + + chat_server.do_send(SendPost { + op: UserOperation::EditPost, + post: res, + my_id: None, + }); + + Ok(()) +} + +fn receive_update_comment( + update: &Update, + note: &Note, + request: &HttpRequest, + conn: &PgConnection, + chat_server: ChatServerParam, +) -> Result<(), Error> { + let user_uri = update + .update_props + .get_actor_xsd_any_uri() + .unwrap() + .to_string(); + + let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; + verify(request, &user.public_key.unwrap())?; + + let comment = CommentForm::from_apub(¬e, &conn)?; + let comment_id = Comment::read_from_apub_id(conn, &comment.ap_id)?.id; + Comment::update(conn, comment_id, &comment)?; + + // Refetch the view + let comment_view = CommentView::read(&conn, comment_id, None)?; + + // TODO get those recipient actor ids from somewhere + let recipient_ids = vec![]; + let res = CommentResponse { + comment: comment_view, + recipient_ids, + }; + + chat_server.do_send(SendComment { + op: UserOperation::EditComment, + comment: res, + my_id: None, + }); + + Ok(()) +} diff --git a/server/src/apub/user_inbox.rs b/server/src/apub/user_inbox.rs index 251a221c..7c00b5bb 100644 --- a/server/src/apub/user_inbox.rs +++ b/server/src/apub/user_inbox.rs @@ -3,8 +3,6 @@ use super::*; #[serde(untagged)] #[derive(Deserialize, Debug)] pub enum UserAcceptedObjects { - Create(Create), - Update(Update), Accept(Accept), } @@ -23,73 +21,10 @@ pub async fn user_inbox( debug!("User {} received activity: {:?}", &username, &input); match input { - UserAcceptedObjects::Create(c) => handle_create(&c, &request, &username, &conn), - UserAcceptedObjects::Update(u) => handle_update(&u, &request, &username, &conn), UserAcceptedObjects::Accept(a) => handle_accept(&a, &request, &username, &conn), } } -/// Handle create activities and insert them in the database. -fn handle_create( - create: &Create, - request: &HttpRequest, - _username: &str, - conn: &PgConnection, -) -> Result<HttpResponse, Error> { - // TODO before this even gets named, because we don't know what type of object it is, we need - // to parse this out - let user_uri = create - .create_props - .get_actor_xsd_any_uri() - .unwrap() - .to_string(); - - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; - verify(request, &user.public_key.unwrap())?; - - let page = create - .create_props - .get_object_base_box() - .to_owned() - .unwrap() - .to_owned() - .to_concrete::<Page>()?; - let post = PostForm::from_apub(&page, conn)?; - Post::create(conn, &post)?; - // TODO: send the new post out via websocket - Ok(HttpResponse::Ok().finish()) -} - -/// Handle update activities and insert them in the database. -fn handle_update( - update: &Update, - request: &HttpRequest, - _username: &str, - conn: &PgConnection, -) -> Result<HttpResponse, Error> { - let user_uri = update - .update_props - .get_actor_xsd_any_uri() - .unwrap() - .to_string(); - - let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?; - verify(request, &user.public_key.unwrap())?; - - let page = update - .update_props - .get_object_base_box() - .to_owned() - .unwrap() - .to_owned() - .to_concrete::<Page>()?; - let post = PostForm::from_apub(&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()) -} - /// Handle accepted follows. fn handle_accept( accept: &Accept, diff --git a/server/src/db/comment.rs b/server/src/db/comment.rs index 0b8a2e20..59c2ccd2 100644 --- a/server/src/db/comment.rs +++ b/server/src/db/comment.rs @@ -38,6 +38,7 @@ pub struct CommentForm { pub content: String, pub removed: Option<bool>, pub read: Option<bool>, + pub published: Option<chrono::NaiveDateTime>, pub updated: Option<chrono::NaiveDateTime>, pub deleted: Option<bool>, pub ap_id: String, @@ -84,6 +85,11 @@ impl Comment { .get_result::<Self>(conn) } + pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> { + use crate::schema::comment::dsl::*; + comment.filter(ap_id.eq(object_id)).first::<Self>(conn) + } + pub fn mark_as_read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> { use crate::schema::comment::dsl::*; @@ -283,6 +289,7 @@ mod tests { deleted: None, read: None, parent_id: None, + published: None, updated: None, ap_id: "changeme".into(), local: true, @@ -313,6 +320,7 @@ mod tests { removed: None, deleted: None, read: None, + published: None, updated: None, ap_id: "changeme".into(), local: true, |