Store activitypub endpoints in database (#162)

Address review comments

Store Activitypub urls in database (fixes #808)

Co-authored-by: Felix Ableitner <me@nutomic.com>
Reviewed-on: https://yerbamate.ml/LemmyNet/lemmy/pulls/162
Co-Authored-By: nutomic <nutomic@noreply.yerbamate.ml>
Co-Committed-By: nutomic <nutomic@noreply.yerbamate.ml>
This commit is contained in:
nutomic 2021-02-04 16:34:58 +00:00 committed by dessalines
parent ed8a12f96f
commit 1a4e35eb50
43 changed files with 985 additions and 178 deletions

1
Cargo.lock generated
View file

@ -1887,6 +1887,7 @@ dependencies = [
"lemmy_db_schema",
"log",
"serde 1.0.123",
"url",
]
[[package]]

View file

@ -9,7 +9,7 @@ use crate::{
Perform,
};
use actix_web::web::Data;
use lemmy_apub::{ApubLikeableType, ApubObjectType};
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
use lemmy_db_queries::{
source::comment::Comment_,
Crud,
@ -26,7 +26,6 @@ use lemmy_db_views::{
};
use lemmy_structs::{blocking, comment::*, send_local_notifs};
use lemmy_utils::{
apub::{make_apub_endpoint, EndpointType},
utils::{remove_slurs, scrape_text_for_mentions},
APIError,
ConnectionId,
@ -104,16 +103,17 @@ impl Perform for CreateComment {
// Necessary to update the ap_id
let inserted_comment_id = inserted_comment.id;
let updated_comment: Comment = match blocking(context.pool(), move |conn| {
let apub_id =
make_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string()).to_string();
Comment::update_ap_id(&conn, inserted_comment_id, apub_id)
})
.await?
{
Ok(comment) => comment,
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
};
let updated_comment: Comment =
match blocking(context.pool(), move |conn| -> Result<Comment, LemmyError> {
let apub_id =
generate_apub_endpoint(EndpointType::Comment, &inserted_comment_id.to_string())?;
Ok(Comment::update_ap_id(&conn, inserted_comment_id, apub_id)?)
})
.await?
{
Ok(comment) => comment,
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
};
updated_comment.send_create(&user, context).await?;

View file

@ -9,7 +9,14 @@ use crate::{
};
use actix_web::web::Data;
use anyhow::Context;
use lemmy_apub::ActorType;
use lemmy_apub::{
generate_apub_endpoint,
generate_followers_url,
generate_inbox_url,
generate_shared_inbox_url,
ActorType,
EndpointType,
};
use lemmy_db_queries::{
diesel_option_overwrite,
source::{
@ -38,7 +45,7 @@ use lemmy_db_views_actor::{
};
use lemmy_structs::{blocking, community::*};
use lemmy_utils::{
apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
apub::generate_actor_keypair,
location_info,
utils::{check_slurs, check_slurs_opt, is_valid_community_name, naive_from_unix},
APIError,
@ -137,10 +144,10 @@ impl Perform for CreateCommunity {
}
// Double check for duplicate community actor_ids
let actor_id = make_apub_endpoint(EndpointType::Community, &data.name);
let actor_id_cloned = actor_id.to_owned();
let community_actor_id = generate_apub_endpoint(EndpointType::Community, &data.name)?;
let actor_id_cloned = community_actor_id.to_owned();
let community_dupe = blocking(context.pool(), move |conn| {
Community::read_from_apub_id(conn, &actor_id_cloned.into())
Community::read_from_apub_id(conn, &actor_id_cloned)
})
.await?;
if community_dupe.is_ok() {
@ -169,12 +176,15 @@ impl Perform for CreateCommunity {
deleted: None,
nsfw: data.nsfw,
updated: None,
actor_id: Some(actor_id.into()),
actor_id: Some(community_actor_id.to_owned()),
local: true,
private_key: Some(keypair.private_key),
public_key: Some(keypair.public_key),
last_refreshed_at: None,
published: None,
followers_url: Some(generate_followers_url(&community_actor_id)?),
inbox_url: Some(generate_inbox_url(&community_actor_id)?),
shared_inbox_url: Some(Some(generate_shared_inbox_url(&community_actor_id)?)),
};
let inserted_community = match blocking(context.pool(), move |conn| {
@ -275,6 +285,9 @@ impl Perform for EditCommunity {
public_key: read_community.public_key,
last_refreshed_at: None,
published: None,
followers_url: None,
inbox_url: None,
shared_inbox_url: None,
};
let community_id = data.community_id;

View file

@ -9,7 +9,7 @@ use crate::{
Perform,
};
use actix_web::web::Data;
use lemmy_apub::{ApubLikeableType, ApubObjectType};
use lemmy_apub::{generate_apub_endpoint, ApubLikeableType, ApubObjectType, EndpointType};
use lemmy_db_queries::{
source::post::Post_,
Crud,
@ -38,7 +38,6 @@ use lemmy_db_views_actor::{
};
use lemmy_structs::{blocking, post::*};
use lemmy_utils::{
apub::{make_apub_endpoint, EndpointType},
request::fetch_iframely_and_pictrs_data,
utils::{check_slurs, check_slurs_opt, is_valid_post_title},
APIError,
@ -115,10 +114,9 @@ impl Perform for CreatePost {
};
let inserted_post_id = inserted_post.id;
let updated_post = match blocking(context.pool(), move |conn| {
let apub_id =
make_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string()).to_string();
Post::update_ap_id(conn, inserted_post_id, apub_id)
let updated_post = match blocking(context.pool(), move |conn| -> Result<Post, LemmyError> {
let apub_id = generate_apub_endpoint(EndpointType::Post, &inserted_post_id.to_string())?;
Ok(Post::update_ap_id(conn, inserted_post_id, apub_id)?)
})
.await?
{

View file

@ -13,7 +13,14 @@ use anyhow::Context;
use bcrypt::verify;
use captcha::{gen, Difficulty};
use chrono::Duration;
use lemmy_apub::ApubObjectType;
use lemmy_apub::{
generate_apub_endpoint,
generate_followers_url,
generate_inbox_url,
generate_shared_inbox_url,
ApubObjectType,
EndpointType,
};
use lemmy_db_queries::{
diesel_option_overwrite,
source::{
@ -61,7 +68,7 @@ use lemmy_db_views_actor::{
};
use lemmy_structs::{blocking, send_email_to_user, user::*};
use lemmy_utils::{
apub::{generate_actor_keypair, make_apub_endpoint, EndpointType},
apub::generate_actor_keypair,
email::send_email,
location_info,
settings::Settings,
@ -179,6 +186,7 @@ impl Perform for Register {
if !is_valid_username(&data.username) {
return Err(APIError::err("invalid_username").into());
}
let user_actor_id = generate_apub_endpoint(EndpointType::User, &data.username)?;
// Register the new user
let user_form = UserForm {
@ -200,12 +208,14 @@ impl Perform for Register {
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
actor_id: Some(make_apub_endpoint(EndpointType::User, &data.username).into()),
actor_id: Some(user_actor_id.clone()),
bio: None,
local: true,
private_key: Some(user_keypair.private_key),
public_key: Some(user_keypair.public_key),
last_refreshed_at: None,
inbox_url: Some(generate_inbox_url(&user_actor_id)?),
shared_inbox_url: Some(Some(generate_shared_inbox_url(&user_actor_id)?)),
};
// Create the user
@ -236,6 +246,7 @@ impl Perform for Register {
Ok(c) => c,
Err(_e) => {
let default_community_name = "main";
let actor_id = generate_apub_endpoint(EndpointType::Community, default_community_name)?;
let community_form = CommunityForm {
name: default_community_name.to_string(),
title: "The Default Community".to_string(),
@ -246,9 +257,7 @@ impl Perform for Register {
removed: None,
deleted: None,
updated: None,
actor_id: Some(
make_apub_endpoint(EndpointType::Community, default_community_name).into(),
),
actor_id: Some(actor_id.to_owned()),
local: true,
private_key: Some(main_community_keypair.private_key),
public_key: Some(main_community_keypair.public_key),
@ -256,6 +265,9 @@ impl Perform for Register {
published: None,
icon: None,
banner: None,
followers_url: Some(generate_followers_url(&actor_id)?),
inbox_url: Some(generate_inbox_url(&actor_id)?),
shared_inbox_url: Some(Some(generate_shared_inbox_url(&actor_id)?)),
};
blocking(context.pool(), move |conn| {
Community::create(conn, &community_form)
@ -420,6 +432,7 @@ impl Perform for SaveUserSettings {
matrix_user_id,
avatar,
banner,
inbox_url: None,
password_encrypted,
preferred_username,
published: Some(user.published),
@ -439,6 +452,7 @@ impl Perform for SaveUserSettings {
private_key: user.private_key,
public_key: user.public_key,
last_refreshed_at: None,
shared_inbox_url: None,
};
let res = blocking(context.pool(), move |conn| {
@ -1036,14 +1050,20 @@ impl Perform for CreatePrivateMessage {
};
let inserted_private_message_id = inserted_private_message.id;
let updated_private_message = match blocking(context.pool(), move |conn| {
let apub_id = make_apub_endpoint(
EndpointType::PrivateMessage,
&inserted_private_message_id.to_string(),
)
.to_string();
PrivateMessage::update_ap_id(&conn, inserted_private_message_id, apub_id)
})
let updated_private_message = match blocking(
context.pool(),
move |conn| -> Result<PrivateMessage, LemmyError> {
let apub_id = generate_apub_endpoint(
EndpointType::PrivateMessage,
&inserted_private_message_id.to_string(),
)?;
Ok(PrivateMessage::update_ap_id(
&conn,
inserted_private_message_id,
apub_id,
)?)
},
)
.await?
{
Ok(private_message) => private_message,

View file

@ -351,7 +351,7 @@ async fn collect_non_local_mentions(
let parent_creator = get_comment_parent_creator(context.pool(), comment).await?;
let mut addressed_ccs = vec![community.actor_id(), parent_creator.actor_id()];
// Note: dont include community inbox here, as we send to it separately with `send_to_community()`
let mut inboxes = vec![parent_creator.get_shared_inbox_url()?];
let mut inboxes = vec![parent_creator.get_shared_inbox_or_inbox_url()];
// Add the mention tag
let mut tags = Vec::new();
@ -370,7 +370,7 @@ async fn collect_non_local_mentions(
addressed_ccs.push(actor_id.to_owned().to_string().parse()?);
let mention_user = get_or_fetch_and_upsert_user(&actor_id, context, &mut 0).await?;
inboxes.push(mention_user.get_shared_inbox_url()?);
inboxes.push(mention_user.get_shared_inbox_or_inbox_url());
let mut mention_tag = Mention::new();
mention_tag.set_href(actor_id).set_name(mention.full_name());

View file

@ -27,16 +27,18 @@ use lemmy_db_queries::DbPool;
use lemmy_db_schema::source::community::Community;
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
use lemmy_structs::blocking;
use lemmy_utils::{location_info, settings::Settings, LemmyError};
use lemmy_utils::{location_info, LemmyError};
use lemmy_websocket::LemmyContext;
use url::Url;
#[async_trait::async_trait(?Send)]
impl ActorType for Community {
fn is_local(&self) -> bool {
self.local
}
fn actor_id(&self) -> Url {
self.actor_id.to_owned().into_inner()
}
fn public_key(&self) -> Option<String> {
self.public_key.to_owned()
}
@ -44,6 +46,14 @@ impl ActorType for Community {
self.private_key.to_owned()
}
fn get_shared_inbox_or_inbox_url(&self) -> Url {
self
.shared_inbox_url
.clone()
.unwrap_or_else(|| self.inbox_url.to_owned())
.into()
}
async fn send_follow(
&self,
_follow_actor_id: &Url,
@ -81,7 +91,7 @@ impl ActorType for Community {
.set_id(generate_activity_id(AcceptType::Accept)?)
.set_to(user.actor_id());
send_activity_single_dest(accept, self, user.get_inbox_url()?, context).await?;
send_activity_single_dest(accept, self, user.inbox_url.into(), context).await?;
Ok(())
}
@ -92,7 +102,7 @@ impl ActorType for Community {
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(public())
.set_many_ccs(vec![self.get_followers_url()?]);
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
send_to_community_followers(delete, self, context).await?;
Ok(())
@ -105,14 +115,14 @@ impl ActorType for Community {
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(public())
.set_many_ccs(vec![self.get_followers_url()?]);
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
let mut undo = Undo::new(self.actor_id(), delete.into_any_base()?);
undo
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(UndoType::Undo)?)
.set_to(public())
.set_many_ccs(vec![self.get_followers_url()?]);
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
send_to_community_followers(undo, self, context).await?;
Ok(())
@ -125,7 +135,7 @@ impl ActorType for Community {
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(RemoveType::Remove)?)
.set_to(public())
.set_many_ccs(vec![self.get_followers_url()?]);
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
send_to_community_followers(remove, self, context).await?;
Ok(())
@ -138,7 +148,7 @@ impl ActorType for Community {
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(RemoveType::Remove)?)
.set_to(public())
.set_many_ccs(vec![self.get_followers_url()?]);
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
// Undo that fake activity
let mut undo = Undo::new(self.actor_id(), remove.into_any_base()?);
@ -146,7 +156,7 @@ impl ActorType for Community {
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(LikeType::Like)?)
.set_to(public())
.set_many_ccs(vec![self.get_followers_url()?]);
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
send_to_community_followers(undo, self, context).await?;
Ok(())
@ -164,7 +174,7 @@ impl ActorType for Community {
.set_many_contexts(lemmy_context()?)
.set_id(generate_activity_id(AnnounceType::Announce)?)
.set_to(public())
.set_many_ccs(vec![self.get_followers_url()?]);
.set_many_ccs(vec![self.followers_url.clone().into_inner()]);
send_to_community_followers(announce, self, context).await?;
@ -172,38 +182,21 @@ impl ActorType for Community {
}
/// For a given community, returns the inboxes of all followers.
///
/// TODO: this function is very badly implemented, we should just store shared_inbox_url in
/// CommunityFollowerView
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError> {
let id = self.id;
let inboxes = blocking(pool, move |conn| {
let follows = blocking(pool, move |conn| {
CommunityFollowerView::for_community(conn, id)
})
.await??;
let inboxes = inboxes
let inboxes = follows
.into_iter()
.filter(|i| !i.follower.local)
.map(|u| -> Result<Url, LemmyError> {
let url = u.follower.actor_id.into_inner();
let domain = url.domain().context(location_info!())?;
let port = if let Some(port) = url.port() {
format!(":{}", port)
} else {
"".to_string()
};
Ok(Url::parse(&format!(
"{}://{}{}/inbox",
Settings::get().get_protocol_string(),
domain,
port,
))?)
})
.filter_map(Result::ok)
.filter(|f| !f.follower.local)
.map(|f| f.follower.shared_inbox_url.unwrap_or(f.follower.inbox_url))
.map(|i| i.into_inner())
.unique()
// Don't send to blocked instances
.filter(|inbox| check_is_apub_id_valid(inbox).is_ok())
.unique()
.collect();
Ok(inboxes)

View file

@ -41,7 +41,7 @@ impl ApubObjectType for PrivateMessage {
.set_id(generate_activity_id(CreateType::Create)?)
.set_to(recipient.actor_id());
send_activity_single_dest(create, creator, recipient.get_inbox_url()?, context).await?;
send_activity_single_dest(create, creator, recipient.inbox_url.into(), context).await?;
Ok(())
}
@ -61,7 +61,7 @@ impl ApubObjectType for PrivateMessage {
.set_id(generate_activity_id(UpdateType::Update)?)
.set_to(recipient.actor_id());
send_activity_single_dest(update, creator, recipient.get_inbox_url()?, context).await?;
send_activity_single_dest(update, creator, recipient.inbox_url.into(), context).await?;
Ok(())
}
@ -78,7 +78,7 @@ impl ApubObjectType for PrivateMessage {
.set_id(generate_activity_id(DeleteType::Delete)?)
.set_to(recipient.actor_id());
send_activity_single_dest(delete, creator, recipient.get_inbox_url()?, context).await?;
send_activity_single_dest(delete, creator, recipient.inbox_url.into(), context).await?;
Ok(())
}
@ -109,7 +109,7 @@ impl ApubObjectType for PrivateMessage {
.set_id(generate_activity_id(UndoType::Undo)?)
.set_to(recipient.actor_id());
send_activity_single_dest(undo, creator, recipient.get_inbox_url()?, context).await?;
send_activity_single_dest(undo, creator, recipient.inbox_url.into(), context).await?;
Ok(())
}

View file

@ -25,6 +25,9 @@ use url::Url;
#[async_trait::async_trait(?Send)]
impl ActorType for User_ {
fn is_local(&self) -> bool {
self.local
}
fn actor_id(&self) -> Url {
self.actor_id.to_owned().into_inner()
}
@ -37,6 +40,14 @@ impl ActorType for User_ {
self.private_key.to_owned()
}
fn get_shared_inbox_or_inbox_url(&self) -> Url {
self
.shared_inbox_url
.clone()
.unwrap_or_else(|| self.inbox_url.to_owned())
.into()
}
/// As a given local user, send out a follow request to a remote community.
async fn send_follow(
&self,
@ -65,7 +76,7 @@ impl ActorType for User_ {
.set_id(generate_activity_id(FollowType::Follow)?)
.set_to(community.actor_id());
send_activity_single_dest(follow, self, community.get_inbox_url()?, context).await?;
send_activity_single_dest(follow, self, community.inbox_url.into(), context).await?;
Ok(())
}
@ -96,7 +107,7 @@ impl ActorType for User_ {
.set_id(generate_activity_id(UndoType::Undo)?)
.set_to(community.actor_id());
send_activity_single_dest(undo, self, community.get_inbox_url()?, context).await?;
send_activity_single_dest(undo, self, community.inbox_url.into(), context).await?;
Ok(())
}

View file

@ -94,7 +94,7 @@ where
.collect();
debug!(
"Sending activity {:?} to followers of {}",
&activity.id_unchecked(),
&activity.id_unchecked().map(|i| i.to_string()),
&community.actor_id
);
@ -135,7 +135,7 @@ where
.send_announce(activity.into_any_base()?, context)
.await?;
} else {
let inbox = community.get_shared_inbox_url()?;
let inbox = community.get_shared_inbox_or_inbox_url();
check_is_apub_id_valid(&inbox)?;
debug!(
"Sending activity {:?} to community {}",

View file

@ -7,10 +7,10 @@ use crate::{
},
inbox::user_inbox::receive_announce,
objects::FromApub,
ActorType,
GroupExt,
};
use activitystreams::{
actor::ApActorExt,
collection::{CollectionExt, OrderedCollection},
object::ObjectExt,
};
@ -116,7 +116,8 @@ async fn fetch_remote_community(
// only fetch outbox for new communities, otherwise this can create an infinite loop
if old_community.is_none() {
fetch_community_outbox(context, &community, recursion_counter).await?
let outbox = group.inner.outbox()?.context(location_info!())?;
fetch_community_outbox(context, outbox, &community, recursion_counter).await?
}
Ok(community)
@ -124,15 +125,12 @@ async fn fetch_remote_community(
async fn fetch_community_outbox(
context: &LemmyContext,
outbox: &Url,
community: &Community,
recursion_counter: &mut i32,
) -> Result<(), LemmyError> {
let outbox = fetch_remote_object::<OrderedCollection>(
context.client(),
&community.get_outbox_url()?,
recursion_counter,
)
.await?;
let outbox =
fetch_remote_object::<OrderedCollection>(context.client(), outbox, recursion_counter).await?;
let outbox_activities = outbox.items().context(location_info!())?.clone();
let mut outbox_activities = outbox_activities.many().context(location_info!())?;
if outbox_activities.len() > 20 {

View file

@ -60,7 +60,7 @@ pub async fn get_apub_community_followers(
let mut collection = UnorderedCollection::new();
collection
.set_many_contexts(lemmy_context()?)
.set_id(community.get_followers_url()?)
.set_id(community.followers_url.into())
.set_total_items(community_followers.len() as u64);
Ok(create_apub_response(&collection))
}

View file

@ -12,7 +12,11 @@ use activitystreams::{
};
use actix_web::HttpRequest;
use anyhow::{anyhow, Context};
use lemmy_db_queries::{source::activity::Activity_, ApubObject, DbPool};
use lemmy_db_queries::{
source::{activity::Activity_, community::Community_},
ApubObject,
DbPool,
};
use lemmy_db_schema::source::{activity::Activity, community::Community, user::User_};
use lemmy_structs::blocking;
use lemmy_utils::{location_info, settings::Settings, LemmyError};
@ -141,16 +145,15 @@ pub(crate) async fn is_addressed_to_community_followers(
pool: &DbPool,
) -> Result<Option<Community>, LemmyError> {
for url in to_and_cc {
let url = url.to_string();
// TODO: extremely hacky, we should just store the followers url for each community in the db
if url.ends_with("/followers") {
let community_url = Url::parse(&url.replace("/followers", ""))?;
let community = blocking(&pool, move |conn| {
Community::read_from_apub_id(&conn, &community_url.into())
})
.await??;
if !community.local {
return Ok(Some(community));
let url = url.to_owned().into();
let community = blocking(&pool, move |conn| {
// ignore errors here, because the current url might not actually be a followers url
Community::read_from_followers_url(&conn, &url).ok()
})
.await?;
if let Some(c) = community {
if !c.local {
return Ok(Some(c));
}
}
}

View file

@ -140,6 +140,7 @@ pub trait ApubLikeableType {
/// implemented by all actors.
#[async_trait::async_trait(?Send)]
pub trait ActorType {
fn is_local(&self) -> bool;
fn actor_id(&self) -> Url;
// TODO: every actor should have a public key, so this shouldnt be an option (needs to be fixed in db)
@ -178,32 +179,15 @@ pub trait ActorType {
/// For a given community, returns the inboxes of all followers.
async fn get_follower_inboxes(&self, pool: &DbPool) -> Result<Vec<Url>, LemmyError>;
// TODO move these to the db rows
fn get_inbox_url(&self) -> Result<Url, ParseError> {
Url::parse(&format!("{}/inbox", &self.actor_id()))
}
fn get_shared_inbox_or_inbox_url(&self) -> Url;
fn get_shared_inbox_url(&self) -> Result<Url, LemmyError> {
let actor_id = self.actor_id();
let url = format!(
"{}://{}{}/inbox",
&actor_id.scheme(),
&actor_id.host_str().context(location_info!())?,
if let Some(port) = actor_id.port() {
format!(":{}", port)
} else {
"".to_string()
},
);
Ok(Url::parse(&url)?)
}
fn get_outbox_url(&self) -> Result<Url, ParseError> {
Url::parse(&format!("{}/outbox", &self.actor_id()))
}
fn get_followers_url(&self) -> Result<Url, ParseError> {
Url::parse(&format!("{}/followers", &self.actor_id()))
/// Outbox URL is not generally used by Lemmy, so it can be generated on the fly (but only for
/// local actors).
fn get_outbox_url(&self) -> Result<Url, LemmyError> {
if !self.is_local() {
return Err(anyhow!("get_outbox_url() called for remote actor").into());
}
Ok(Url::parse(&format!("{}/outbox", &self.actor_id()))?)
}
fn get_public_key_ext(&self) -> Result<PublicKeyExtension, LemmyError> {
@ -218,6 +202,67 @@ pub trait ActorType {
}
}
pub enum EndpointType {
Community,
User,
Post,
Comment,
PrivateMessage,
}
/// Generates the ActivityPub ID for a given object type and ID.
pub fn generate_apub_endpoint(
endpoint_type: EndpointType,
name: &str,
) -> Result<lemmy_db_schema::Url, ParseError> {
let point = match endpoint_type {
EndpointType::Community => "c",
EndpointType::User => "u",
EndpointType::Post => "post",
EndpointType::Comment => "comment",
EndpointType::PrivateMessage => "private_message",
};
Ok(
Url::parse(&format!(
"{}/{}/{}",
Settings::get().get_protocol_and_hostname(),
point,
name
))?
.into(),
)
}
pub fn generate_followers_url(
actor_id: &lemmy_db_schema::Url,
) -> Result<lemmy_db_schema::Url, ParseError> {
Ok(Url::parse(&format!("{}/followers", actor_id))?.into())
}
pub fn generate_inbox_url(
actor_id: &lemmy_db_schema::Url,
) -> Result<lemmy_db_schema::Url, ParseError> {
Ok(Url::parse(&format!("{}/inbox", actor_id))?.into())
}
pub fn generate_shared_inbox_url(
actor_id: &lemmy_db_schema::Url,</