diff options
author | Felix Ableitner <me@nutomic.com> | 2019-12-27 18:25:07 +0100 |
---|---|---|
committer | Felix Ableitner <me@nutomic.com> | 2019-12-30 13:31:54 +0100 |
commit | 581f36d6eff4e79139afd5049b4efb8b0ccc6e99 (patch) | |
tree | d65f7bdbf96fc2ae0f796726a8763216ee624a9e /server/src | |
parent | 47d55d9d2b69e7da49160675206b64a14ab75ebe (diff) |
Implementing very basic federation including test setup
Diffstat (limited to 'server/src')
-rw-r--r-- | server/src/api/community.rs | 16 | ||||
-rw-r--r-- | server/src/apub/mod.rs | 1 | ||||
-rw-r--r-- | server/src/apub/puller.rs | 97 | ||||
-rw-r--r-- | server/src/main.rs | 30 | ||||
-rw-r--r-- | server/src/settings.rs | 1 | ||||
-rw-r--r-- | server/src/webfinger.rs | 10 | ||||
-rw-r--r-- | server/src/websocket/server.rs | 34 |
7 files changed, 168 insertions, 21 deletions
diff --git a/server/src/api/community.rs b/server/src/api/community.rs index 5c97f088..014fa4c9 100644 --- a/server/src/api/community.rs +++ b/server/src/api/community.rs @@ -4,16 +4,16 @@ use std::str::FromStr; #[derive(Serialize, Deserialize)] pub struct GetCommunity { id: Option<i32>, - name: Option<String>, + pub name: Option<String>, auth: Option<String>, } #[derive(Serialize, Deserialize)] pub struct GetCommunityResponse { - op: String, - community: CommunityView, - moderators: Vec<CommunityModeratorView>, - admins: Vec<UserView>, + pub op: String, + pub community: CommunityView, + pub moderators: Vec<CommunityModeratorView>, + pub admins: Vec<UserView>, } #[derive(Serialize, Deserialize)] @@ -40,10 +40,10 @@ pub struct ListCommunities { auth: Option<String>, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] pub struct ListCommunitiesResponse { - op: String, - communities: Vec<CommunityView>, + pub op: String, + pub communities: Vec<CommunityView>, } #[derive(Serialize, Deserialize, Clone)] diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs index b1a01b6d..1e959232 100644 --- a/server/src/apub/mod.rs +++ b/server/src/apub/mod.rs @@ -1,5 +1,6 @@ pub mod community; pub mod post; +pub mod puller; pub mod user; use crate::Settings; diff --git a/server/src/apub/puller.rs b/server/src/apub/puller.rs new file mode 100644 index 00000000..efca6c7b --- /dev/null +++ b/server/src/apub/puller.rs @@ -0,0 +1,97 @@ +extern crate reqwest; + +use self::reqwest::Error; +use crate::api::community::{GetCommunityResponse, ListCommunitiesResponse}; +use crate::api::post::GetPosts; +use crate::api::UserOperation; +use crate::db::community_view::CommunityView; +use crate::naive_now; +use crate::settings::Settings; +use activitypub::actor::Group; + +// TODO: right now all of the data is requested on demand, for production we will need to store +// things in the local database to not ruin the performance + +fn fetch_communities_from_instance(domain: &str) -> Result<Vec<CommunityView>, Error> { + // TODO: check nodeinfo to make sure we are dealing with a lemmy instance + // -> means we need proper nodeinfo json classes instead of inline generation + // TODO: follow pagination (seems like page count is missing?) + // TODO: see if there is any standard for discovering remote actors, so we dont have to rely on lemmy apis + let communities_uri = format!("http://{}/api/v1/communities/list?sort=Hot", domain); + let communities1: ListCommunitiesResponse = reqwest::get(&communities_uri)?.json()?; + let mut communities2 = communities1.communities.to_owned(); + for c in &mut communities2 { + c.name = format_community_name(&c.name, domain); + } + Ok(communities2) +} + +pub fn get_remote_community_posts(name: String) -> Result<GetPosts, Error> { + // TODO: this is for urls like /c/!main@example.com, activitypub exposes it through the outbox + // https://www.w3.org/TR/activitypub/#outbox + dbg!(name); + unimplemented!() +} + +pub fn get_remote_community(identifier: String) -> Result<GetCommunityResponse, Error> { + let x: Vec<&str> = identifier.split("@").collect(); + let name = x[0].replace("!", ""); + let instance = x[1]; + let community_uri = format!("http://{}/federation/c/{}", instance, name); + let community: Group = reqwest::get(&community_uri)?.json()?; + + // TODO: looks like a bunch of data is missing from the activitypub response + // TODO: i dont think simple numeric ids are going to work, we probably need something like uuids + // TODO: why are the Group properties not typed? + Ok(GetCommunityResponse { + op: UserOperation::GetCommunity.to_string(), + moderators: vec![], + admins: vec![], + community: CommunityView { + id: -1, + name: identifier.clone(), + title: identifier, + description: community.object_props.summary.map(|c| c.to_string()), + category_id: -1, + creator_id: -1, + removed: false, + published: naive_now(), // TODO: community.object_props.published + updated: Some(naive_now()), // TODO: community.object_props.updated + deleted: false, + nsfw: false, + creator_name: "".to_string(), + category_name: "".to_string(), + number_of_subscribers: -1, + number_of_posts: -1, + number_of_comments: -1, + hot_rank: -1, + user_id: None, + subscribed: None, + }, + }) +} + +pub fn get_following_instances() -> Result<Vec<String>, Error> { + let instance_list = match Settings::get().federated_instance.clone() { + Some(f) => vec![f, Settings::get().hostname.clone()], + None => vec![Settings::get().hostname.clone()], + }; + Ok(instance_list) +} + +pub fn get_all_communities() -> Result<Vec<CommunityView>, Error> { + let mut communities_list: Vec<CommunityView> = vec![]; + for instance in &get_following_instances()? { + communities_list.append(fetch_communities_from_instance(instance)?.as_mut()); + } + Ok(communities_list) +} + +/// If community is on local instance, don't include the @instance part +pub fn format_community_name(name: &str, instance: &str) -> String { + if instance == Settings::get().hostname { + format!("!{}", name) + } else { + format!("!{}@{}", name, instance) + } +} diff --git a/server/src/main.rs b/server/src/main.rs index 52c395d3..d7002359 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -4,8 +4,13 @@ extern crate diesel_migrations; use actix::prelude::*; use actix_files::NamedFile; +use actix_web::web::Query; use actix_web::*; use actix_web_actors::ws; +use lemmy_server::api::community::ListCommunities; +use lemmy_server::api::Oper; +use lemmy_server::api::Perform; +use lemmy_server::api::UserOperation; use lemmy_server::apub; use lemmy_server::db::establish_connection; use lemmy_server::feeds; @@ -263,11 +268,25 @@ fn main() { .route("/feeds/all.xml", web::get().to(feeds::get_all_feed)); // Federation - if settings.federation_enabled { - app.route( - ".well-known/webfinger", - web::get().to(webfinger::get_webfinger_response), - ) + if Settings::get().federation_enabled { + println!("federation enabled, host is {}", Settings::get().hostname); + app + .route( + ".well-known/webfinger", + web::get().to(webfinger::get_webfinger_response), + ) + // TODO: this is a very quick and dirty implementation for http api calls + .route( + "/api/v1/communities/list", + web::get().to(|query: Query<ListCommunities>| { + let res = Oper::new(UserOperation::ListCommunities, query.into_inner()) + .perform() + .unwrap(); + HttpResponse::Ok() + .content_type("application/json") + .body(serde_json::to_string(&res).unwrap()) + }), + ) } else { app } @@ -277,6 +296,7 @@ fn main() { .start(); println!("Started http server at {}:{}", settings.bind, settings.port); + let _ = sys.run(); } diff --git a/server/src/settings.rs b/server/src/settings.rs index 6cb4de0b..2f635b93 100644 --- a/server/src/settings.rs +++ b/server/src/settings.rs @@ -18,6 +18,7 @@ pub struct Settings { pub rate_limit: RateLimitConfig, pub email: Option<EmailConfig>, pub federation_enabled: bool, + pub federated_instance: Option<String>, } #[derive(Debug, Deserialize)] diff --git a/server/src/webfinger.rs b/server/src/webfinger.rs index 55894745..03d2fafb 100644 --- a/server/src/webfinger.rs +++ b/server/src/webfinger.rs @@ -48,7 +48,13 @@ pub fn get_webfinger_response(info: Query<Params>) -> HttpResponse<Body> { Err(_) => return HttpResponse::NotFound().finish(), }; - let community_url = community.get_url(); + // TODO: might want to move this into community or apub + // TODO: should use http during local testing, and https during production + let community_url = format!( + "http://{}/federation/c/{}", + Settings::get().hostname, + community.name + ); let json = json!({ "subject": info.resource, @@ -59,7 +65,7 @@ pub fn get_webfinger_response(info: Query<Params>) -> HttpResponse<Body> { { "rel": "http://webfinger.net/rel/profile-page", "type": "text/html", - "href": community_url + "href": community.get_url(), }, { "rel": "self", diff --git a/server/src/websocket/server.rs b/server/src/websocket/server.rs index 56fff275..344324cb 100644 --- a/server/src/websocket/server.rs +++ b/server/src/websocket/server.rs @@ -17,6 +17,7 @@ use crate::api::post::*; use crate::api::site::*; use crate::api::user::*; use crate::api::*; +use crate::apub::puller::*; use crate::Settings; /// Chat server sends this messages to session @@ -352,14 +353,35 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str Ok(serde_json::to_string(&res)?) } UserOperation::GetCommunity => { - let get_community: GetCommunity = serde_json::from_str(data)?; - let res = Oper::new(user_operation, get_community).perform()?; - Ok(serde_json::to_string(&res)?) + let mut get_community: GetCommunity = serde_json::from_str(data)?; + if Settings::get().federation_enabled && get_community.name.is_some() { + let name = &get_community.name.unwrap(); + let remote_community = if name.contains("@") { + // TODO: need to support sort, filter etc for remote communities + get_remote_community(name.to_owned())? + } else { + get_community.name = Some(name.replace("!", "")); + Oper::new(user_operation, get_community).perform()? + }; + Ok(serde_json::to_string(&remote_community)?) + } else { + let res = Oper::new(user_operation, get_community).perform()?; + Ok(serde_json::to_string(&res)?) + } } UserOperation::ListCommunities => { - let list_communities: ListCommunities = serde_json::from_str(data)?; - let res = Oper::new(user_operation, list_communities).perform()?; - Ok(serde_json::to_string(&res)?) + if Settings::get().federation_enabled { + let res = get_all_communities()?; + let val = ListCommunitiesResponse { + op: UserOperation::ListCommunities.to_string(), + communities: res, + }; + Ok(serde_json::to_string(&val)?) + } else { + let list_communities: ListCommunities = serde_json::from_str(data)?; + let res = Oper::new(user_operation, list_communities).perform()?; + Ok(serde_json::to_string(&res)?) + } } UserOperation::CreateCommunity => { chat.check_rate_limit_register(msg.id)?; |