summaryrefslogtreecommitdiffstats
path: root/server/src
diff options
context:
space:
mode:
authorFelix Ableitner <me@nutomic.com>2019-12-27 18:25:07 +0100
committerFelix Ableitner <me@nutomic.com>2019-12-30 13:31:54 +0100
commit581f36d6eff4e79139afd5049b4efb8b0ccc6e99 (patch)
treed65f7bdbf96fc2ae0f796726a8763216ee624a9e /server/src
parent47d55d9d2b69e7da49160675206b64a14ab75ebe (diff)
Implementing very basic federation including test setup
Diffstat (limited to 'server/src')
-rw-r--r--server/src/api/community.rs16
-rw-r--r--server/src/apub/mod.rs1
-rw-r--r--server/src/apub/puller.rs97
-rw-r--r--server/src/main.rs30
-rw-r--r--server/src/settings.rs1
-rw-r--r--server/src/webfinger.rs10
-rw-r--r--server/src/websocket/server.rs34
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)?;