mirror of https://github.com/LemmyNet/lemmy
Lots of additions to federation.
- Added a shared inbox. - Added federated comments, comment updates, and tests. - Abstracted ap object sends into a common trait.pull/722/head
parent
3ce0618362
commit
22abbebd41
@ -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(())
|
||||
}
|
||||
}
|
@ -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(())
|
||||
}
|
||||
|
Loading…
Reference in New Issue