let json = serde_json::to_string(&activity)?;
debug!("Sending activitypub activity {} to {:?}", json, to);
// TODO it needs to expand, the to field needs to expand and dedup the followers urls
- // The inbox is determined by first retrieving the target actor's JSON-LD representation and then looking up the inbox property. If a recipient is a Collection or OrderedCollection, then the server MUST dereference the collection (with the user's credentials) and discover inboxes for each item in the collection. Servers MUST limit the number of layers of indirections through collections which will be performed, which MAY be one.
+ // The inbox is determined by first retrieving the target actor's JSON-LD representation and then looking up the inbox property. If a recipient is a Collection or OrderedCollection, then the server MUST dereference the collection (with the user's credentials) and discover inboxes for each item in the collection. Servers MUST limit the number of layers of indirections through collections which will be performed, which MAY be one.
for t in to {
let to_url = Url::parse(&t)?;
if !is_apub_id_valid(&to_url) {
/// 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 page = post.to_apub(conn)?;
let community = Community::read(conn, post.community_id)?;
let mut create = Create::new();
populate_object_props(
/// 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 page = post.to_apub(conn)?;
let community = Community::read(conn, post.community_id)?;
let mut update = Update::new();
populate_object_props(
.get_object_xsd_any_uri()
.unwrap()
.to_string();
- let actor_uri = follow.follow_props.get_actor_xsd_any_uri().unwrap().to_string();
+ let actor_uri = follow
+ .follow_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
let community = Community::read_from_actor_id(conn, &community_uri)?;
let mut accept = Accept::new();
accept
.follow_props
.get_actor_xsd_any_uri()
.unwrap()
- .to_string()
+ .to_string(),
)?;
accept
.accept_props
community_name: String,
}
-// TODO turn these as_group, as_page, into apub trait... something like to_apub
-impl Community {
+impl ToApub<GroupExt> for 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> {
+ fn to_apub(&self, conn: &PgConnection) -> Result<GroupExt, Error> {
let mut group = Group::default();
let oprops: &mut ObjectProperties = group.as_mut();
Ok(group.extend(actor_props).extend(public_key.to_ext()))
}
+}
- pub fn get_followers_url(&self) -> String {
- format!("{}/followers", &self.actor_id)
- }
- pub fn get_inbox_url(&self) -> String {
- format!("{}/inbox", &self.actor_id)
- }
- pub fn get_outbox_url(&self) -> String {
- format!("{}/outbox", &self.actor_id)
+impl ActorType for Community {
+ fn actor_id(&self) -> String {
+ self.actor_id.to_owned()
}
}
-impl CommunityForm {
+impl FromApub<GroupExt> for CommunityForm {
/// Parse an ActivityPub group received from another instance into a Lemmy community.
- pub fn from_group(group: &GroupExt, conn: &PgConnection) -> Result<Self, Error> {
+ fn from_apub(group: &GroupExt, conn: &PgConnection) -> Result<Self, Error> {
let oprops = &group.base.base.object_props;
let aprops = &group.base.extension;
let public_key: &PublicKey = &group.extension.public_key;
- let followers_uri = Url::parse(&aprops.get_followers().unwrap().to_string())?;
- let outbox_uri = Url::parse(&aprops.get_outbox().to_string())?;
- let _outbox = fetch_remote_object::<OrderedCollection>(&outbox_uri)?;
- let _followers = fetch_remote_object::<UnorderedCollection>(&followers_uri)?;
+ let _followers_uri = Url::parse(&aprops.get_followers().unwrap().to_string())?;
+ let _outbox_uri = Url::parse(&aprops.get_outbox().to_string())?;
+ // TODO don't do extra fetching here
+ // let _outbox = fetch_remote_object::<OrderedCollection>(&outbox_uri)?;
+ // let _followers = fetch_remote_object::<UnorderedCollection>(&followers_uri)?;
let apub_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
let creator = get_or_fetch_and_upsert_remote_user(&apub_id, conn)?;
pub async fn get_apub_community_http(
info: Path<CommunityQuery>,
db: DbPoolParam,
- chat_server: ChatServerParam,
) -> Result<HttpResponse<Body>, Error> {
let community = Community::read_from_name(&&db.get()?, &info.community_name)?;
- let c = community.as_group(&db.get().unwrap())?;
+ let c = community.to_apub(&db.get().unwrap())?;
Ok(create_apub_response(&c))
}
pub async fn get_apub_community_followers(
info: Path<CommunityQuery>,
db: DbPoolParam,
- chat_server: ChatServerParam,
) -> Result<HttpResponse<Body>, Error> {
let community = Community::read_from_name(&&db.get()?, &info.community_name)?;
- let connection = establish_unpooled_connection();
+ let conn = db.get()?;
+
//As we are an object, we validated that the community id was valid
- let community_followers =
- CommunityFollowerView::for_community(&connection, community.id).unwrap();
+ let community_followers = CommunityFollowerView::for_community(&conn, community.id).unwrap();
let mut collection = UnorderedCollection::default();
let oprops: &mut ObjectProperties = collection.as_mut();
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: DbPoolParam,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse<Body>, Error> {
- let community = Community::read_from_name(&&db.get()?, &info.community_name)?;
-
- let conn = establish_unpooled_connection();
- //As we are an object, we validated that the community id was valid
- let community_posts: Vec<Post> = Post::list_for_community(&conn, community.id)?;
-
- let mut collection = OrderedCollection::default();
- let oprops: &mut ObjectProperties = collection.as_mut();
- oprops
- .set_context_xsd_any_uri(context())?
- .set_id(community.actor_id)?;
- collection
- .collection_props
- .set_many_items_base_boxes(
- community_posts
- .iter()
- .map(|c| c.as_page(&conn).unwrap())
- .collect(),
- )?
- .set_total_items(community_posts.len() as u64)?;
-
- Ok(create_apub_response(&collection))
-}
+// TODO should not be doing this
+// Returns an UnorderedCollection with the latest posts from the community.
+//pub async fn get_apub_community_outbox(
+// info: Path<CommunityQuery>,
+// db: DbPoolParam,
+// chat_server: ChatServerParam,
+//) -> Result<HttpResponse<Body>, Error> {
+// let community = Community::read_from_name(&&db.get()?, &info.community_name)?;
+
+// let conn = establish_unpooled_connection();
+// //As we are an object, we validated that the community id was valid
+// let community_posts: Vec<Post> = Post::list_for_community(&conn, community.id)?;
+
+// let mut collection = OrderedCollection::default();
+// let oprops: &mut ObjectProperties = collection.as_mut();
+// oprops
+// .set_context_xsd_any_uri(context())?
+// .set_id(community.actor_id)?;
+// collection
+// .collection_props
+// .set_many_items_base_boxes(
+// community_posts
+// .iter()
+// .map(|c| c.as_page(&conn).unwrap())
+// .collect(),
+// )?
+// .set_total_items(community_posts.len() as u64)?;
+
+// Ok(create_apub_response(&collection))
+//}
Follow(Follow),
}
+// TODO Consolidate community and user inboxes into a single shared one
/// Handler for all incoming activities to community inboxes.
pub async fn community_inbox(
request: HttpRequest,
let community_name = path.into_inner();
debug!(
"Community {} received activity {:?}",
- &community_name,
- &input
+ &community_name, &input
);
match input {
- CommunityAcceptedObjects::Follow(f) => handle_follow(&f, &request, &community_name, db, chat_server),
+ CommunityAcceptedObjects::Follow(f) => {
+ handle_follow(&f, &request, &community_name, db, chat_server)
+ }
}
}
request: &HttpRequest,
community_name: &str,
db: DbPoolParam,
- chat_server: ChatServerParam,
+ _chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> {
let user_uri = follow
.follow_props
.get_actor_xsd_any_uri()
.unwrap()
.to_string();
- let community_uri = follow
+ let _community_uri = follow
.follow_props
.get_object_xsd_any_uri()
.unwrap()
Ok(fetch_remote_object::<NodeInfo>(&well_known.links.href)?)
}
-// // TODO: move these to db
-// // TODO use the last_refreshed_at
-// fn upsert_community(
-// community_form: &CommunityForm,
-// conn: &PgConnection,
-// ) -> Result<Community, Error> {
-// let existing = Community::read_from_actor_id(conn, &community_form.actor_id);
-// match existing {
-// Err(NotFound {}) => Ok(Community::create(conn, &community_form)?),
-// Ok(c) => Ok(Community::update(conn, c.id, &community_form)?),
-// Err(e) => Err(Error::from(e)),
-// }
-// }
-// fn upsert_user(user_form: &UserForm, conn: &PgConnection) -> Result<User_, Error> {
-// let existing = User_::read_from_actor_id(conn, &user_form.actor_id);
-// Ok(match existing {
-// Err(NotFound {}) => User_::create(conn, &user_form)?,
-// Ok(u) => User_::update(conn, u.id, &user_form)?,
-// Err(e) => return Err(Error::from(e)),
-// })
-// }
-
-fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, Error> {
- let existing = Post::read_from_apub_id(conn, &post_form.ap_id);
- match existing {
- Err(NotFound {}) => Ok(Post::create(conn, &post_form)?),
- Ok(p) => Ok(Post::update(conn, p.id, &post_form)?),
- Err(e) => Err(Error::from(e)),
- }
-}
-
/// 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>,
pub enum SearchAcceptedObjects {
Person(Box<PersonExt>),
Group(Box<GroupExt>),
- // 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
+/// http://lemmy_alpha:8540/c/main
+/// http://lemmy_alpha:8540/u/lemmy_alpha
+/// http://lemmy_alpha:8540/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 {
};
match fetch_remote_object::<SearchAcceptedObjects>(&query_url)? {
SearchAcceptedObjects::Person(p) => {
- let user = get_or_fetch_and_upsert_remote_user(query, &conn)?;
+ let user_uri = p.base.base.object_props.get_id().unwrap().to_string();
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
response.users = vec![UserView::read(conn, user.id)?];
}
SearchAcceptedObjects::Group(g) => {
- let community = get_or_fetch_and_upsert_remote_community(query, &conn)?;
+ let community_uri = g.base.base.object_props.get_id().unwrap().to_string();
+ let community = get_or_fetch_and_upsert_remote_community(&community_uri, &conn)?;
+ // TODO Maybe at some point in the future, fetch all the history of a community
// fetch_community_outbox(&c, conn)?;
response.communities = vec![CommunityView::read(conn, community.id, None)?];
}
- // SearchAcceptedObjects::Page(p) => {
- // let p = upsert_post(&PostForm::from_page(&p, conn)?, conn)?;
- // response.posts = vec![PostView::read(conn, p.id, None)?];
- // }
}
Ok(response)
}
-// TODO It should not be fetching data from a community outbox.
-// All posts, comments, comment likes, etc should be posts to our community_inbox
-// The only data we should be periodically fetching (if it hasn't been fetched in the last day
-// maybe), is community and user actors
-// and user actors
-/// 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)?;
- let items = outbox.collection_props.get_many_items_base_boxes();
-
- Ok(
- items
- .unwrap()
- .map(|obox: &BaseBox| -> Result<PostForm, Error> {
- let page = obox.clone().to_concrete::<Page>()?;
- PostForm::from_page(&page, conn)
- })
- .map(|pf| upsert_post(&pf?, conn))
- .collect::<Result<Vec<Post>, Error>>()?,
- )
-}
-
/// Check if a remote user exists, create if not found, if its too old update it.Fetch a user, insert/update it in the database and return the user.
-pub fn get_or_fetch_and_upsert_remote_user(apub_id: &str, conn: &PgConnection) -> Result<User_, Error> {
+pub fn get_or_fetch_and_upsert_remote_user(
+ apub_id: &str,
+ conn: &PgConnection,
+) -> Result<User_, Error> {
match User_::read_from_actor_id(&conn, &apub_id) {
Ok(u) => {
// If its older than a day, re-fetch it
// TODO the less than needs to be tested
- if u.last_refreshed_at.lt(&(naive_now() - chrono::Duration::days(1))) {
+ if u
+ .last_refreshed_at
+ .lt(&(naive_now() - chrono::Duration::days(1)))
+ {
debug!("Fetching and updating from remote user: {}", apub_id);
let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?;
- let uf = UserForm::from_person(&person)?;
+ let mut uf = UserForm::from_apub(&person, &conn)?;
uf.last_refreshed_at = Some(naive_now());
Ok(User_::update(&conn, u.id, &uf)?)
} else {
Ok(u)
}
- },
+ }
Err(NotFound {}) => {
debug!("Fetching and creating remote user: {}", apub_id);
let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?;
- let uf = UserForm::from_person(&person)?;
+ let uf = UserForm::from_apub(&person, &conn)?;
Ok(User_::create(conn, &uf)?)
}
Err(e) => Err(Error::from(e)),
}
/// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community.
-pub fn get_or_fetch_and_upsert_remote_community(apub_id: &str, conn: &PgConnection) -> Result<Community, Error> {
+pub fn get_or_fetch_and_upsert_remote_community(
+ apub_id: &str,
+ conn: &PgConnection,
+) -> Result<Community, Error> {
match Community::read_from_actor_id(&conn, &apub_id) {
Ok(c) => {
// If its older than a day, re-fetch it
// TODO the less than needs to be tested
- if c.last_refreshed_at.lt(&(naive_now() - chrono::Duration::days(1))) {
+ if c
+ .last_refreshed_at
+ .lt(&(naive_now() - chrono::Duration::days(1)))
+ {
debug!("Fetching and updating from remote community: {}", apub_id);
let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
- let cf = CommunityForm::from_group(&group, conn)?;
+ let mut cf = CommunityForm::from_apub(&group, conn)?;
cf.last_refreshed_at = Some(naive_now());
Ok(Community::update(&conn, c.id, &cf)?)
} else {
Ok(c)
}
- },
+ }
Err(NotFound {}) => {
debug!("Fetching and creating remote community: {}", apub_id);
let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
- let cf = CommunityForm::from_group(&group, conn)?;
+ let cf = CommunityForm::from_apub(&group, conn)?;
Ok(Community::create(conn, &cf)?)
}
Err(e) => Err(Error::from(e)),
}
}
+
+// TODO Maybe add post, comment searching / caching at a later time
+// fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, Error> {
+// let existing = Post::read_from_apub_id(conn, &post_form.ap_id);
+// match existing {
+// Err(NotFound {}) => Ok(Post::create(conn, &post_form)?),
+// Ok(p) => Ok(Post::update(conn, p.id, &post_form)?),
+// Err(e) => Err(Error::from(e)),
+// }
+// }
+
+// TODO It should not be fetching data from a community outbox.
+// All posts, comments, comment likes, etc should be posts to our community_inbox
+// The only data we should be periodically fetching (if it hasn't been fetched in the last day
+// maybe), is community and user actors
+// and user actors
+// 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)?;
+// let items = outbox.collection_props.get_many_items_base_boxes();
+
+// Ok(
+// items
+// .unwrap()
+// .map(|obox: &BaseBox| -> Result<PostForm, Error> {
+// let page = obox.clone().to_concrete::<Page>()?;
+// PostForm::from_page(&page, conn)
+// })
+// .map(|pf| upsert_post(&pf?, conn))
+// .collect::<Result<Vec<Post>, Error>>()?,
+// )
+// }
pub mod user_inbox;
use activitystreams::{
- context, public, BaseBox,
- actor::{
- Actor,
- Person,
- Group,
- properties::ApActorProperties,
- },
activity::{Accept, Create, Follow, Update},
- object::{
- Page,
- properties::ObjectProperties,
- },
- ext::{
- Ext,
- Extensible,
- Extension,
- },
- collection::{
- UnorderedCollection,
- OrderedCollection,
- },
+ actor::{properties::ApActorProperties, Actor, Group, Person},
+ collection::UnorderedCollection,
+ context,
+ ext::{Ext, Extensible, Extension},
+ object::{properties::ObjectProperties, Page},
+ public, BaseBox,
};
use actix_web::body::Body;
-use actix_web::{web, Result, HttpRequest, HttpResponse};
use actix_web::web::Path;
-use url::Url;
-use failure::Error;
-use failure::_core::fmt::Debug;
-use log::debug;
-use isahc::prelude::*;
+use actix_web::{web, HttpRequest, HttpResponse, Result};
use diesel::result::Error::NotFound;
use diesel::PgConnection;
+use failure::Error;
+use failure::_core::fmt::Debug;
use http::request::Builder;
use http_signature_normalization::Config;
+use isahc::prelude::*;
+use log::debug;
use openssl::hash::MessageDigest;
use openssl::sign::{Signer, Verifier};
use openssl::{pkey::PKey, rsa::Rsa};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::time::Duration;
+use url::Url;
-use crate::routes::{DbPoolParam, ChatServerParam};
-use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
-use crate::{convert_datetime, naive_now, Settings};
-use crate::db::community::{Community, CommunityForm, CommunityFollower, CommunityFollowerForm};
+use crate::api::site::SearchResponse;
+use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm, CommunityForm};
use crate::db::community_view::{CommunityFollowerView, CommunityView};
use crate::db::post::{Post, PostForm};
-use crate::db::post_view::PostView;
use crate::db::user::{UserForm, User_};
use crate::db::user_view::UserView;
-// TODO check on unpooled connection
-use crate::db::{Crud, Followable, SearchType, establish_unpooled_connection};
-use crate::api::site::SearchResponse;
+use crate::db::{Crud, Followable, SearchType};
+use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
+use crate::routes::{ChatServerParam, DbPoolParam};
+use crate::{convert_datetime, naive_now, Settings};
-use signatures::{PublicKey, PublicKeyExtension, sign};
use activities::accept_follow;
+use fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user};
use signatures::verify;
-use fetcher::{fetch_remote_object, get_or_fetch_and_upsert_remote_user, get_or_fetch_and_upsert_remote_community};
+use signatures::{sign, PublicKey, PublicKeyExtension};
type GroupExt = Ext<Ext<Group, ApActorProperties>, PublicKeyExtension>;
type PersonExt = Ext<Ext<Person, ApActorProperties>, PublicKeyExtension>;
None => false,
}
}
+
+// TODO Not sure good names for these
+pub trait ToApub<Response> {
+ fn to_apub(&self, conn: &PgConnection) -> Result<Response, Error>;
+}
+
+pub trait FromApub<ApubType> {
+ fn from_apub(apub: &ApubType, conn: &PgConnection) -> Result<Self, Error>
+ where
+ Self: Sized;
+}
+
+pub trait ActorType {
+ fn actor_id(&self) -> String;
+
+ fn get_inbox_url(&self) -> String {
+ format!("{}/inbox", &self.actor_id())
+ }
+ fn get_outbox_url(&self) -> String {
+ format!("{}/outbox", &self.actor_id())
+ }
+
+ fn get_followers_url(&self) -> String {
+ format!("{}/followers", &self.actor_id())
+ }
+ fn get_following_url(&self) -> String {
+ format!("{}/following", &self.actor_id())
+ }
+ fn get_liked_url(&self) -> String {
+ format!("{}/liked", &self.actor_id())
+ }
+}
pub async fn get_apub_post(
info: Path<PostQuery>,
db: DbPoolParam,
- chat_server: ChatServerParam,
) -> Result<HttpResponse<Body>, Error> {
let id = info.post_id.parse::<i32>()?;
let post = Post::read(&&db.get()?, id)?;
- Ok(create_apub_response(&post.as_page(&db.get().unwrap())?))
+ Ok(create_apub_response(&post.to_apub(&db.get().unwrap())?))
}
-impl Post {
+impl ToApub<Page> for 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> {
+ fn to_apub(&self, conn: &PgConnection) -> Result<Page, Error> {
let mut page = Page::default();
let oprops: &mut ObjectProperties = page.as_mut();
let creator = User_::read(conn, self.creator_id)?;
}
}
-impl PostForm {
+impl FromApub<Page> for PostForm {
/// Parse an ActivityPub page received from another instance into a Lemmy post.
- pub fn from_page(page: &Page, conn: &PgConnection) -> Result<PostForm, Error> {
+ fn from_apub(page: &Page, conn: &PgConnection) -> Result<PostForm, Error> {
let oprops = &page.object_props;
let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?;
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: DbPoolParam,
- chat_server: ChatServerParam,
-) -> Result<HttpResponse<Body>, Error> {
- let user = User_::find_by_email_or_username(&&db.get()?, &info.user_name)?;
+impl ToApub<PersonExt> for User_ {
+ // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
+ fn to_apub(&self, _conn: &PgConnection) -> Result<PersonExt, Error> {
+ // TODO go through all these to_string and to_owned()
+ let mut person = Person::default();
+ let oprops: &mut ObjectProperties = person.as_mut();
+ oprops
+ .set_context_xsd_any_uri(context())?
+ .set_id(self.actor_id.to_string())?
+ .set_name_xsd_string(self.name.to_owned())?
+ .set_published(convert_datetime(self.published))?;
- let mut person = Person::default();
- let oprops: &mut ObjectProperties = person.as_mut();
- oprops
- .set_context_xsd_any_uri(context())?
- .set_id(user.actor_id.to_string())?
- .set_name_xsd_string(user.name.to_owned())?
- .set_published(convert_datetime(user.published))?;
+ if let Some(u) = self.updated {
+ oprops.set_updated(convert_datetime(u))?;
+ }
- if let Some(u) = user.updated {
- oprops.set_updated(convert_datetime(u))?;
- }
+ if let Some(i) = &self.preferred_username {
+ oprops.set_name_xsd_string(i.to_owned())?;
+ }
- if let Some(i) = &user.preferred_username {
- oprops.set_name_xsd_string(i.to_owned())?;
- }
+ let mut actor_props = ApActorProperties::default();
- let mut actor_props = ApActorProperties::default();
+ actor_props
+ .set_inbox(self.get_inbox_url())?
+ .set_outbox(self.get_outbox_url())?
+ .set_followers(self.get_followers_url())?
+ .set_following(self.get_following_url())?
+ .set_liked(self.get_liked_url())?;
- actor_props
- .set_inbox(format!("{}/inbox", &user.actor_id))?
- .set_outbox(format!("{}/outbox", &user.actor_id))?
- .set_following(format!("{}/following", &user.actor_id))?
- .set_liked(format!("{}/liked", &user.actor_id))?;
+ let public_key = PublicKey {
+ id: format!("{}#main-key", self.actor_id),
+ owner: self.actor_id.to_owned(),
+ public_key_pem: self.public_key.to_owned().unwrap(),
+ };
- let public_key = PublicKey {
- id: format!("{}#main-key", user.actor_id),
- owner: user.actor_id.to_owned(),
- public_key_pem: user.public_key.unwrap(),
- };
+ Ok(person.extend(actor_props).extend(public_key.to_ext()))
+ }
+}
- Ok(create_apub_response(
- &person.extend(actor_props).extend(public_key.to_ext()),
- ))
+impl ActorType for User_ {
+ fn actor_id(&self) -> String {
+ self.actor_id.to_owned()
+ }
}
-impl UserForm {
+impl FromApub<PersonExt> for UserForm {
/// Parse an ActivityPub person received from another instance into a Lemmy user.
- pub fn from_person(person: &PersonExt) -> Result<Self, Error> {
+ fn from_apub(person: &PersonExt, _conn: &PgConnection) -> Result<Self, Error> {
let oprops = &person.base.base.object_props;
let aprops = &person.base.extension;
let public_key: &PublicKey = &person.extension.public_key;
})
}
}
+
+/// Return the user json over HTTP.
+pub async fn get_apub_user_http(
+ info: Path<UserQuery>,
+ db: DbPoolParam,
+) -> Result<HttpResponse<Body>, Error> {
+ let user = User_::find_by_email_or_username(&&db.get()?, &info.user_name)?;
+ let u = user.to_apub(&db.get().unwrap())?;
+ Ok(create_apub_response(&u))
+}
input: web::Json<UserAcceptedObjects>,
path: web::Path<String>,
db: DbPoolParam,
- chat_server: ChatServerParam,
+ _chat_server: ChatServerParam,
) -> Result<HttpResponse, Error> {
// TODO: would be nice if we could do the signature check here, but we cant access the actor property
let input = input.into_inner();
let conn = &db.get().unwrap();
let username = path.into_inner();
- debug!(
- "User {} received activity: {:?}",
- &username,
- &input
- );
+ debug!("User {} received activity: {:?}", &username, &input);
match input {
UserAcceptedObjects::Create(c) => handle_create(&c, &request, &username, &conn),
fn handle_create(
create: &Create,
request: &HttpRequest,
- username: &str,
+ _username: &str,
conn: &PgConnection,
) -> Result<HttpResponse, Error> {
// TODO before this even gets named, because we don't know what type of object it is, we need
// to parse this out
- let community_uri = create
+ let user_uri = create
.create_props
.get_actor_xsd_any_uri()
.unwrap()
.to_string();
- // TODO: should do this in a generic way so we dont need to know if its a user or a community
- let user = fetch_remote_user(&Url::parse(&community_uri)?, conn)?;
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
verify(request, &user.public_key.unwrap())?;
let page = create
.unwrap()
.to_owned()
.to_concrete::<Page>()?;
- let post = PostForm::from_page(&page, conn)?;
+ let post = PostForm::from_apub(&page, conn)?;
Post::create(conn, &post)?;
// TODO: send the new post out via websocket
Ok(HttpResponse::Ok().finish())
fn handle_update(
update: &Update,
request: &HttpRequest,
- username: &str,
+ _username: &str,
conn: &PgConnection,
) -> Result<HttpResponse, Error> {
- let community_uri = update
+ let user_uri = update
.update_props
.get_actor_xsd_any_uri()
.unwrap()
.to_string();
- let user = fetch_remote_user(&Url::parse(&community_uri)?, conn)?;
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
verify(request, &user.public_key.unwrap())?;
let page = update
.unwrap()
.to_owned()
.to_concrete::<Page>()?;
- let post = PostForm::from_page(&page, conn)?;
+ let post = PostForm::from_apub(&page, conn)?;
let id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
Post::update(conn, id, &post)?;
// TODO: send the new post out via websocket
.get_actor_xsd_any_uri()
.unwrap()
.to_string();
-
+
let community = get_or_fetch_and_upsert_remote_community(&community_uri, conn)?;
verify(request, &community.public_key.unwrap())?;
"/c/{community_name}/followers",
web::get().to(get_apub_community_followers),
)
- .route(
- "/c/{community_name}/outbox",
- web::get().to(get_apub_community_outbox),
- )
- .route("/u/{user_name}", web::get().to(get_apub_user))
+ // TODO This is only useful for history which we aren't doing right now
+ // .route(
+ // "/c/{community_name}/outbox",
+ // web::get().to(get_apub_community_outbox),
+ // )
+ .route("/u/{user_name}", web::get().to(get_apub_user_http))
.route("/post/{post_id}", web::get().to(get_apub_post)),
)
// Inboxes dont work with the header guard for some reason.