diff --git a/server/Cargo.lock b/server/Cargo.lock index a645d56e..db4b0fbb 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -2,7 +2,7 @@ # It is not intended for manual editing. [[package]] name = "activitystreams" -version = "0.5.0-alpha.4" +version = "0.5.0-alpha.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "activitystreams-derive 0.5.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1562,7 +1562,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "lemmy_server" version = "0.0.1" dependencies = [ - "activitystreams 0.5.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)", + "activitystreams 0.5.0-alpha.7 (registry+https://github.com/rust-lang/crates.io-index)", "actix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "actix-files 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "actix-rt 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3282,7 +3282,7 @@ dependencies = [ ] [metadata] -"checksum activitystreams 0.5.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1f5d1db7f182bc74c9a6d2002cb7a5eb99b001ef41ddc8df1c21750ccc3638fa" +"checksum activitystreams 0.5.0-alpha.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a9e82b6649331396e8bd17547a3b775ba7f530a30d574d43cf1d373899dafd94" "checksum activitystreams-derive 0.5.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f95c948a832a0b7b230b28369bafe79477bb8ebe7306dc97bcaff43832d3cc4d" "checksum actix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4af87564ff659dee8f9981540cac9418c45e910c8072fdedd643a262a38fcaf" "checksum actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380" diff --git a/server/Cargo.toml b/server/Cargo.toml index 6aa9adc2..e039b7b5 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -9,7 +9,7 @@ diesel = { version = "1.4.2", features = ["postgres","chrono", "r2d2", "64-colum diesel_migrations = "1.4.0" dotenv = "0.15.0" bcrypt = "0.6.1" -activitystreams = "0.5.0-alpha.4" +activitystreams = "0.5.0-alpha.7" chrono = { version = "0.4.7", features = ["serde"] } failure = "0.1.5" serde_json = { version = "1.0.45", features = ["preserve_order"]} diff --git a/server/src/apub/community.rs b/server/src/apub/community.rs index 61b0b2ce..8c0fb160 100644 --- a/server/src/apub/community.rs +++ b/server/src/apub/community.rs @@ -1,4 +1,4 @@ -use crate::apub::make_apub_endpoint; +use crate::apub::{create_apub_response, make_apub_endpoint}; use crate::convert_datetime; use crate::db::community::Community; use crate::db::community_view::CommunityFollowerView; @@ -12,134 +12,101 @@ use activitystreams::{ use actix_web::body::Body; use actix_web::web::Path; use actix_web::HttpResponse; +use actix_web::{web, Result}; +use diesel::r2d2::{ConnectionManager, Pool}; +use diesel::PgConnection; use failure::Error; use serde::Deserialize; -impl Community { - pub fn as_group(&self) -> Result { - let base_url = make_apub_endpoint("c", &self.name); - - let mut group = Group::default(); - let oprops: &mut ObjectProperties = group.as_mut(); - - oprops - .set_context_xsd_any_uri(context())? - .set_id(base_url.to_owned())? - .set_name_xsd_string(self.title.to_owned())? - .set_published(convert_datetime(self.published))? - .set_attributed_to_xsd_any_uri(make_apub_endpoint("u", &self.creator_id))?; - - if let Some(u) = self.updated.to_owned() { - oprops.set_updated(convert_datetime(u))?; - } - if let Some(d) = self.description.to_owned() { - oprops.set_summary_xsd_string(d)?; - } - - group - .ap_actor_props - .set_inbox(format!("{}/inbox", &base_url))? - .set_outbox(format!("{}/outbox", &base_url))? - .set_followers(format!("{}/followers", &base_url))?; - - Ok(group) - } - - pub fn get_followers(&self) -> Result { - let base_url = make_apub_endpoint("c", &self.name); - - let connection = establish_unpooled_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 mut collection = UnorderedCollection::default(); - let oprops: &mut ObjectProperties = collection.as_mut(); - oprops - .set_context_xsd_any_uri(context())? - .set_id(base_url)?; - collection - .collection_props - .set_total_items(community_followers.len() as u64)?; - Ok(collection) - } - - pub fn get_outbox(&self) -> Result { - let base_url = make_apub_endpoint("c", &self.name); - - let connection = establish_unpooled_connection(); - //As we are an object, we validated that the community id was valid - let community_posts: Vec = PostQueryBuilder::create(&connection) - .for_community_id(self.id) - .list() - .unwrap(); - - let mut collection = OrderedCollection::default(); - let oprops: &mut ObjectProperties = collection.as_mut(); - oprops - .set_context_xsd_any_uri(context())? - .set_id(base_url)?; - collection - .collection_props - .set_many_items_object_boxs( - community_posts - .iter() - .map(|c| c.as_page().unwrap()) - .collect(), - )? - .set_total_items(community_posts.len() as u64)?; - - Ok(collection) - } -} - #[derive(Deserialize)] pub struct CommunityQuery { community_name: String, } -// TODO: move all this boilerplate code to routes::federation or such -pub async fn get_apub_community(info: Path) -> Result, Error> { - let connection = establish_unpooled_connection(); +pub async fn get_apub_community( + info: Path, + db: web::Data>>, +) -> Result, Error> { + let community = Community::read_from_name(&&db.get()?, info.community_name.to_owned())?; + let base_url = make_apub_endpoint("c", &community.name); - if let Ok(community) = Community::read_from_name(&connection, info.community_name.to_owned()) { - Ok( - HttpResponse::Ok() - .content_type("application/activity+json") - .body(serde_json::to_string(&community.as_group()?).unwrap()), - ) - } else { - Ok(HttpResponse::NotFound().finish()) + let mut group = Group::default(); + let oprops: &mut ObjectProperties = group.as_mut(); + + oprops + .set_context_xsd_any_uri(context())? + .set_id(base_url.to_owned())? + .set_name_xsd_string(community.title.to_owned())? + .set_published(convert_datetime(community.published))? + .set_attributed_to_xsd_any_uri(make_apub_endpoint("u", &community.creator_id))?; + + if let Some(u) = community.updated.to_owned() { + oprops.set_updated(convert_datetime(u))?; } + if let Some(d) = community.description { + oprops.set_summary_xsd_string(d)?; + } + + group + .ap_actor_props + .set_inbox(format!("{}/inbox", &base_url))? + .set_outbox(format!("{}/outbox", &base_url))? + .set_followers(format!("{}/followers", &base_url))?; + + Ok(create_apub_response(serde_json::to_string(&group)?)) } pub async fn get_apub_community_followers( info: Path, + db: web::Data>>, ) -> Result, Error> { - let connection = establish_unpooled_connection(); + let community = Community::read_from_name(&&db.get()?, info.community_name.to_owned())?; + let base_url = make_apub_endpoint("c", &community.name); - if let Ok(community) = Community::read_from_name(&connection, info.community_name.to_owned()) { - Ok( - HttpResponse::Ok() - .content_type("application/activity+json") - .body(serde_json::to_string(&community.get_followers()?).unwrap()), - ) - } else { - Ok(HttpResponse::NotFound().finish()) - } + let connection = establish_unpooled_connection(); + //As we are an object, we validated that the community id was valid + let community_followers = + CommunityFollowerView::for_community(&connection, community.id).unwrap(); + + let mut collection = UnorderedCollection::default(); + let oprops: &mut ObjectProperties = collection.as_mut(); + oprops + .set_context_xsd_any_uri(context())? + .set_id(base_url)?; + collection + .collection_props + .set_total_items(community_followers.len() as u64)?; + Ok(create_apub_response(serde_json::to_string(&collection)?)) } pub async fn get_apub_community_outbox( info: Path, + db: web::Data>>, ) -> Result, Error> { - let connection = establish_unpooled_connection(); + let community = Community::read_from_name(&&db.get()?, info.community_name.to_owned())?; + let base_url = make_apub_endpoint("c", &community.name); - if let Ok(community) = Community::read_from_name(&connection, info.community_name.to_owned()) { - Ok( - HttpResponse::Ok() - .content_type("application/activity+json") - .body(serde_json::to_string(&community.get_outbox()?).unwrap()), - ) - } else { - Ok(HttpResponse::NotFound().finish()) - } + let connection = establish_unpooled_connection(); + //As we are an object, we validated that the community id was valid + let community_posts: Vec = PostQueryBuilder::create(&connection) + .for_community_id(community.id) + .list() + .unwrap(); + + let mut collection = OrderedCollection::default(); + let oprops: &mut ObjectProperties = collection.as_mut(); + oprops + .set_context_xsd_any_uri(context())? + .set_id(base_url)?; + collection + .collection_props + .set_many_items_object_boxs( + community_posts + .iter() + .map(|c| c.as_page().unwrap()) + .collect(), + )? + .set_total_items(community_posts.len() as u64)?; + + Ok(create_apub_response(serde_json::to_string(&collection)?)) } diff --git a/server/src/apub/mod.rs b/server/src/apub/mod.rs index 6ce59992..d648e902 100644 --- a/server/src/apub/mod.rs +++ b/server/src/apub/mod.rs @@ -3,76 +3,21 @@ pub mod post; pub mod puller; pub mod user; use crate::Settings; -use failure::Error; +use actix_web::body::Body; +use actix_web::HttpResponse; use std::fmt::Display; use url::Url; -#[cfg(test)] -mod tests { - use crate::db::community::Community; - 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, - matrix_user_id: None, - avatar: 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(), - show_avatars: true, - send_notifications_to_email: false, - }; - - let person = user.as_person(); - assert_eq!( - format!("https://{}/federation/u/thom", Settings::get().hostname), - person.unwrap().object_props.get_id().unwrap().to_string() - ); - } - - #[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.unwrap().object_props.get_id().unwrap().to_string() - ); - } +fn create_apub_response(json_data: String) -> HttpResponse { + HttpResponse::Ok() + .content_type("application/activity+json") + .body(json_data) } // TODO: this should take an enum community/user/post for `point` // TODO: also not sure what exactly `value` should be (numeric id, name string, ...) -pub fn make_apub_endpoint(point: S, value: T) -> Url { +fn make_apub_endpoint(point: S, value: T) -> Url { Url::parse(&format!( "{}://{}/federation/{}/{}", get_apub_protocol_string(), @@ -83,13 +28,6 @@ pub fn make_apub_endpoint(point: S, value: T) -> Url { .unwrap() } -/// Parses an ID generated by `make_apub_endpoint()`. Will break when federating with anything -/// that is not Lemmy. This is just a crutch until we change the database to store URLs as ID. -pub fn parse_apub_endpoint(id: &str) -> Result<(&str, &str), Error> { - let split = id.split('/').collect::>(); - Ok((split[4], split[5])) -} - -pub fn get_apub_protocol_string() -> &'static str { +fn get_apub_protocol_string() -> &'static str { "http" } diff --git a/server/src/apub/puller.rs b/server/src/apub/puller.rs index 50a6cd69..7ae0696e 100644 --- a/server/src/apub/puller.rs +++ b/server/src/apub/puller.rs @@ -44,6 +44,7 @@ where { // TODO: should cache responses here when we are in production // TODO: this function should return a future + // TODO: in production mode, fail if protocol is not https let x: Response = reqwest::get(uri)?.json()?; Ok(x) } diff --git a/server/src/apub/user.rs b/server/src/apub/user.rs index c1ffcedd..005ca9de 100644 --- a/server/src/apub/user.rs +++ b/server/src/apub/user.rs @@ -1,6 +1,5 @@ -use crate::apub::make_apub_endpoint; +use crate::apub::{make_apub_endpoint, create_apub_response}; use crate::convert_datetime; -use crate::db::establish_unpooled_connection; use crate::db::user::User_; use activitystreams::{actor::apub::Person, context, object::properties::ObjectProperties}; use actix_web::body::Body; @@ -8,52 +7,42 @@ use actix_web::web::Path; use actix_web::HttpResponse; use failure::Error; use serde::Deserialize; - -impl User_ { - pub fn as_person(&self) -> Result { - let base_url = make_apub_endpoint("u", &self.name); - - let mut person = Person::default(); - let oprops: &mut ObjectProperties = person.as_mut(); - oprops - .set_context_xsd_any_uri(context())? - .set_id(base_url.to_string())? - .set_published(convert_datetime(self.published))?; - - if let Some(u) = self.updated { - oprops.set_updated(convert_datetime(u))?; - } - - if let Some(i) = &self.preferred_username { - oprops.set_name_xsd_string(i.to_owned())?; - } - - person - .ap_actor_props - .set_inbox(format!("{}/inbox", &base_url))? - .set_outbox(format!("{}/outbox", &base_url))? - .set_following(format!("{}/following", &base_url))? - .set_liked(format!("{}/liked", &base_url))?; - - Ok(person) - } -} +use diesel::r2d2::{ConnectionManager, Pool}; +use diesel::PgConnection; +use actix_web::{web, Result}; #[derive(Deserialize)] pub struct UserQuery { user_name: String, } -pub async fn get_apub_user(info: Path) -> Result, Error> { - let connection = establish_unpooled_connection(); +pub async fn get_apub_user( + info: Path, + db: web::Data>>,) -> Result, Error> { + let user = User_::find_by_email_or_username(&&db.get()?, &info.user_name)?; + let base_url = make_apub_endpoint("u", &user.name); - if let Ok(user) = User_::find_by_email_or_username(&connection, &info.user_name) { - Ok( - HttpResponse::Ok() - .content_type("application/activity+json") - .body(serde_json::to_string(&user.as_person()?).unwrap()), - ) - } else { - Ok(HttpResponse::NotFound().finish()) + let mut person = Person::default(); + let oprops: &mut ObjectProperties = person.as_mut(); + oprops + .set_context_xsd_any_uri(context())? + .set_id(base_url.to_string())? + .set_published(convert_datetime(user.published))?; + + if let Some(u) = user.updated { + oprops.set_updated(convert_datetime(u))?; } + + if let Some(i) = &user.preferred_username { + oprops.set_name_xsd_string(i.to_owned())?; + } + + person + .ap_actor_props + .set_inbox(format!("{}/inbox", &base_url))? + .set_outbox(format!("{}/outbox", &base_url))? + .set_following(format!("{}/following", &base_url))? + .set_liked(format!("{}/liked", &base_url))?; + + Ok(create_apub_response(serde_json::to_string(&person)?)) }