Remove boilerplate code

This commit is contained in:
Felix Ableitner 2020-03-16 18:30:25 +01:00
parent 8ebcc7ac02
commit 05735b31c0
6 changed files with 120 additions and 225 deletions

6
server/Cargo.lock generated vendored
View file

@ -2,7 +2,7 @@
# It is not intended for manual editing. # It is not intended for manual editing.
[[package]] [[package]]
name = "activitystreams" name = "activitystreams"
version = "0.5.0-alpha.4" version = "0.5.0-alpha.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [ dependencies = [
"activitystreams-derive 0.5.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)", "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" name = "lemmy_server"
version = "0.0.1" version = "0.0.1"
dependencies = [ 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 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-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)", "actix-rt 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -3282,7 +3282,7 @@ dependencies = [
] ]
[metadata] [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 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 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" "checksum actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380"

2
server/Cargo.toml vendored
View file

@ -9,7 +9,7 @@ diesel = { version = "1.4.2", features = ["postgres","chrono", "r2d2", "64-colum
diesel_migrations = "1.4.0" diesel_migrations = "1.4.0"
dotenv = "0.15.0" dotenv = "0.15.0"
bcrypt = "0.6.1" bcrypt = "0.6.1"
activitystreams = "0.5.0-alpha.4" activitystreams = "0.5.0-alpha.7"
chrono = { version = "0.4.7", features = ["serde"] } chrono = { version = "0.4.7", features = ["serde"] }
failure = "0.1.5" failure = "0.1.5"
serde_json = { version = "1.0.45", features = ["preserve_order"]} serde_json = { version = "1.0.45", features = ["preserve_order"]}

View file

@ -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::convert_datetime;
use crate::db::community::Community; use crate::db::community::Community;
use crate::db::community_view::CommunityFollowerView; use crate::db::community_view::CommunityFollowerView;
@ -12,134 +12,101 @@ use activitystreams::{
use actix_web::body::Body; use actix_web::body::Body;
use actix_web::web::Path; use actix_web::web::Path;
use actix_web::HttpResponse; use actix_web::HttpResponse;
use actix_web::{web, Result};
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection;
use failure::Error; use failure::Error;
use serde::Deserialize; use serde::Deserialize;
impl Community {
pub fn as_group(&self) -> Result<Group, Error> {
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<UnorderedCollection, Error> {
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<OrderedCollection, Error> {
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<PostView> = 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)] #[derive(Deserialize)]
pub struct CommunityQuery { pub struct CommunityQuery {
community_name: String, community_name: String,
} }
// TODO: move all this boilerplate code to routes::federation or such pub async fn get_apub_community(
pub async fn get_apub_community(info: Path<CommunityQuery>) -> Result<HttpResponse<Body>, Error> { info: Path<CommunityQuery>,
let connection = establish_unpooled_connection(); db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> Result<HttpResponse<Body>, 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()) { let mut group = Group::default();
Ok( let oprops: &mut ObjectProperties = group.as_mut();
HttpResponse::Ok()
.content_type("application/activity+json") oprops
.body(serde_json::to_string(&community.as_group()?).unwrap()), .set_context_xsd_any_uri(context())?
) .set_id(base_url.to_owned())?
} else { .set_name_xsd_string(community.title.to_owned())?
Ok(HttpResponse::NotFound().finish()) .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( pub async fn get_apub_community_followers(
info: Path<CommunityQuery>, info: Path<CommunityQuery>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> Result<HttpResponse<Body>, Error> { ) -> Result<HttpResponse<Body>, 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()) { let connection = establish_unpooled_connection();
Ok( //As we are an object, we validated that the community id was valid
HttpResponse::Ok() let community_followers =
.content_type("application/activity+json") CommunityFollowerView::for_community(&connection, community.id).unwrap();
.body(serde_json::to_string(&community.get_followers()?).unwrap()),
) let mut collection = UnorderedCollection::default();
} else { let oprops: &mut ObjectProperties = collection.as_mut();
Ok(HttpResponse::NotFound().finish()) 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( pub async fn get_apub_community_outbox(
info: Path<CommunityQuery>, info: Path<CommunityQuery>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> Result<HttpResponse<Body>, Error> { ) -> Result<HttpResponse<Body>, 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()) { let connection = establish_unpooled_connection();
Ok( //As we are an object, we validated that the community id was valid
HttpResponse::Ok() let community_posts: Vec<PostView> = PostQueryBuilder::create(&connection)
.content_type("application/activity+json") .for_community_id(community.id)
.body(serde_json::to_string(&community.get_outbox()?).unwrap()), .list()
) .unwrap();
} else {
Ok(HttpResponse::NotFound().finish()) 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)?))
} }

View file

@ -3,76 +3,21 @@ pub mod post;
pub mod puller; pub mod puller;
pub mod user; pub mod user;
use crate::Settings; use crate::Settings;
use failure::Error;
use actix_web::body::Body;
use actix_web::HttpResponse;
use std::fmt::Display; use std::fmt::Display;
use url::Url; use url::Url;
#[cfg(test)] fn create_apub_response(json_data: String) -> HttpResponse<Body> {
mod tests { HttpResponse::Ok()
use crate::db::community::Community; .content_type("application/activity+json")
use crate::db::user::User_; .body(json_data)
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()
);
}
} }
// TODO: this should take an enum community/user/post for `point` // TODO: this should take an enum community/user/post for `point`
// TODO: also not sure what exactly `value` should be (numeric id, name string, ...) // TODO: also not sure what exactly `value` should be (numeric id, name string, ...)
pub fn make_apub_endpoint<S: Display, T: Display>(point: S, value: T) -> Url { fn make_apub_endpoint<S: Display, T: Display>(point: S, value: T) -> Url {
Url::parse(&format!( Url::parse(&format!(
"{}://{}/federation/{}/{}", "{}://{}/federation/{}/{}",
get_apub_protocol_string(), get_apub_protocol_string(),
@ -83,13 +28,6 @@ pub fn make_apub_endpoint<S: Display, T: Display>(point: S, value: T) -> Url {
.unwrap() .unwrap()
} }
/// Parses an ID generated by `make_apub_endpoint()`. Will break when federating with anything fn get_apub_protocol_string() -> &'static str {
/// 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::<Vec<&str>>();
Ok((split[4], split[5]))
}
pub fn get_apub_protocol_string() -> &'static str {
"http" "http"
} }

View file

@ -44,6 +44,7 @@ where
{ {
// TODO: should cache responses here when we are in production // TODO: should cache responses here when we are in production
// TODO: this function should return a future // TODO: this function should return a future
// TODO: in production mode, fail if protocol is not https
let x: Response = reqwest::get(uri)?.json()?; let x: Response = reqwest::get(uri)?.json()?;
Ok(x) Ok(x)
} }

View file

@ -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::convert_datetime;
use crate::db::establish_unpooled_connection;
use crate::db::user::User_; use crate::db::user::User_;
use activitystreams::{actor::apub::Person, context, object::properties::ObjectProperties}; use activitystreams::{actor::apub::Person, context, object::properties::ObjectProperties};
use actix_web::body::Body; use actix_web::body::Body;
@ -8,52 +7,42 @@ use actix_web::web::Path;
use actix_web::HttpResponse; use actix_web::HttpResponse;
use failure::Error; use failure::Error;
use serde::Deserialize; use serde::Deserialize;
use diesel::r2d2::{ConnectionManager, Pool};
impl User_ { use diesel::PgConnection;
pub fn as_person(&self) -> Result<Person, Error> { use actix_web::{web, 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)
}
}
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct UserQuery { pub struct UserQuery {
user_name: String, user_name: String,
} }
pub async fn get_apub_user(info: Path<UserQuery>) -> Result<HttpResponse<Body>, Error> { pub async fn get_apub_user(
let connection = establish_unpooled_connection(); info: Path<UserQuery>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,) -> Result<HttpResponse<Body>, 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) { let mut person = Person::default();
Ok( let oprops: &mut ObjectProperties = person.as_mut();
HttpResponse::Ok() oprops
.content_type("application/activity+json") .set_context_xsd_any_uri(context())?
.body(serde_json::to_string(&user.as_person()?).unwrap()), .set_id(base_url.to_string())?
) .set_published(convert_datetime(user.published))?;
} else {
Ok(HttpResponse::NotFound().finish()) 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)?))
} }