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.
[[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"

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"
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"]}

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::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<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)]
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<CommunityQuery>) -> Result<HttpResponse<Body>, Error> {
let connection = establish_unpooled_connection();
pub async fn get_apub_community(
info: Path<CommunityQuery>,
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()) {
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<CommunityQuery>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> 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()) {
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<CommunityQuery>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> 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()) {
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<PostView> = 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)?))
}

View File

@ -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<Body> {
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<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!(
"{}://{}/federation/{}/{}",
get_apub_protocol_string(),
@ -83,13 +28,6 @@ pub fn make_apub_endpoint<S: Display, T: Display>(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::<Vec<&str>>();
Ok((split[4], split[5]))
}
pub fn get_apub_protocol_string() -> &'static str {
fn get_apub_protocol_string() -> &'static str {
"http"
}

View File

@ -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)
}

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::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<Person, Error> {
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<UserQuery>) -> Result<HttpResponse<Body>, Error> {
let connection = establish_unpooled_connection();
pub async fn get_apub_user(
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) {
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)?))
}