Added documentation for most functions
This commit is contained in:
parent
8908c8b184
commit
c5ced6fa5e
9 changed files with 62 additions and 53 deletions
|
@ -30,6 +30,7 @@ fn populate_object_props(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Send an activity to a list of recipients, using the correct headers etc.
|
||||
fn send_activity<A>(activity: &A, to: Vec<String>) -> Result<(), Error>
|
||||
where
|
||||
A: Serialize + Debug,
|
||||
|
@ -47,7 +48,8 @@ where
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn get_followers(conn: &PgConnection, community: &Community) -> Result<Vec<String>, Error> {
|
||||
/// For a given community, returns the inboxes of all followers.
|
||||
fn get_follower_inboxes(conn: &PgConnection, community: &Community) -> Result<Vec<String>, Error> {
|
||||
Ok(
|
||||
CommunityFollowerView::for_community(conn, community.id)?
|
||||
.iter()
|
||||
|
@ -57,6 +59,7 @@ fn get_followers(conn: &PgConnection, community: &Community) -> Result<Vec<Strin
|
|||
)
|
||||
}
|
||||
|
||||
/// Send out information about a newly created post, to the followers of the community.
|
||||
pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||
let page = post.as_page(conn)?;
|
||||
let community = Community::read(conn, post.community_id)?;
|
||||
|
@ -70,10 +73,11 @@ pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result<
|
|||
.create_props
|
||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||
.set_object_base_box(page)?;
|
||||
send_activity(&create, get_followers(conn, &community)?)?;
|
||||
send_activity(&create, get_follower_inboxes(conn, &community)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Send out information about an edited post, to the followers of the community.
|
||||
pub fn post_update(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
|
||||
let page = post.as_page(conn)?;
|
||||
let community = Community::read(conn, post.community_id)?;
|
||||
|
@ -87,10 +91,11 @@ pub fn post_update(post: &Post, creator: &User_, conn: &PgConnection) -> Result<
|
|||
.update_props
|
||||
.set_actor_xsd_any_uri(creator.actor_id.to_owned())?
|
||||
.set_object_base_box(page)?;
|
||||
send_activity(&update, get_followers(conn, &community)?)?;
|
||||
send_activity(&update, get_follower_inboxes(conn, &community)?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// As a given local user, send out a follow request to a remote community.
|
||||
pub fn follow_community(
|
||||
community: &Community,
|
||||
user: &User_,
|
||||
|
@ -111,6 +116,7 @@ pub fn follow_community(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// As a local community, accept the follow request from a remote user.
|
||||
pub fn accept_follow(follow: &Follow) -> Result<(), Error> {
|
||||
let mut accept = Accept::new();
|
||||
accept
|
||||
|
|
|
@ -7,7 +7,6 @@ use crate::db::establish_unpooled_connection;
|
|||
use crate::db::post::Post;
|
||||
use crate::db::user::User_;
|
||||
use crate::db::Crud;
|
||||
use crate::settings::Settings;
|
||||
use crate::{convert_datetime, naive_now};
|
||||
use activitystreams::actor::properties::ApActorProperties;
|
||||
use activitystreams::collection::OrderedCollection;
|
||||
|
@ -30,30 +29,8 @@ pub struct CommunityQuery {
|
|||
community_name: String,
|
||||
}
|
||||
|
||||
pub async fn get_apub_community_list(
|
||||
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
|
||||
) -> Result<HttpResponse<Body>, Error> {
|
||||
// TODO: implement pagination
|
||||
let communities = Community::list_local(&db.get().unwrap())?
|
||||
.iter()
|
||||
.map(|c| c.as_group(&db.get().unwrap()))
|
||||
.collect::<Result<Vec<GroupExt>, Error>>()?;
|
||||
let mut collection = UnorderedCollection::default();
|
||||
let oprops: &mut ObjectProperties = collection.as_mut();
|
||||
oprops.set_context_xsd_any_uri(context())?.set_id(format!(
|
||||
"{}://{}/federation/communities",
|
||||
get_apub_protocol_string(),
|
||||
Settings::get().hostname
|
||||
))?;
|
||||
|
||||
collection
|
||||
.collection_props
|
||||
.set_total_items(communities.len() as u64)?
|
||||
.set_many_items_base_boxes(communities)?;
|
||||
Ok(create_apub_response(&collection))
|
||||
}
|
||||
|
||||
impl Community {
|
||||
// Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
|
||||
fn as_group(&self, conn: &PgConnection) -> Result<GroupExt, Error> {
|
||||
let mut group = Group::default();
|
||||
let oprops: &mut ObjectProperties = group.as_mut();
|
||||
|
@ -104,6 +81,7 @@ impl Community {
|
|||
}
|
||||
|
||||
impl CommunityForm {
|
||||
/// Parse an ActivityPub group received from another instance into a Lemmy community.
|
||||
pub fn from_group(group: &GroupExt, conn: &PgConnection) -> Result<Self, Error> {
|
||||
let oprops = &group.base.base.object_props;
|
||||
let aprops = &group.base.extension;
|
||||
|
@ -142,6 +120,7 @@ impl CommunityForm {
|
|||
}
|
||||
}
|
||||
|
||||
/// Return the community json over HTTP.
|
||||
pub async fn get_apub_community_http(
|
||||
info: Path<CommunityQuery>,
|
||||
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
|
||||
|
@ -151,6 +130,7 @@ pub async fn get_apub_community_http(
|
|||
Ok(create_apub_response(&c))
|
||||
}
|
||||
|
||||
/// Returns an empty followers collection, only populating the siz (for privacy).
|
||||
pub async fn get_apub_community_followers(
|
||||
info: Path<CommunityQuery>,
|
||||
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
|
||||
|
@ -173,6 +153,7 @@ pub async fn get_apub_community_followers(
|
|||
Ok(create_apub_response(&collection))
|
||||
}
|
||||
|
||||
/// Returns an UnorderedCollection with the latest posts from the community.
|
||||
pub async fn get_apub_community_outbox(
|
||||
info: Path<CommunityQuery>,
|
||||
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
|
||||
|
|
|
@ -22,6 +22,7 @@ pub struct Params {
|
|||
community_name: String,
|
||||
}
|
||||
|
||||
/// Handler for all incoming activities to community inboxes.
|
||||
pub async fn community_inbox(
|
||||
input: web::Json<CommunityAcceptedObjects>,
|
||||
params: web::Query<Params>,
|
||||
|
@ -38,6 +39,8 @@ pub async fn community_inbox(
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle a follow request from a remote user, adding it to the local database and returning an
|
||||
/// Accept activity.
|
||||
fn handle_follow(follow: &Follow, conn: &PgConnection) -> Result<HttpResponse, Error> {
|
||||
// TODO: make sure this is a local community
|
||||
let community_uri = follow
|
||||
|
|
|
@ -20,6 +20,7 @@ use serde::Deserialize;
|
|||
use std::time::Duration;
|
||||
use url::Url;
|
||||
|
||||
// Fetch nodeinfo metadata from a remote instance.
|
||||
fn _fetch_node_info(domain: &str) -> Result<NodeInfo, Error> {
|
||||
let well_known_uri = Url::parse(&format!(
|
||||
"{}://{}/.well-known/nodeinfo",
|
||||
|
@ -60,7 +61,9 @@ fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, Error>
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: add an optional param last_updated and only fetch if its too old
|
||||
/// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
|
||||
/// timeouts etc.
|
||||
/// TODO: add an optional param last_updated and only fetch if its too old
|
||||
pub fn fetch_remote_object<Response>(url: &Url) -> Result<Response, Error>
|
||||
where
|
||||
Response: for<'de> Deserialize<'de>,
|
||||
|
@ -81,6 +84,7 @@ where
|
|||
Ok(res)
|
||||
}
|
||||
|
||||
/// The types of ActivityPub objects that can be fetched directly by searching for their ID.
|
||||
#[serde(untagged)]
|
||||
#[derive(serde::Deserialize)]
|
||||
pub enum SearchAcceptedObjects {
|
||||
|
@ -89,6 +93,12 @@ pub enum SearchAcceptedObjects {
|
|||
Page(Box<Page>),
|
||||
}
|
||||
|
||||
/// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
|
||||
///
|
||||
/// Some working examples for use with the docker/federation/ setup:
|
||||
/// http://lemmy_alpha:8540/federation/c/main
|
||||
/// http://lemmy_alpha:8540/federation/u/lemmy_alpha
|
||||
/// http://lemmy_alpha:8540/federation/p/3
|
||||
pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchResponse, Error> {
|
||||
let query_url = Url::parse(&query)?;
|
||||
let mut response = SearchResponse {
|
||||
|
@ -98,10 +108,6 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchRespo
|
|||
communities: vec![],
|
||||
users: vec![],
|
||||
};
|
||||
// test with:
|
||||
// http://lemmy_alpha:8540/federation/c/main
|
||||
// http://lemmy_alpha:8540/federation/u/lemmy_alpha
|
||||
// http://lemmy_alpha:8540/federation/p/3
|
||||
match fetch_remote_object::<SearchAcceptedObjects>(&query_url)? {
|
||||
SearchAcceptedObjects::Person(p) => {
|
||||
let u = upsert_user(&UserForm::from_person(&p)?, conn)?;
|
||||
|
@ -120,6 +126,7 @@ pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchRespo
|
|||
Ok(response)
|
||||
}
|
||||
|
||||
/// Fetch all posts in the outbox of the given user, and insert them into the database.
|
||||
fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result<Vec<Post>, Error> {
|
||||
let outbox_url = Url::parse(&community.get_outbox_url())?;
|
||||
let outbox = fetch_remote_object::<OrderedCollection>(&outbox_url)?;
|
||||
|
@ -137,12 +144,14 @@ fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result<
|
|||
)
|
||||
}
|
||||
|
||||
/// Fetch a user, insert/update it in the database and return the user.
|
||||
pub fn fetch_remote_user(apub_id: &Url, conn: &PgConnection) -> Result<User_, Error> {
|
||||
let person = fetch_remote_object::<PersonExt>(apub_id)?;
|
||||
let uf = UserForm::from_person(&person)?;
|
||||
upsert_user(&uf, conn)
|
||||
}
|
||||
|
||||
/// Fetch a community, insert/update it in the database and return the community.
|
||||
pub fn fetch_remote_community(apub_id: &Url, conn: &PgConnection) -> Result<Community, Error> {
|
||||
let group = fetch_remote_object::<GroupExt>(apub_id)?;
|
||||
let cf = CommunityForm::from_group(&group, conn)?;
|
||||
|
|
|
@ -13,6 +13,7 @@ use activitystreams::ext::Ext;
|
|||
use actix_web::body::Body;
|
||||
use actix_web::HttpResponse;
|
||||
use openssl::{pkey::PKey, rsa::Rsa};
|
||||
use serde::ser::Serialize;
|
||||
use url::Url;
|
||||
|
||||
type GroupExt = Ext<Ext<Group, ApActorProperties>, PublicKeyExtension>;
|
||||
|
@ -27,18 +28,22 @@ pub enum EndpointType {
|
|||
Comment,
|
||||
}
|
||||
|
||||
fn create_apub_response<T>(json: &T) -> HttpResponse<Body>
|
||||
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
|
||||
/// headers.
|
||||
fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
|
||||
where
|
||||
T: serde::ser::Serialize,
|
||||
T: Serialize,
|
||||
{
|
||||
HttpResponse::Ok()
|
||||
.content_type(APUB_JSON_CONTENT_TYPE)
|
||||
.json(json)
|
||||
.json(data)
|
||||
}
|
||||
|
||||
// TODO: we will probably need to change apub endpoint urls so that html and activity+json content
|
||||
// types are handled at the same endpoint, so that you can copy the url into mastodon search
|
||||
// and have it fetch the object.
|
||||
/// Generates the ActivityPub ID for a given object type and name.
|
||||
///
|
||||
/// TODO: we will probably need to change apub endpoint urls so that html and activity+json content
|
||||
/// types are handled at the same endpoint, so that you can copy the url into mastodon search
|
||||
/// and have it fetch the object.
|
||||
pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url {
|
||||
let point = match endpoint_type {
|
||||
EndpointType::Community => "c",
|
||||
|
@ -67,21 +72,16 @@ pub fn get_apub_protocol_string() -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
|
||||
/// Generate the asymmetric keypair for ActivityPub HTTP signatures.
|
||||
pub fn gen_keypair_str() -> (String, String) {
|
||||
let rsa = Rsa::generate(2048).expect("sign::gen_keypair: key generation error");
|
||||
let pkey = PKey::from_rsa(rsa).expect("sign::gen_keypair: parsing error");
|
||||
(
|
||||
pkey
|
||||
.public_key_to_pem()
|
||||
.expect("sign::gen_keypair: public key encoding error"),
|
||||
pkey
|
||||
.private_key_to_pem_pkcs8()
|
||||
.expect("sign::gen_keypair: private key encoding error"),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn gen_keypair_str() -> (String, String) {
|
||||
let (public_key, private_key) = gen_keypair();
|
||||
let public_key = pkey
|
||||
.public_key_to_pem()
|
||||
.expect("sign::gen_keypair: public key encoding error");
|
||||
let private_key = pkey
|
||||
.private_key_to_pem_pkcs8()
|
||||
.expect("sign::gen_keypair: private key encoding error");
|
||||
(vec_bytes_to_str(public_key), vec_bytes_to_str(private_key))
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ pub struct PostQuery {
|
|||
post_id: String,
|
||||
}
|
||||
|
||||
/// Return the post json over HTTP.
|
||||
pub async fn get_apub_post(
|
||||
info: Path<PostQuery>,
|
||||
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
|
||||
|
@ -30,6 +31,7 @@ pub async fn get_apub_post(
|
|||
}
|
||||
|
||||
impl Post {
|
||||
// Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
|
||||
pub fn as_page(&self, conn: &PgConnection) -> Result<Page, Error> {
|
||||
let mut page = Page::default();
|
||||
let oprops: &mut ObjectProperties = page.as_mut();
|
||||
|
@ -67,6 +69,7 @@ impl Post {
|
|||
}
|
||||
|
||||
impl PostForm {
|
||||
/// Parse an ActivityPub page received from another instance into a Lemmy post.
|
||||
pub fn from_page(page: &Page, conn: &PgConnection) -> Result<PostForm, Error> {
|
||||
let oprops = &page.object_props;
|
||||
let creator_id = Url::parse(&oprops.get_attributed_to_xsd_any_uri().unwrap().to_string())?;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
// For this example, we'll use the Extensible trait, the Extension trait, the Actor trait, and
|
||||
// the Person type
|
||||
use activitystreams::{actor::Actor, ext::Extension};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
// The following is taken from here:
|
||||
// https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html
|
||||
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PublicKey {
|
||||
pub id: String,
|
||||
|
@ -13,7 +14,7 @@ pub struct PublicKey {
|
|||
pub public_key_pem: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
|
||||
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PublicKeyExtension {
|
||||
pub public_key: PublicKey,
|
||||
|
|
|
@ -22,6 +22,7 @@ pub struct UserQuery {
|
|||
user_name: String,
|
||||
}
|
||||
|
||||
// Turn a Lemmy user into an ActivityPub person and return it as json.
|
||||
pub async fn get_apub_user(
|
||||
info: Path<UserQuery>,
|
||||
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
|
||||
|
@ -64,6 +65,7 @@ pub async fn get_apub_user(
|
|||
}
|
||||
|
||||
impl UserForm {
|
||||
/// Parse an ActivityPub person received from another instance into a Lemmy user.
|
||||
pub fn from_person(person: &PersonExt) -> Result<Self, Error> {
|
||||
let oprops = &person.base.base.object_props;
|
||||
let aprops = &person.base.extension;
|
||||
|
|
|
@ -22,6 +22,7 @@ pub struct Params {
|
|||
user_name: String,
|
||||
}
|
||||
|
||||
/// Handler for all incoming activities to user inboxes.
|
||||
pub async fn user_inbox(
|
||||
input: web::Json<UserAcceptedObjects>,
|
||||
params: web::Query<Params>,
|
||||
|
@ -38,6 +39,7 @@ pub async fn user_inbox(
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle create activities and insert them in the database.
|
||||
fn handle_create(create: &Create, conn: &PgConnection) -> Result<HttpResponse, Error> {
|
||||
let page = create
|
||||
.create_props
|
||||
|
@ -52,6 +54,7 @@ fn handle_create(create: &Create, conn: &PgConnection) -> Result<HttpResponse, E
|
|||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
/// Handle update activities and insert them in the database.
|
||||
fn handle_update(update: &Update, conn: &PgConnection) -> Result<HttpResponse, Error> {
|
||||
let page = update
|
||||
.update_props
|
||||
|
@ -67,6 +70,7 @@ fn handle_update(update: &Update, conn: &PgConnection) -> Result<HttpResponse, E
|
|||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
||||
/// Handle accepted follows.
|
||||
fn handle_accept(_accept: &Accept, _conn: &PgConnection) -> Result<HttpResponse, Error> {
|
||||
// TODO: make sure that we actually requested a follow
|
||||
// TODO: at this point, indicate to the user that they are following the community
|
||||
|
|
Loading…
Reference in a new issue