From 34def84d4307d1f4d822091a35d94ab090c558af Mon Sep 17 00:00:00 2001 From: Lyra Date: Thu, 19 Dec 2019 22:59:13 +0100 Subject: [PATCH] Add correct ActivityPub types conversion for Community and Post. --- server/src/api/comment.rs | 2 +- server/src/apub.rs | 90 ---------------------------- server/src/apub/community.rs | 112 +++++++++++++++++++++++++++++++++++ server/src/apub/mod.rs | 100 +++++++++++++++++++++++++++++++ server/src/apub/post.rs | 38 ++++++++++++ server/src/apub/user.rs | 74 +++++++++++++++++++++++ server/src/lib.rs | 7 --- server/src/main.rs | 14 +++++ 8 files changed, 339 insertions(+), 98 deletions(-) delete mode 100644 server/src/apub.rs create mode 100644 server/src/apub/community.rs create mode 100644 server/src/apub/mod.rs create mode 100644 server/src/apub/post.rs create mode 100644 server/src/apub/user.rs diff --git a/server/src/api/comment.rs b/server/src/api/comment.rs index 9f9d46d30..9a057f806 100644 --- a/server/src/api/comment.rs +++ b/server/src/api/comment.rs @@ -328,7 +328,7 @@ impl Perform for Oper { CommentLike::remove(&conn, &like_form)?; // Only add the like if the score isnt 0 - let do_add = &like_form.score != &0 && (&like_form.score == &1 || &like_form.score == &-1); + let do_add = like_form.score != 0 && (like_form.score == 1 || like_form.score == -1); if do_add { let _inserted_like = match CommentLike::like(&conn, &like_form) { Ok(like) => like, diff --git a/server/src/apub.rs b/server/src/apub.rs deleted file mode 100644 index 66878d373..000000000 --- a/server/src/apub.rs +++ /dev/null @@ -1,90 +0,0 @@ -extern crate activitypub; -use self::activitypub::{actor::Person, context}; -use crate::db::user::User_; - -impl User_ { - pub fn person(&self) -> Person { - use crate::{to_datetime_utc, Settings}; - let base_url = &format!("{}/user/{}", Settings::get().api_endpoint(), self.name); - let mut person = Person::default(); - person.object_props.set_context_object(context()).ok(); - person.object_props.set_id_string(base_url.to_string()).ok(); - person - .object_props - .set_name_string(self.name.to_owned()) - .ok(); - person - .object_props - .set_published_utctime(to_datetime_utc(self.published)) - .ok(); - if let Some(i) = self.updated { - person - .object_props - .set_updated_utctime(to_datetime_utc(i)) - .ok(); - } - // person.object_props.summary = self.summary; - - person - .ap_actor_props - .set_inbox_string(format!("{}/inbox", &base_url)) - .ok(); - person - .ap_actor_props - .set_outbox_string(format!("{}/outbox", &base_url)) - .ok(); - person - .ap_actor_props - .set_following_string(format!("{}/following", &base_url)) - .ok(); - person - .ap_actor_props - .set_liked_string(format!("{}/liked", &base_url)) - .ok(); - if let Some(i) = &self.preferred_username { - person - .ap_actor_props - .set_preferred_username_string(i.to_string()) - .ok(); - } - - person - } -} - -#[cfg(test)] -mod tests { - use super::User_; - use crate::db::{ListingType, SortType}; - use crate::naive_now; - - #[test] - fn test_person() { - let expected_user = User_ { - id: 52, - name: "thom".into(), - fedi_name: "rrf".into(), - preferred_username: None, - password_encrypted: "here".into(), - email: None, - icon: None, - published: naive_now(), - admin: false, - banned: false, - updated: None, - show_nsfw: false, - theme: "darkly".into(), - default_sort_type: SortType::Hot as i16, - default_listing_type: ListingType::Subscribed as i16, - lang: "browser".into(), - }; - - let person = expected_user.person(); - assert_eq!( - "rrr/api/v1/user/thom", - person.object_props.id_string().unwrap() - ); - let json = serde_json::to_string_pretty(&person).unwrap(); - println!("{}", json); - } -} diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs new file mode 100644 index 000000000..3de68b967 --- /dev/null +++ b/server/src/apub/community.rs @@ -0,0 +1,112 @@ +use crate::apub::make_apub_endpoint; +use crate::db::community::Community; +use crate::db::community_view::CommunityFollowerView; +use crate::db::establish_connection; +use crate::to_datetime_utc; +use activitypub::{actor::Group, collection::UnorderedCollection, context}; +use actix_web::body::Body; +use actix_web::web::Path; +use actix_web::HttpResponse; +use serde::Deserialize; + +impl Community { + pub fn as_group(&self) -> Group { + let base_url = make_apub_endpoint("c", &self.name); + + let mut group = Group::default(); + + group.object_props.set_context_object(context()).ok(); + group.object_props.set_id_string(base_url.to_string()).ok(); + group + .object_props + .set_name_string(self.name.to_owned()) + .ok(); + group + .object_props + .set_published_utctime(to_datetime_utc(self.published)) + .ok(); + if let Some(updated) = self.updated { + group + .object_props + .set_updated_utctime(to_datetime_utc(updated)) + .ok(); + } + + if let Some(description) = &self.description { + group + .object_props + .set_summary_string(description.to_string()) + .ok(); + } + + group + .ap_actor_props + .set_inbox_string(format!("{}/inbox", &base_url)) + .ok(); + group + .ap_actor_props + .set_outbox_string(format!("{}/outbox", &base_url)) + .ok(); + group + .ap_actor_props + .set_followers_string(format!("{}/followers", &base_url)) + .ok(); + + group + } + + pub fn followers_as_collection(&self) -> UnorderedCollection { + let base_url = make_apub_endpoint("c", &self.name); + + let mut collection = UnorderedCollection::default(); + collection.object_props.set_context_object(context()).ok(); + collection + .object_props + .set_id_string(base_url.to_string()) + .ok(); + + let connection = establish_connection(); + //As we are an object, we validated that the community id was valid + let community_followers = CommunityFollowerView::for_community(&connection, self.id).unwrap(); + + let ap_followers = community_followers + .iter() + .map(|follower| make_apub_endpoint("u", &follower.user_name)) + .collect(); + + collection + .collection_props + .set_items_string_vec(ap_followers) + .unwrap(); + collection + } +} + +#[derive(Deserialize)] +pub struct CommunityQuery { + community_name: String, +} + +pub fn get_apub_community(info: Path) -> HttpResponse { + let connection = establish_connection(); + + if let Ok(community) = Community::read_from_name(&connection, info.community_name.to_owned()) { + HttpResponse::Ok() + .content_type("application/activity+json") + .body(serde_json::to_string(&community.as_group()).unwrap()) + } else { + HttpResponse::NotFound().finish() + } +} + +pub fn get_apub_community_followers(info: Path) -> HttpResponse { + let connection = establish_connection(); + + if let Ok(community) = Community::read_from_name(&connection, info.community_name.to_owned()) { + HttpResponse::Ok() + .content_type("application/activity+json") + .body(serde_json::to_string(&community.followers_as_collection()).unwrap()) + } else { + HttpResponse::NotFound().finish() + } +} diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs new file mode 100644 index 000000000..9b861f45f --- /dev/null +++ b/server/src/apub/mod.rs @@ -0,0 +1,100 @@ +pub mod community; +pub mod post; +pub mod user; +use crate::Settings; + +use std::fmt::Display; + +#[cfg(test)] +mod tests { + use crate::db::community::Community; + use crate::db::post::Post; + use crate::db::user::User_; + use crate::db::{ListingType, SortType}; + use crate::{naive_now, Settings}; + + #[test] + fn test_person() { + let user = User_ { + id: 52, + name: "thom".into(), + fedi_name: "rrf".into(), + preferred_username: None, + password_encrypted: "here".into(), + email: None, + icon: None, + published: naive_now(), + admin: false, + banned: false, + updated: None, + show_nsfw: false, + theme: "darkly".into(), + default_sort_type: SortType::Hot as i16, + default_listing_type: ListingType::Subscribed as i16, + lang: "browser".into(), + }; + + let person = user.as_person(); + assert_eq!( + format!("https://{}/federation/u/thom", Settings::get().hostname), + person.object_props.id_string().unwrap() + ); + } + + #[test] + fn test_community() { + let community = Community { + id: 42, + name: "Test".into(), + title: "Test Title".into(), + description: Some("Test community".into()), + category_id: 32, + creator_id: 52, + removed: false, + published: naive_now(), + updated: Some(naive_now()), + deleted: false, + nsfw: false, + }; + + let group = community.as_group(); + assert_eq!( + format!("https://{}/federation/c/Test", Settings::get().hostname), + group.object_props.id_string().unwrap() + ); + } + + #[test] + fn test_post() { + let post = Post { + id: 62, + name: "A test post".into(), + url: None, + body: None, + creator_id: 52, + community_id: 42, + published: naive_now(), + removed: false, + locked: false, + stickied: false, + nsfw: false, + deleted: false, + updated: None, + }; + + let page = post.as_page(); + assert_eq!( + format!("https://{}/federation/post/62", Settings::get().hostname), + page.object_props.id_string().unwrap() + ); + } +} + +pub fn make_apub_endpoint(point: S, value: T) -> String { + format!( + "https://{}/federation/{}/{}", + Settings::get().hostname, + point, + value + ) +} diff --git a/server/src/apub/post.rs b/server/src/apub/post.rs new file mode 100644 index 000000000..19163657b --- /dev/null +++ b/server/src/apub/post.rs @@ -0,0 +1,38 @@ +use crate::apub::make_apub_endpoint; +use crate::db::post::Post; +use crate::to_datetime_utc; +use activitypub::{context, object::Page}; + +impl Post { + pub fn as_page(&self) -> Page { + let base_url = make_apub_endpoint("post", self.id); + let mut page = Page::default(); + + page.object_props.set_context_object(context()).ok(); + page.object_props.set_id_string(base_url.to_string()).ok(); + page.object_props.set_name_string(self.name.to_owned()).ok(); + + if let Some(body) = &self.body { + page.object_props.set_content_string(body.to_owned()).ok(); + } + + if let Some(url) = &self.url { + page.object_props.set_url_string(url.to_owned()).ok(); + } + + //page.object_props.set_attributed_to_string + + page + .object_props + .set_published_utctime(to_datetime_utc(self.published)) + .ok(); + if let Some(updated) = self.updated { + page + .object_props + .set_updated_utctime(to_datetime_utc(updated)) + .ok(); + } + + page + } +} diff --git a/server/src/apub/user.rs b/server/src/apub/user.rs new file mode 100644 index 000000000..9de2c36c3 --- /dev/null +++ b/server/src/apub/user.rs @@ -0,0 +1,74 @@ +use crate::apub::make_apub_endpoint; +use crate::db::establish_connection; +use crate::db::user::User_; +use crate::to_datetime_utc; +use activitypub::{actor::Person, context}; +use actix_web::body::Body; +use actix_web::web::Path; +use actix_web::HttpResponse; +use serde::Deserialize; + +impl User_ { + pub fn as_person(&self) -> Person { + let base_url = make_apub_endpoint("u", &self.name); + let mut person = Person::default(); + person.object_props.set_context_object(context()).ok(); + person.object_props.set_id_string(base_url.to_string()).ok(); + person + .object_props + .set_name_string(self.name.to_owned()) + .ok(); + person + .object_props + .set_published_utctime(to_datetime_utc(self.published)) + .ok(); + if let Some(updated) = self.updated { + person + .object_props + .set_updated_utctime(to_datetime_utc(updated)) + .ok(); + } + + person + .ap_actor_props + .set_inbox_string(format!("{}/inbox", &base_url)) + .ok(); + person + .ap_actor_props + .set_outbox_string(format!("{}/outbox", &base_url)) + .ok(); + person + .ap_actor_props + .set_following_string(format!("{}/following", &base_url)) + .ok(); + person + .ap_actor_props + .set_liked_string(format!("{}/liked", &base_url)) + .ok(); + if let Some(i) = &self.preferred_username { + person + .ap_actor_props + .set_preferred_username_string(i.to_string()) + .ok(); + } + + person + } +} + +#[derive(Deserialize)] +pub struct UserQuery { + user_name: String, +} + +pub fn get_apub_user(info: Path) -> HttpResponse { + let connection = establish_connection(); + + if let Ok(user) = User_::find_by_email_or_username(&connection, &info.user_name) { + HttpResponse::Ok() + .content_type("application/activity+json") + .body(serde_json::to_string(&user.as_person()).unwrap()) + } else { + HttpResponse::NotFound().finish() + } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index c2fa2189d..b560edf4b 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -121,9 +121,6 @@ impl Settings { email_config, } } - fn api_endpoint(&self) -> String { - format!("{}/api/v1", self.hostname) - } } pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime { @@ -210,10 +207,6 @@ pub fn send_email( #[cfg(test)] mod tests { use crate::{extract_usernames, has_slurs, is_email_regex, remove_slurs, Settings}; - #[test] - fn test_api() { - assert_eq!(Settings::get().api_endpoint(), "rrr/api/v1"); - } #[test] fn test_email() { diff --git a/server/src/main.rs b/server/src/main.rs index 98ce8e3fd..d19ef777e 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -6,6 +6,7 @@ use actix::prelude::*; use actix_files::NamedFile; use actix_web::*; use actix_web_actors::ws; +use lemmy_server::apub; use lemmy_server::db::establish_connection; use lemmy_server::feeds; use lemmy_server::nodeinfo; @@ -243,6 +244,19 @@ fn main() { // RSS .route("/feeds/{type}/{name}.xml", web::get().to(feeds::get_feed)) .route("/feeds/all.xml", web::get().to(feeds::get_all_feed)) + // Federation + .route( + "/federation/c/{community_name}", + web::get().to(apub::community::get_apub_community), + ) + .route( + "/federation/c/{community_name}/followers", + web::get().to(apub::community::get_apub_community_followers), + ) + .route( + "/federation/u/{user_name}", + web::get().to(apub::user::get_apub_user), + ) }) .bind((settings.bind, settings.port)) .unwrap()