Federation refactoring (#5855)

* refactor federation http responses

* Rename Activity

* test fixes

* library release
This commit is contained in:
Nutomic 2025-07-11 03:31:33 +00:00 committed by GitHub
parent 7fb11c36fa
commit a8a407ca6d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 277 additions and 457 deletions

72
Cargo.lock generated
View file

@ -10,9 +10,9 @@ checksum = "8f27d075294830fcab6f66e320dab524bc6d048f4a151698e153205559113772"
[[package]]
name = "activitypub_federation"
version = "0.7.0-beta.4"
version = "0.7.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91fb4c19ede0a628977cdc5d1ec7d1fb3e3e3e5e9de955bd0d5fed0c8cd5260d"
checksum = "e58f584c183501d7865b18d8b94c9477f750051ce99d54a399a2f4180667574c"
dependencies = [
"activitystreams-kinds",
"actix-web",
@ -4056,7 +4056,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets 0.53.0",
"windows-targets 0.52.6",
]
[[package]]
@ -7976,29 +7976,13 @@ dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b"
dependencies = [
"windows_aarch64_gnullvm 0.53.0",
"windows_aarch64_msvc 0.53.0",
"windows_i686_gnu 0.53.0",
"windows_i686_gnullvm 0.53.0",
"windows_i686_msvc 0.53.0",
"windows_x86_64_gnu 0.53.0",
"windows_x86_64_gnullvm 0.53.0",
"windows_x86_64_msvc 0.53.0",
]
[[package]]
name = "windows-threading"
version = "0.1.0"
@ -8020,12 +8004,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
@ -8038,12 +8016,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
@ -8056,24 +8028,12 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
@ -8086,12 +8046,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
@ -8104,12 +8058,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
@ -8122,12 +8070,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
@ -8140,12 +8082,6 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
[[package]]
name = "winnow"
version = "0.7.10"

View file

@ -140,7 +140,7 @@ lemmy_db_views_reports = { version = "=1.0.0-alpha.5", path = "./crates/db_views
lemmy_db_views_search_combined = { version = "=1.0.0-alpha.5", path = "./crates/db_views/search_combined" }
lemmy_db_views_site = { version = "=1.0.0-alpha.5", path = "./crates/db_views/site" }
lemmy_db_views_vote = { version = "=1.0.0-alpha.5", path = "./crates/db_views/vote" }
activitypub_federation = { version = "0.7.0-beta.4", default-features = false, features = [
activitypub_federation = { version = "0.7.0-beta.5", default-features = false, features = [
"actix-web",
] }
diesel = { version = "2.2.10", features = [

View file

@ -18,6 +18,9 @@ doctest = false
[lints]
workspace = true
[features]
full = []
[dependencies]
lemmy_db_views_comment = { workspace = true, features = ["full"] }
lemmy_db_views_community = { workspace = true, features = ["full"] }

View file

@ -13,7 +13,7 @@ use crate::{
use activitypub_federation::{
config::Data,
kinds::activity::BlockType,
traits::{ActivityHandler, Actor},
traits::{Activity, Actor, Object},
};
use chrono::{DateTime, Utc};
use lemmy_api_utils::{
@ -48,11 +48,11 @@ impl BlockUser {
) -> LemmyResult<BlockUser> {
let to = to(target)?;
Ok(BlockUser {
actor: mod_.id().into(),
actor: mod_.id().clone().into(),
to,
object: user.id().into(),
object: user.id().clone().into(),
cc: generate_cc(target, &mut context.pool()).await?,
target: target.id(),
target: target.id().clone().into(),
kind: BlockType::Block,
remove_data,
summary: reason,
@ -82,11 +82,11 @@ impl BlockUser {
.await?;
match target {
SiteOrCommunity::Site(_) => {
SiteOrCommunity::Left(_) => {
let inboxes = ActivitySendTargets::to_all_instances();
send_lemmy_activity(context, block, mod_, inboxes, false).await
}
SiteOrCommunity::Community(c) => {
SiteOrCommunity::Right(c) => {
let activity = AnnouncableActivities::BlockUser(block);
let inboxes = ActivitySendTargets::to_inbox(user.shared_inbox_or_inbox());
send_activity_in_community(activity, mod_, c, inboxes, true, context).await
@ -96,7 +96,7 @@ impl BlockUser {
}
#[async_trait::async_trait]
impl ActivityHandler for BlockUser {
impl Activity for BlockUser {
type DataType = LemmyContext;
type Error = LemmyError;
@ -110,10 +110,10 @@ impl ActivityHandler for BlockUser {
async fn verify(&self, context: &Data<LemmyContext>) -> LemmyResult<()> {
match self.target.dereference(context).await? {
SiteOrCommunity::Site(_site) => {
SiteOrCommunity::Left(_site) => {
verify_is_public(&self.to, &self.cc)?;
}
SiteOrCommunity::Community(community) => {
SiteOrCommunity::Right(community) => {
verify_visibility(&self.to, &self.cc, &community)?;
verify_person_in_community(&self.actor, &community, context).await?;
verify_mod_action(&self.actor, &community, context).await?;
@ -130,7 +130,7 @@ impl ActivityHandler for BlockUser {
let reason = self.summary;
let pool = &mut context.pool();
match target {
SiteOrCommunity::Site(site) => {
SiteOrCommunity::Left(site) => {
let form = InstanceBanForm::new(blocked_person.id, site.instance_id, expires_at);
InstanceActions::ban(pool, &form).await?;
@ -155,7 +155,7 @@ impl ActivityHandler for BlockUser {
};
ModBan::create(&mut context.pool(), &form).await?;
}
SiteOrCommunity::Community(community) => {
SiteOrCommunity::Right(community) => {
let community_user_ban_form = CommunityPersonBanForm {
ban_expires_at: Some(expires_at),
..CommunityPersonBanForm::new(community.id, blocked_person.id)

View file

@ -1,15 +1,9 @@
use crate::protocol::activities::block::{block_user::BlockUser, undo_block_user::UndoBlockUser};
use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
kinds::public,
traits::{Actor, Object},
};
use chrono::{DateTime, Utc};
use activitypub_federation::{config::Data, kinds::public, traits::Object};
use either::Either;
use lemmy_api_utils::{context::LemmyContext, utils::check_expire_time};
use lemmy_apub_objects::{
objects::{community::ApubCommunity, instance::ApubSite},
protocol::{group::Group, instance::Instance},
utils::functions::generate_to,
};
use lemmy_db_schema::{
@ -19,106 +13,22 @@ use lemmy_db_schema::{
utils::DbPool,
};
use lemmy_db_views_community::api::BanFromCommunity;
use lemmy_utils::error::{LemmyError, LemmyResult};
use serde::Deserialize;
use lemmy_utils::error::LemmyResult;
use url::Url;
pub mod block_user;
pub mod undo_block_user;
#[derive(Clone, Debug)]
pub enum SiteOrCommunity {
Site(ApubSite),
Community(ApubCommunity),
}
#[derive(Deserialize)]
#[serde(untagged)]
pub enum InstanceOrGroup {
Instance(Box<Instance>),
Group(Box<Group>),
}
#[async_trait::async_trait]
impl Object for SiteOrCommunity {
type DataType = LemmyContext;
type Kind = InstanceOrGroup;
type Error = LemmyError;
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
Some(match self {
SiteOrCommunity::Site(i) => i.last_refreshed_at,
SiteOrCommunity::Community(c) => c.last_refreshed_at,
})
}
async fn read_from_id(object_id: Url, data: &Data<Self::DataType>) -> LemmyResult<Option<Self>>
where
Self: Sized,
{
let site = ApubSite::read_from_id(object_id.clone(), data).await?;
Ok(match site {
Some(o) => Some(SiteOrCommunity::Site(o)),
None => ApubCommunity::read_from_id(object_id, data)
.await?
.map(SiteOrCommunity::Community),
})
}
async fn delete(self, data: &Data<Self::DataType>) -> LemmyResult<()> {
match self {
SiteOrCommunity::Site(i) => i.delete(data).await,
SiteOrCommunity::Community(c) => c.delete(data).await,
}
}
async fn into_json(self, data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
Ok(match self {
SiteOrCommunity::Site(i) => InstanceOrGroup::Instance(Box::new(i.into_json(data).await?)),
SiteOrCommunity::Community(c) => InstanceOrGroup::Group(Box::new(c.into_json(data).await?)),
})
}
async fn verify(
apub: &Self::Kind,
expected_domain: &Url,
data: &Data<Self::DataType>,
) -> LemmyResult<()> {
match apub {
InstanceOrGroup::Instance(i) => ApubSite::verify(i, expected_domain, data).await,
InstanceOrGroup::Group(g) => ApubCommunity::verify(g, expected_domain, data).await,
}
}
async fn from_json(apub: Self::Kind, data: &Data<Self::DataType>) -> LemmyResult<Self>
where
Self: Sized,
{
Ok(match apub {
InstanceOrGroup::Instance(p) => SiteOrCommunity::Site(ApubSite::from_json(*p, data).await?),
InstanceOrGroup::Group(n) => {
SiteOrCommunity::Community(ApubCommunity::from_json(*n, data).await?)
}
})
}
}
impl SiteOrCommunity {
fn id(&self) -> ObjectId<SiteOrCommunity> {
match self {
SiteOrCommunity::Site(s) => ObjectId::from(s.ap_id.clone()),
SiteOrCommunity::Community(c) => ObjectId::from(c.ap_id.clone()),
}
}
}
pub type SiteOrCommunity = Either<ApubSite, ApubCommunity>;
async fn generate_cc(target: &SiteOrCommunity, pool: &mut DbPool<'_>) -> LemmyResult<Vec<Url>> {
Ok(match target {
SiteOrCommunity::Site(_) => Site::read_remote_sites(pool)
SiteOrCommunity::Left(_) => Site::read_remote_sites(pool)
.await?
.into_iter()
.map(|s| s.ap_id.into())
.collect(),
SiteOrCommunity::Community(c) => vec![c.id()],
SiteOrCommunity::Right(c) => vec![c.id().clone()],
})
}
@ -131,7 +41,7 @@ pub(crate) async fn send_ban_from_site(
expires: Option<i64>,
context: Data<LemmyContext>,
) -> LemmyResult<()> {
let site = SiteOrCommunity::Site(Site::read_local(&mut context.pool()).await?.into());
let site = SiteOrCommunity::Left(Site::read_local(&mut context.pool()).await?.into());
let expires = check_expire_time(expires)?;
if ban {
@ -172,7 +82,7 @@ pub(crate) async fn send_ban_from_community(
if data.ban {
BlockUser::send(
&SiteOrCommunity::Community(community),
&SiteOrCommunity::Right(community),
&banned_person.into(),
&mod_.into(),
data.remove_or_restore_data.unwrap_or(false),
@ -183,7 +93,7 @@ pub(crate) async fn send_ban_from_community(
.await
} else {
UndoBlockUser::send(
&SiteOrCommunity::Community(community),
&SiteOrCommunity::Right(community),
&banned_person.into(),
&mod_.into(),
data.remove_or_restore_data.unwrap_or(false),
@ -195,7 +105,7 @@ pub(crate) async fn send_ban_from_community(
}
fn to(target: &SiteOrCommunity) -> LemmyResult<Vec<Url>> {
Ok(if let SiteOrCommunity::Community(c) = target {
Ok(if let SiteOrCommunity::Right(c) = target {
generate_to(c)?
} else {
vec![public()]

View file

@ -13,7 +13,7 @@ use activitypub_federation::{
config::Data,
kinds::activity::UndoType,
protocol::verification::verify_domains_match,
traits::{ActivityHandler, Actor},
traits::{Activity, Actor, Object},
};
use lemmy_api_utils::{
context::LemmyContext,
@ -49,7 +49,7 @@ impl UndoBlockUser {
let id = generate_activity_id(UndoType::Undo, context)?;
let undo = UndoBlockUser {
actor: mod_.id().into(),
actor: mod_.id().clone().into(),
to,
object: block,
cc: generate_cc(target, &mut context.pool()).await?,
@ -60,11 +60,11 @@ impl UndoBlockUser {
let mut inboxes = ActivitySendTargets::to_inbox(user.shared_inbox_or_inbox());
match target {
SiteOrCommunity::Site(_) => {
SiteOrCommunity::Left(_) => {
inboxes.set_all_instances();
send_lemmy_activity(context, undo, mod_, inboxes, false).await
}
SiteOrCommunity::Community(c) => {
SiteOrCommunity::Right(c) => {
let activity = AnnouncableActivities::UndoBlockUser(undo);
send_activity_in_community(activity, mod_, c, inboxes, true, context).await
}
@ -73,7 +73,7 @@ impl UndoBlockUser {
}
#[async_trait::async_trait]
impl ActivityHandler for UndoBlockUser {
impl Activity for UndoBlockUser {
type DataType = LemmyContext;
type Error = LemmyError;
@ -97,7 +97,7 @@ impl ActivityHandler for UndoBlockUser {
let blocked_person = self.object.object.dereference(context).await?;
let pool = &mut context.pool();
match self.object.target.dereference(context).await? {
SiteOrCommunity::Site(site) => {
SiteOrCommunity::Left(site) => {
verify_is_public(&self.to, &self.cc)?;
let form = InstanceBanForm::new(blocked_person.id, site.instance_id, expires_at);
InstanceActions::unban(pool, &form).await?;
@ -123,7 +123,7 @@ impl ActivityHandler for UndoBlockUser {
};
ModBan::create(&mut context.pool(), &form).await?;
}
SiteOrCommunity::Community(community) => {
SiteOrCommunity::Right(community) => {
verify_visibility(&self.to, &self.cc, &community)?;
let community_user_ban_form = CommunityPersonBanForm::new(community.id, blocked_person.id);
CommunityActions::unban(&mut context.pool(), &community_user_ban_form).await?;

View file

@ -9,7 +9,7 @@ use crate::{
use activitypub_federation::{
config::Data,
kinds::activity::AnnounceType,
traits::{ActivityHandler, Actor},
traits::{Activity, Object},
};
use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::{
@ -25,7 +25,7 @@ use serde_json::Value;
use url::Url;
#[async_trait::async_trait]
impl ActivityHandler for RawAnnouncableActivities {
impl Activity for RawAnnouncableActivities {
type DataType = LemmyContext;
type Error = LemmyError;
@ -70,6 +70,12 @@ impl ActivityHandler for RawAnnouncableActivities {
}
}
impl Id for RawAnnouncableActivities {
fn id(&self) -> &Url {
&self.id
}
}
impl AnnounceActivity {
pub(crate) fn new(
object: RawAnnouncableActivities,
@ -84,7 +90,7 @@ impl AnnounceActivity {
let id =
generate_announce_activity_id(inner_kind, &context.settings().get_protocol_and_hostname())?;
Ok(AnnounceActivity {
actor: community.id().into(),
actor: community.id().clone().into(),
to: generate_to(community)?,
object: IdOrNestedObject::NestedObject(object),
cc: community
@ -129,7 +135,7 @@ impl AnnounceActivity {
}
#[async_trait::async_trait]
impl ActivityHandler for AnnounceActivity {
impl Activity for AnnounceActivity {
type DataType = LemmyContext;
type Error = LemmyError;
@ -163,12 +169,6 @@ impl ActivityHandler for AnnounceActivity {
}
}
impl Id for RawAnnouncableActivities {
fn object_id(&self) -> &Url {
ActivityHandler::id(self)
}
}
impl TryFrom<RawAnnouncableActivities> for AnnouncableActivities {
type Error = serde_json::error::Error;

View file

@ -10,7 +10,7 @@ use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
kinds::activity::AddType,
traits::{ActivityHandler, Actor},
traits::{Activity, Actor, Object},
};
use lemmy_api_utils::{
context::LemmyContext,
@ -47,11 +47,11 @@ impl CollectionAdd {
) -> LemmyResult<()> {
let id = generate_activity_id(AddType::Add, context)?;
let add = CollectionAdd {
actor: actor.id().into(),
actor: actor.id().clone().into(),
to: generate_to(community)?,
object: added_mod.id(),
object: added_mod.id().clone(),
target: generate_moderators_url(&community.ap_id)?.into(),
cc: vec![community.id()],
cc: vec![community.id().clone()],
kind: AddType::Add,
id: id.clone(),
};
@ -69,11 +69,11 @@ impl CollectionAdd {
) -> LemmyResult<()> {
let id = generate_activity_id(AddType::Add, context)?;
let add = CollectionAdd {
actor: actor.id().into(),
actor: actor.id().clone().into(),
to: generate_to(community)?,
object: featured_post.ap_id.clone().into(),
target: generate_featured_url(&community.ap_id)?.into(),
cc: vec![community.id()],
cc: vec![community.id().clone()],
kind: AddType::Add,
id: id.clone(),
};
@ -91,7 +91,7 @@ impl CollectionAdd {
}
#[async_trait::async_trait]
impl ActivityHandler for CollectionAdd {
impl Activity for CollectionAdd {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -7,7 +7,7 @@ use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
kinds::activity::RemoveType,
traits::{ActivityHandler, Actor},
traits::{Activity, Actor, Object},
};
use lemmy_api_utils::{
context::LemmyContext,
@ -42,12 +42,12 @@ impl CollectionRemove {
) -> LemmyResult<()> {
let id = generate_activity_id(RemoveType::Remove, context)?;
let remove = CollectionRemove {
actor: actor.id().into(),
actor: actor.id().clone().into(),
to: generate_to(community)?,
object: removed_mod.id(),
object: removed_mod.id().clone(),
target: generate_moderators_url(&community.ap_id)?.into(),
id: id.clone(),
cc: vec![community.id()],
cc: vec![community.id().clone()],
kind: RemoveType::Remove,
};
@ -64,11 +64,11 @@ impl CollectionRemove {
) -> LemmyResult<()> {
let id = generate_activity_id(RemoveType::Remove, context)?;
let remove = CollectionRemove {
actor: actor.id().into(),
actor: actor.id().clone().into(),
to: generate_to(community)?,
object: featured_post.ap_id.clone().into(),
target: generate_featured_url(&community.ap_id)?.into(),
cc: vec![community.id()],
cc: vec![community.id().clone()],
kind: RemoveType::Remove,
id: id.clone(),
};
@ -86,7 +86,7 @@ impl CollectionRemove {
}
#[async_trait::async_trait]
impl ActivityHandler for CollectionRemove {
impl Activity for CollectionRemove {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -12,7 +12,7 @@ use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
kinds::activity::UndoType,
traits::ActivityHandler,
traits::Activity,
};
use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::{
@ -36,7 +36,7 @@ use lemmy_utils::error::{LemmyError, LemmyResult};
use url::Url;
#[async_trait::async_trait]
impl ActivityHandler for LockPage {
impl Activity for LockPage {
type DataType = LemmyContext;
type Error = LemmyError;
@ -80,7 +80,7 @@ impl ActivityHandler for LockPage {
}
#[async_trait::async_trait]
impl ActivityHandler for UndoLockPage {
impl Activity for UndoLockPage {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -11,7 +11,7 @@ use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
kinds::activity::FlagType,
traits::{ActivityHandler, Actor},
traits::{Activity, Object},
};
use either::Either;
use lemmy_api_utils::{
@ -54,8 +54,8 @@ impl Report {
let kind = FlagType::Flag;
let id = generate_activity_id(kind.clone(), context)?;
Ok(Report {
actor: actor.id().into(),
to: [receiver.id().into()],
actor: actor.id().clone().into(),
to: [receiver.id().clone().into()],
object: ReportObject::Lemmy(object_id.clone()),
summary: reason,
content: None,
@ -79,7 +79,7 @@ impl Report {
}
#[async_trait::async_trait]
impl ActivityHandler for Report {
impl Activity for Report {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -12,7 +12,7 @@ use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor},
traits::{Activity, Object},
};
use either::Either;
use lemmy_api_utils::context::LemmyContext;
@ -49,8 +49,8 @@ impl ResolveReport {
let id = generate_activity_id(kind.clone(), &context)?;
let object = Report::new(&object_id, report_creator, receiver, None, &context)?;
let resolve = ResolveReport {
actor: actor.id().into(),
to: [receiver.id().into()],
actor: actor.id().clone().into(),
to: [receiver.id().clone().into()],
object,
kind,
id: id.clone(),
@ -62,7 +62,7 @@ impl ResolveReport {
}
#[async_trait::async_trait]
impl ActivityHandler for ResolveReport {
impl Activity for ResolveReport {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -11,7 +11,7 @@ use crate::{
use activitypub_federation::{
config::Data,
kinds::{activity::UpdateType, public},
traits::{ActivityHandler, Actor, Object},
traits::{Activity, Object},
};
use either::Either;
use lemmy_api_utils::context::LemmyContext;
@ -44,10 +44,10 @@ pub(crate) async fn send_update_community(
let actor: ApubPerson = actor.into();
let id = generate_activity_id(UpdateType::Update, &context)?;
let update = Update {
actor: actor.id().into(),
actor: actor.id().clone().into(),
to: generate_to(&community)?,
object: Either::Left(community.clone().into_json(&context).await?),
cc: vec![community.id()],
cc: vec![community.id().clone()],
kind: UpdateType::Update,
id: id.clone(),
};
@ -73,7 +73,7 @@ pub(crate) async fn send_update_multi_community(
let actor: ApubPerson = actor.into();
let id = generate_activity_id(UpdateType::Update, &context)?;
let update = Update {
actor: actor.id().into(),
actor: actor.id().clone().into(),
to: vec![multi.ap_id.clone().into(), public()],
object: Either::Right(multi.clone().into_json(&context).await?),
cc: vec![],
@ -88,7 +88,7 @@ pub(crate) async fn send_update_multi_community(
}
#[async_trait::async_trait]
impl ActivityHandler for Update {
impl Activity for Update {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -11,7 +11,7 @@ use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
protocol::verification::{verify_domains_match, verify_urls_match},
traits::{ActivityHandler, Actor, Object},
traits::{Activity, Actor, Object},
};
use lemmy_api_utils::{
build_response::send_local_notifs,
@ -65,7 +65,7 @@ impl CreateOrUpdateNote {
let note = ApubComment(comment).into_json(&context).await?;
let create_or_update = CreateOrUpdateNote {
actor: person.id().into(),
actor: person.id().clone().into(),
to: generate_to(&community)?,
cc: note.cc.clone(),
tag: note.tag.clone(),
@ -103,7 +103,7 @@ impl CreateOrUpdateNote {
}
#[async_trait::async_trait]
impl ActivityHandler for CreateOrUpdateNote {
impl Activity for CreateOrUpdateNote {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -3,7 +3,7 @@ use crate::protocol::activities::create_or_update::{
note_wrapper::CreateOrUpdateNoteWrapper,
private_message::CreateOrUpdatePrivateMessage,
};
use activitypub_federation::{config::Data, traits::ActivityHandler};
use activitypub_federation::{config::Data, traits::Activity};
use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::{objects::community::ApubCommunity, utils::protocol::InCommunity};
use lemmy_utils::error::{LemmyError, LemmyResult};
@ -14,7 +14,7 @@ use url::Url;
/// makes it difficult to distinguish them. This wrapper handles receiving of both types, and
/// routes them to the correct handler.
#[async_trait::async_trait]
impl ActivityHandler for CreateOrUpdateNoteWrapper {
impl Activity for CreateOrUpdateNoteWrapper {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -10,7 +10,7 @@ use crate::{
use activitypub_federation::{
config::Data,
protocol::verification::{verify_domains_match, verify_urls_match},
traits::{ActivityHandler, Actor, Object},
traits::{Activity, Object},
};
use lemmy_api_utils::{build_response::send_local_notifs, context::LemmyContext};
use lemmy_apub_objects::{
@ -47,10 +47,10 @@ impl CreateOrUpdatePage {
) -> LemmyResult<CreateOrUpdatePage> {
let id = generate_activity_id(kind.clone(), context)?;
Ok(CreateOrUpdatePage {
actor: actor.id().into(),
actor: actor.id().clone().into(),
to: generate_to(community)?,
object: post.into_json(context).await?,
cc: vec![community.id()],
cc: vec![community.id().clone()],
kind,
id: id.clone(),
})
@ -85,7 +85,7 @@ impl CreateOrUpdatePage {
}
#[async_trait::async_trait]
impl ActivityHandler for CreateOrUpdatePage {
impl Activity for CreateOrUpdatePage {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -8,7 +8,7 @@ use crate::{
use activitypub_federation::{
config::Data,
protocol::verification::{verify_domains_match, verify_urls_match},
traits::{ActivityHandler, Actor, Object},
traits::{Activity, Actor, Object},
};
use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::objects::{person::ApubPerson, private_message::ApubPrivateMessage};
@ -28,8 +28,8 @@ pub(crate) async fn send_create_or_update_pm(
let id = generate_activity_id(kind.clone(), &context)?;
let create_or_update = CreateOrUpdatePrivateMessage {
id: id.clone(),
actor: actor.id().into(),
to: [recipient.id().into()],
actor: actor.id().clone().into(),
to: [recipient.id().clone().into()],
object: ApubPrivateMessage(pm_view.private_message.clone())
.into_json(&context)
.await?,
@ -40,7 +40,7 @@ pub(crate) async fn send_create_or_update_pm(
}
#[async_trait::async_trait]
impl ActivityHandler for CreateOrUpdatePrivateMessage {
impl Activity for CreateOrUpdatePrivateMessage {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -5,7 +5,7 @@ use crate::{
},
protocol::{activities::deletion::delete::Delete, IdOrNestedObject},
};
use activitypub_federation::{config::Data, kinds::activity::DeleteType, traits::ActivityHandler};
use activitypub_federation::{config::Data, kinds::activity::DeleteType, traits::Activity};
use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::objects::person::ApubPerson;
use lemmy_db_schema::{
@ -31,7 +31,7 @@ use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorType, LemmyResul
use url::Url;
#[async_trait::async_trait]
impl ActivityHandler for Delete {
impl Activity for Delete {
type DataType = LemmyContext;
type Error = LemmyError;
@ -91,7 +91,7 @@ impl Delete {
Ok(Delete {
actor: actor.ap_id.clone().into(),
to,
object: IdOrNestedObject::Id(object.id()),
object: IdOrNestedObject::Id(object.id().clone()),
cc: cc.into_iter().collect(),
kind: DeleteType::Delete,
summary,

View file

@ -93,10 +93,24 @@ pub(crate) async fn send_apub_delete_private_message(
let deletable = DeletableObjects::PrivateMessage(pm.into());
let inbox = ActivitySendTargets::to_inbox(recipient.shared_inbox_or_inbox());
if deleted {
let delete: Delete = Delete::new(actor, deletable, vec![recipient.id()], None, None, &context)?;
let delete: Delete = Delete::new(
actor,
deletable,
vec![recipient.id().clone()],
None,
None,
&context,
)?;
send_lemmy_activity(&context, delete, actor, inbox, true).await?;
} else {
let undo = UndoDelete::new(actor, deletable, vec![recipient.id()], None, None, &context)?;
let undo = UndoDelete::new(
actor,
deletable,
vec![recipient.id().clone()],
None,
None,
&context,
)?;
send_lemmy_activity(&context, undo, actor, inbox, true).await?;
};
Ok(())
@ -150,13 +164,13 @@ impl DeletableObjects {
Err(diesel::NotFound.into())
}
pub(crate) fn id(&self) -> Url {
pub(crate) fn id(&self) -> &Url {
match self {
DeletableObjects::Community(c) => c.id(),
DeletableObjects::Person(p) => p.id(),
DeletableObjects::Comment(c) => c.ap_id.clone().into(),
DeletableObjects::Post(p) => p.ap_id.clone().into(),
DeletableObjects::PrivateMessage(p) => p.ap_id.clone().into(),
DeletableObjects::Comment(c) => c.ap_id.inner(),
DeletableObjects::Post(p) => p.ap_id.inner(),
DeletableObjects::PrivateMessage(p) => p.ap_id.inner(),
}
}
}

View file

@ -5,7 +5,7 @@ use crate::{
},
protocol::activities::deletion::{delete::Delete, undo_delete::UndoDelete},
};
use activitypub_federation::{config::Data, kinds::activity::UndoType, traits::ActivityHandler};
use activitypub_federation::{config::Data, kinds::activity::UndoType, traits::Activity};
use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::objects::person::ApubPerson;
use lemmy_db_schema::{
@ -28,7 +28,7 @@ use lemmy_utils::error::{FederationError, LemmyError, LemmyErrorType, LemmyResul
use url::Url;
#[async_trait::async_trait]
impl ActivityHandler for UndoDelete {
impl Activity for UndoDelete {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -6,7 +6,7 @@ use activitypub_federation::{
config::Data,
kinds::activity::AcceptType,
protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor},
traits::{Activity, Actor, Object},
};
use lemmy_api_utils::context::LemmyContext;
use lemmy_db_schema::{
@ -21,8 +21,8 @@ impl AcceptFollow {
let target = follow.object.dereference_local(context).await?;
let person = follow.actor.clone().dereference(context).await?;
let accept = AcceptFollow {
actor: target.id().into(),
to: Some([person.id().into()]),
actor: target.id().clone().into(),
to: Some([person.id().clone().into()]),
object: follow,
kind: AcceptType::Accept,
id: generate_activity_id(AcceptType::Accept, context)?,
@ -34,7 +34,7 @@ impl AcceptFollow {
/// Handle accepted follows
#[async_trait::async_trait]
impl ActivityHandler for AcceptFollow {
impl Activity for AcceptFollow {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -6,7 +6,7 @@ use activitypub_federation::{
config::Data,
kinds::activity::FollowType,
protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor},
traits::{Activity, Actor, Object},
};
use either::Either::*;
use lemmy_api_utils::context::LemmyContext;
@ -35,9 +35,9 @@ impl Follow {
context: &Data<LemmyContext>,
) -> LemmyResult<Follow> {
Ok(Follow {
actor: actor.id().into(),
object: target.id().into(),
to: Some([target.id().into()]),
actor: actor.id().clone().into(),
object: target.id().clone().into(),
to: Some([target.id().clone().into()]),
kind: FollowType::Follow,
id: generate_activity_id(FollowType::Follow, context)?,
})
@ -55,7 +55,7 @@ impl Follow {
}
#[async_trait::async_trait]
impl ActivityHandler for Follow {
impl Activity for Follow {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -5,7 +5,7 @@ use crate::protocol::activities::following::{
reject::RejectFollow,
undo_follow::UndoFollow,
};
use activitypub_federation::{config::Data, kinds::activity::FollowType, traits::ActivityHandler};
use activitypub_federation::{config::Data, kinds::activity::FollowType, traits::Activity};
use either::Either::*;
use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::objects::{person::ApubPerson, CommunityOrMulti, UserOrCommunityOrMulti};
@ -60,14 +60,14 @@ pub async fn send_accept_or_reject_follow(
}
/// Wrapper type which is needed because we cant implement ActorT for Either.
async fn send_activity_from_user_or_community_or_multi<Activity>(
async fn send_activity_from_user_or_community_or_multi<A>(
context: &Data<LemmyContext>,
activity: Activity,
activity: A,
target: UserOrCommunityOrMulti,
send_targets: ActivitySendTargets,
) -> LemmyResult<()>
where
Activity: ActivityHandler + Serialize + Send + Sync + Clone + ActivityHandler<Error = LemmyError>,
A: Activity + Serialize + Send + Sync + Clone + Activity<Error = LemmyError>,
{
match target {
Left(user) => send_lemmy_activity(context, activity, &user, send_targets, true).await,

View file

@ -7,7 +7,7 @@ use activitypub_federation::{
config::Data,
kinds::activity::RejectType,
protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor},
traits::{Activity, Actor, Object},
};
use lemmy_api_utils::context::LemmyContext;
use lemmy_db_schema::{
@ -22,8 +22,8 @@ impl RejectFollow {
let user_or_community = follow.object.dereference_local(context).await?;
let person = follow.actor.clone().dereference(context).await?;
let reject = RejectFollow {
actor: user_or_community.id().into(),
to: Some([person.id().into()]),
actor: user_or_community.id().clone().into(),
to: Some([person.id().clone().into()]),
object: follow,
kind: RejectType::Reject,
id: generate_activity_id(RejectType::Reject, context)?,
@ -35,7 +35,7 @@ impl RejectFollow {
/// Handle rejected follows
#[async_trait::async_trait]
impl ActivityHandler for RejectFollow {
impl Activity for RejectFollow {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -6,7 +6,7 @@ use activitypub_federation::{
config::Data,
kinds::activity::UndoType,
protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor},
traits::{Activity, Actor, Object},
};
use either::Either::*;
use lemmy_api_utils::context::LemmyContext;
@ -31,8 +31,8 @@ impl UndoFollow {
) -> LemmyResult<()> {
let object = Follow::new(actor, target, context)?;
let undo = UndoFollow {
actor: actor.id().into(),
to: Some([target.id().into()]),
actor: actor.id().clone().into(),
to: Some([target.id().clone().into()]),
object,
kind: UndoType::Undo,
id: generate_activity_id(UndoType::Undo, context)?,
@ -43,7 +43,7 @@ impl UndoFollow {
}
#[async_trait::async_trait]
impl ActivityHandler for UndoFollow {
impl Activity for UndoFollow {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -26,7 +26,7 @@ use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
kinds::activity::AnnounceType,
traits::{ActivityHandler, Actor},
traits::{Activity, Actor},
};
use either::Either;
use following::send_accept_or_reject_follow;
@ -137,15 +137,15 @@ fn generate_announce_activity_id(
Url::parse(&id)
}
async fn send_lemmy_activity<Activity, ActorT>(
async fn send_lemmy_activity<A, ActorT>(
data: &Data<LemmyContext>,
activity: Activity,
activity: A,
actor: &ActorT,
send_targets: ActivitySendTargets,
sensitive: bool,
) -> LemmyResult<()>
where
Activity: ActivityHandler + Serialize + Send + Sync + Clone + ActivityHandler<Error = LemmyError>,
A: Activity + Serialize + Send + Sync + Clone + Activity<Error = LemmyError>,
ActorT: Actor + GetActorType,
{
info!("Saving outgoing activity to queue {}", activity.id());
@ -162,7 +162,7 @@ where
send_all_instances: send_targets.all_instances,
send_community_followers_of: send_targets.community_followers_of.map(|e| e.0),
actor_type: actor.actor_type(),
actor_apub_id: actor.id().into(),
actor_apub_id: actor.id().clone().into(),
};
SentActivity::create(&mut data.pool(), form).await?;

View file

@ -9,7 +9,7 @@ use activitypub_federation::{
config::Data,
kinds::activity::UndoType,
protocol::verification::verify_urls_match,
traits::{ActivityHandler, Actor},
traits::{Activity, Object},
};
use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::{
@ -26,7 +26,7 @@ impl UndoVote {
context: &Data<LemmyContext>,
) -> LemmyResult<Self> {
Ok(UndoVote {
actor: actor.id().into(),
actor: actor.id().clone().into(),
object: vote,
kind: UndoType::Undo,
id: generate_activity_id(UndoType::Undo, context)?,
@ -35,7 +35,7 @@ impl UndoVote {
}
#[async_trait::async_trait]
impl ActivityHandler for UndoVote {
impl Activity for UndoVote {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -8,7 +8,7 @@ use crate::{
use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
traits::{ActivityHandler, Actor},
traits::{Activity, Object},
};
use lemmy_api_utils::{context::LemmyContext, utils::check_bot_account};
use lemmy_apub_objects::{
@ -28,7 +28,7 @@ impl Vote {
context: &Data<LemmyContext>,
) -> LemmyResult<Vote> {
Ok(Vote {
actor: actor.id().into(),
actor: actor.id().clone().into(),
object: object_id,
kind: kind.clone(),
id: generate_activity_id(kind, context)?,
@ -37,7 +37,7 @@ impl Vote {
}
#[async_trait::async_trait]
impl ActivityHandler for Vote {
impl Activity for Vote {
type DataType = LemmyContext;
type Error = LemmyError;

View file

@ -19,7 +19,7 @@ use crate::protocol::activities::{
},
voting::{undo_vote::UndoVote, vote::Vote},
};
use activitypub_federation::{config::Data, traits::ActivityHandler};
use activitypub_federation::{config::Data, traits::Activity};
use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::{
objects::community::ApubCommunity,
@ -37,7 +37,7 @@ use url::Url;
/// are handled correctly.
#[derive(Debug, Deserialize, Serialize, Clone)]
#[serde(untagged)]
#[enum_delegate::implement(ActivityHandler)]
#[enum_delegate::implement(Activity)]
pub(crate) enum SharedInboxActivities {
Follow(Follow),
AcceptFollow(AcceptFollow),
@ -52,7 +52,7 @@ pub(crate) enum SharedInboxActivities {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
#[enum_delegate::implement(ActivityHandler)]
#[enum_delegate::implement(Activity)]
pub enum AnnouncableActivities {
CreateOrUpdateNoteWrapper(CreateOrUpdateNoteWrapper),
CreateOrUpdatePost(CreateOrUpdatePage),

View file

@ -13,7 +13,7 @@ use activitypub_federation::{
config::Data,
kinds::collection::OrderedCollectionType,
protocol::verification::verify_domains_match,
traits::{ActivityHandler, Collection},
traits::{Activity, Collection},
};
use futures::future::join_all;
use lemmy_api_utils::{context::LemmyContext, utils::generate_outbox_url};

View file

@ -1,5 +1,4 @@
use super::check_community_content_fetchable;
use crate::http::{create_apub_response, create_apub_tombstone_response, redirect_remote_object};
use activitypub_federation::{config::Data, traits::Object};
use actix_web::{web::Path, HttpRequest, HttpResponse};
use lemmy_api_utils::context::LemmyContext;
@ -9,7 +8,7 @@ use lemmy_db_schema::{
source::{comment::Comment, community::Community, post::Post},
traits::Crud,
};
use lemmy_utils::error::LemmyResult;
use lemmy_utils::{error::LemmyResult, FEDERATION_CONTEXT};
use serde::Deserialize;
#[derive(Deserialize)]
@ -30,11 +29,5 @@ pub(crate) async fn get_apub_comment(
let community = Community::read(&mut context.pool(), post.community_id).await?;
check_community_content_fetchable(&community, &request, &context).await?;
if !comment.local {
Ok(redirect_remote_object(&comment.ap_id))
} else if !comment.deleted && !comment.removed {
create_apub_response(&comment.into_json(&context).await?)
} else {
create_apub_tombstone_response(comment.ap_id.clone())
}
comment.http_response(&FEDERATION_CONTEXT, &context).await
}

View file

@ -7,10 +7,10 @@ use crate::{
community_outbox::ApubCommunityOutbox,
},
fetcher::get_instance_id,
http::{check_community_fetchable, create_apub_response, create_apub_tombstone_response},
http::check_community_fetchable,
};
use activitypub_federation::{
actix_web::signing_actor,
actix_web::{response::create_http_response, signing_actor},
config::Data,
fetch::object_id::ObjectId,
traits::{Collection, Object},
@ -33,7 +33,10 @@ use lemmy_db_schema::{
};
use lemmy_db_schema_file::enums::CommunityVisibility;
use lemmy_db_views_community_follower::CommunityFollowerView;
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
use lemmy_utils::{
error::{LemmyErrorType, LemmyResult},
FEDERATION_CONTEXT,
};
use serde::Deserialize;
#[derive(Deserialize, Clone)]
@ -57,13 +60,9 @@ pub(crate) async fn get_apub_community_http(
.ok_or(LemmyErrorType::NotFound)?
.into();
if community.deleted || community.removed {
return create_apub_tombstone_response(community.ap_id.clone());
}
check_community_fetchable(&community)?;
let apub = community.into_json(&context).await?;
create_apub_response(&apub)
community.http_response(&FEDERATION_CONTEXT, &context).await
}
/// Returns an empty followers collection, only populating the size (for privacy).
@ -81,7 +80,7 @@ pub(crate) async fn get_apub_community_followers(
}
check_community_fetchable(&community)?;
let followers = ApubCommunityFollower::read_local(&community.into(), &context).await?;
create_apub_response(&followers)
Ok(create_http_response(followers, &FEDERATION_CONTEXT)?)
}
/// Checks if a given actor follows the private community. Returns status 200 if true.
@ -132,7 +131,7 @@ pub(crate) async fn get_apub_community_outbox(
.into();
check_community_content_fetchable(&community, &request, &context).await?;
let outbox = ApubCommunityOutbox::read_local(&community, &context).await?;
create_apub_response(&outbox)
Ok(create_http_response(outbox, &FEDERATION_CONTEXT)?)
}
pub(crate) async fn get_apub_community_moderators(
@ -146,7 +145,7 @@ pub(crate) async fn get_apub_community_moderators(
.into();
check_community_fetchable(&community)?;
let moderators = ApubCommunityModerators::read_local(&community, &context).await?;
create_apub_response(&moderators)
Ok(create_http_response(moderators, &FEDERATION_CONTEXT)?)
}
/// Returns collection of featured (stickied) posts.
@ -162,7 +161,7 @@ pub(crate) async fn get_apub_community_featured(
.into();
check_community_content_fetchable(&community, &request, &context).await?;
let featured = ApubCommunityFeatured::read_local(&community, &context).await?;
create_apub_response(&featured)
Ok(create_http_response(featured, &FEDERATION_CONTEXT)?)
}
#[derive(Deserialize)]
@ -179,7 +178,7 @@ pub(crate) async fn get_apub_person_multi_community(
.await?
.into();
create_apub_response(&multi.into_json(&context).await?)
multi.http_response(&FEDERATION_CONTEXT, &context).await
}
pub(crate) async fn get_apub_person_multi_community_follows(
@ -191,15 +190,16 @@ pub(crate) async fn get_apub_person_multi_community_follows(
.into();
let collection = ApubFeedCollection::read_local(&multi, &context).await?;
create_apub_response(&collection)
Ok(create_http_response(collection, &FEDERATION_CONTEXT)?)
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use activitypub_federation::protocol::tombstone::Tombstone;
use actix_web::{body::to_bytes, test::TestRequest};
use lemmy_apub_objects::protocol::{group::Group, tombstone::Tombstone};
use lemmy_apub_objects::protocol::group::Group;
use lemmy_db_schema::{
source::{
community::CommunityInsertForm,
@ -211,6 +211,7 @@ pub(crate) mod tests {
};
use serde::de::DeserializeOwned;
use serial_test::serial;
use url::Url;
async fn init(
deleted: bool,
@ -221,6 +222,7 @@ pub(crate) mod tests {
let community_form = CommunityInsertForm {
deleted: Some(deleted),
ap_id: Some(Url::parse("http://lemmy-alpha")?.into()),
visibility: Some(visibility),
..CommunityInsertForm::new(
data.instance.id,
@ -308,7 +310,6 @@ pub(crate) mod tests {
let res = get_apub_community_outbox(path, context.clone(), request).await;
assert!(res.is_err());
//Community::delete(&mut context.pool(), community.id).await?;
data.delete(&mut context.pool()).await?;
Ok(())
}

View file

@ -2,30 +2,22 @@ use crate::{activity_lists::SharedInboxActivities, fetcher::get_instance_id};
use activitypub_federation::{
actix_web::{
inbox::{receive_activity_with_hook, ReceiveActivityHook},
response::create_http_response,
signing_actor,
},
config::Data,
protocol::context::WithContext,
traits::{ActivityHandler, Actor},
FEDERATION_CONTENT_TYPE,
traits::{Activity, Object},
};
use actix_web::{
http::header::VARY,
web::{self, Bytes},
HttpRequest,
HttpResponse,
};
use lemmy_api_utils::{context::LemmyContext, plugins::plugin_hook_after};
use lemmy_apub_objects::{
objects::{SiteOrMultiOrCommunityOrUser, UserOrCommunity},
protocol::tombstone::Tombstone,
};
use lemmy_db_schema::{
newtypes::DbUrl,
source::{
activity::{ReceivedActivity, SentActivity},
community::Community,
},
use lemmy_apub_objects::objects::{SiteOrMultiOrCommunityOrUser, UserOrCommunity};
use lemmy_db_schema::source::{
activity::{ReceivedActivity, SentActivity},
community::Community,
};
use lemmy_db_schema_file::enums::CommunityVisibility;
use lemmy_db_views_community_follower::CommunityFollowerView;
@ -33,8 +25,8 @@ use lemmy_utils::{
error::{FederationError, LemmyErrorExt, LemmyErrorType, LemmyResult},
FEDERATION_CONTEXT,
};
use serde::{Deserialize, Serialize};
use std::{ops::Deref, time::Duration};
use serde::Deserialize;
use std::time::Duration;
use tokio::time::timeout;
use tracing::debug;
use url::Url;
@ -91,46 +83,6 @@ impl ReceiveActivityHook<SharedInboxActivities, UserOrCommunity, LemmyContext> f
}
}
/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
/// headers.
///
/// actix-web doesn't allow pretty-print for json so we need to do this manually.
fn create_apub_response<T>(data: &T) -> LemmyResult<HttpResponse>
where
T: Serialize,
{
let json = serde_json::to_string_pretty(&WithContext::new(data, FEDERATION_CONTEXT.clone()))?;
Ok(
HttpResponse::Ok()
.content_type(FEDERATION_CONTENT_TYPE)
.insert_header((VARY, "Accept"))
.body(json),
)
}
fn create_apub_tombstone_response<T: Into<Url>>(id: T) -> LemmyResult<HttpResponse> {
let tombstone = Tombstone::new(id.into());
let json = serde_json::to_string_pretty(&WithContext::new(
tombstone,
FEDERATION_CONTEXT.deref().clone(),
))?;
Ok(
HttpResponse::Gone()
.content_type(FEDERATION_CONTENT_TYPE)
.status(actix_web::http::StatusCode::GONE)
.insert_header((VARY, "Accept"))
.body(json),
)
}
fn redirect_remote_object(url: &DbUrl) -> HttpResponse {
let mut res = HttpResponse::PermanentRedirect();
res.insert_header((actix_web::http::header::LOCATION, url.as_str()));
res.finish()
}
#[derive(Deserialize)]
pub struct ActivityQuery {
type_: String,
@ -140,7 +92,7 @@ pub struct ActivityQuery {
/// Return the ActivityPub json representation of a local activity over HTTP.
pub(crate) async fn get_activity(
info: web::Path<ActivityQuery>,
context: web::Data<LemmyContext>,
context: Data<LemmyContext>,
) -> LemmyResult<HttpResponse> {
let settings = context.settings();
let activity_id = Url::parse(&format!(
@ -156,13 +108,12 @@ pub(crate) async fn get_activity(
if sensitive {
Ok(HttpResponse::Forbidden().finish())
} else {
create_apub_response(&activity.data)
Ok(create_http_response(&activity.data, &FEDERATION_CONTEXT)?)
}
}
/// Ensure that the community is public and not removed/deleted.
fn check_community_fetchable(community: &Community) -> LemmyResult<()> {
check_community_removed_or_deleted(community)?;
if !community.visibility.can_federate() {
return Err(LemmyErrorType::NotFound.into());
}
@ -176,7 +127,6 @@ async fn check_community_content_fetchable(
context: &Data<LemmyContext>,
) -> LemmyResult<()> {
use CommunityVisibility::*;
check_community_removed_or_deleted(community)?;
match community.visibility {
Public | Unlisted => Ok(()),
Private => {
@ -207,10 +157,3 @@ async fn check_community_content_fetchable(
LocalOnlyPublic | LocalOnlyPrivate => Err(LemmyErrorType::NotFound.into()),
}
}
fn check_community_removed_or_deleted(community: &Community) -> LemmyResult<()> {
if community.deleted || community.removed {
Err(LemmyErrorType::Deleted)?
}
Ok(())
}

View file

@ -1,13 +1,17 @@
use crate::{
http::{create_apub_response, create_apub_tombstone_response},
protocol::collections::empty_outbox::EmptyOutbox,
use crate::protocol::collections::empty_outbox::EmptyOutbox;
use activitypub_federation::{
actix_web::response::create_http_response,
config::Data,
traits::Object,
};
use activitypub_federation::{config::Data, traits::Object};
use actix_web::{web::Path, HttpResponse};
use lemmy_api_utils::{context::LemmyContext, utils::generate_outbox_url};
use lemmy_apub_objects::objects::person::ApubPerson;
use lemmy_db_schema::{source::person::Person, traits::ApubActor};
use lemmy_utils::error::{LemmyErrorType, LemmyResult};
use lemmy_utils::{
error::{LemmyErrorType, LemmyResult},
FEDERATION_CONTEXT,
};
use serde::Deserialize;
#[derive(Deserialize)]
@ -21,19 +25,13 @@ pub(crate) async fn get_apub_person_http(
context: Data<LemmyContext>,
) -> LemmyResult<HttpResponse> {
let user_name = info.into_inner().user_name;
// TODO: this needs to be able to read deleted persons, so that it can send tombstones
// This needs to be able to read deleted persons, so that it can send tombstones
let person: ApubPerson = Person::read_from_name(&mut context.pool(), &user_name, true)
.await?
.ok_or(LemmyErrorType::NotFound)?
.into();
if !person.deleted {
let apub = person.into_json(&context).await?;
create_apub_response(&apub)
} else {
create_apub_tombstone_response(person.ap_id.clone())
}
person.http_response(&FEDERATION_CONTEXT, &context).await
}
pub(crate) async fn get_apub_person_outbox(
@ -45,5 +43,5 @@ pub(crate) async fn get_apub_person_outbox(
.ok_or(LemmyErrorType::NotFound)?;
let outbox_id = generate_outbox_url(&person.ap_id)?.into();
let outbox = EmptyOutbox::new(outbox_id)?;
create_apub_response(&outbox)
Ok(create_http_response(outbox, &FEDERATION_CONTEXT)?)
}

View file

@ -1,5 +1,4 @@
use super::check_community_content_fetchable;
use crate::http::{create_apub_response, create_apub_tombstone_response, redirect_remote_object};
use activitypub_federation::{config::Data, traits::Object};
use actix_web::{web, HttpRequest, HttpResponse};
use lemmy_api_utils::context::LemmyContext;
@ -9,7 +8,7 @@ use lemmy_db_schema::{
source::{community::Community, post::Post},
traits::Crud,
};
use lemmy_utils::error::LemmyResult;
use lemmy_utils::{error::LemmyResult, FEDERATION_CONTEXT};
use serde::Deserialize;
#[derive(Deserialize)]
@ -30,11 +29,5 @@ pub(crate) async fn get_apub_post(
check_community_content_fetchable(&community, &request, &context).await?;
if !post.local {
Ok(redirect_remote_object(&post.ap_id))
} else if !post.deleted && !post.removed {
create_apub_response(&post.into_json(&context).await?)
} else {
create_apub_tombstone_response(post.ap_id.clone())
}
post.http_response(&FEDERATION_CONTEXT, &context).await
}

View file

@ -1,17 +1,20 @@
use crate::{http::create_apub_response, protocol::collections::empty_outbox::EmptyOutbox};
use activitypub_federation::{config::Data, traits::Object};
use crate::protocol::collections::empty_outbox::EmptyOutbox;
use activitypub_federation::{
actix_web::response::create_http_response,
config::Data,
traits::Object,
};
use actix_web::HttpResponse;
use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::objects::instance::ApubSite;
use lemmy_db_schema::source::site::Site;
use lemmy_utils::error::LemmyResult;
use lemmy_utils::{error::LemmyResult, FEDERATION_CONTEXT};
use url::Url;
pub(crate) async fn get_apub_site_http(context: Data<LemmyContext>) -> LemmyResult<HttpResponse> {
let site: ApubSite = Site::read_local(&mut context.pool()).await?.into();
let apub = site.into_json(&context).await?;
create_apub_response(&apub)
site.http_response(&FEDERATION_CONTEXT, &context).await
}
pub(crate) async fn get_apub_site_outbox(context: Data<LemmyContext>) -> LemmyResult<HttpResponse> {
@ -20,5 +23,5 @@ pub(crate) async fn get_apub_site_outbox(context: Data<LemmyContext>) -> LemmyRe
context.settings().get_protocol_and_hostname()
);
let outbox = EmptyOutbox::new(Url::parse(&outbox_id)?)?;
create_apub_response(&outbox)
Ok(create_http_response(outbox, &FEDERATION_CONTEXT)?)
}

View file

@ -44,8 +44,8 @@ impl InCommunity for BlockUser {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let target = self.target.dereference(context).await?;
let community = match target {
SiteOrCommunity::Community(c) => c,
SiteOrCommunity::Site(_) => return Err(anyhow!("activity is not in community").into()),
SiteOrCommunity::Right(c) => c,
SiteOrCommunity::Left(_) => return Err(anyhow!("activity is not in community").into()),
};
Ok(community)
}

View file

@ -3,13 +3,12 @@ use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
kinds::activity::DeleteType,
protocol::helpers::deserialize_one_or_many,
protocol::{helpers::deserialize_one_or_many, tombstone::Tombstone},
};
use anyhow::anyhow;
use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::{
objects::{community::ApubCommunity, person::ApubPerson},
protocol::tombstone::Tombstone,
utils::protocol::InCommunity,
};
use lemmy_db_schema::{

View file

@ -3,16 +3,11 @@ use lemmy_api_utils::context::LemmyContext;
use lemmy_apub_objects::utils::protocol::Id;
use lemmy_utils::error::LemmyResult;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::collections::HashMap;
use url::Url;
pub mod activities;
pub(crate) mod collections;
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(transparent)]
pub struct Unparsed(HashMap<String, serde_json::Value>);
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub(crate) enum IdOrNestedObject<Kind: Id> {
@ -24,7 +19,7 @@ impl<Kind: Id + DeserializeOwned + Send> IdOrNestedObject<Kind> {
pub(crate) fn id(&self) -> &Url {
match self {
IdOrNestedObject::Id(i) => i,
IdOrNestedObject::NestedObject(n) => n.object_id(),
IdOrNestedObject::NestedObject(n) => n.id(),
}
}
pub(crate) async fn object(self, context: &Data<LemmyContext>) -> LemmyResult<Kind> {

View file

@ -18,6 +18,9 @@ doctest = false
[lints]
workspace = true
[features]
full = []
[dependencies]
lemmy_db_views_community_moderator = { workspace = true, features = ["full"] }
lemmy_db_views_community_person_ban = { workspace = true, features = ["full"] }
@ -25,7 +28,7 @@ lemmy_db_views_local_user = { workspace = true, features = ["full"] }
lemmy_db_views_site = { workspace = true, features = ["full"] }
lemmy_utils = { workspace = true, features = ["full"] }
lemmy_db_schema = { workspace = true, features = ["full"] }
lemmy_api_utils = { workspace = true }
lemmy_api_utils = { workspace = true, features = ["full"] }
activitypub_federation = { workspace = true }
lemmy_db_schema_file = { workspace = true }
chrono = { workspace = true }

View file

@ -74,8 +74,8 @@ impl Object for ApubComment {
type Kind = Note;
type Error = LemmyError;
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
None
fn id(&self) -> &Url {
self.ap_id.inner()
}
async fn read_from_id(
@ -100,6 +100,10 @@ impl Object for ApubComment {
Ok(())
}
fn is_deleted(&self) -> bool {
self.removed || self.deleted
}
async fn into_json(self, context: &Data<Self::DataType>) -> LemmyResult<Note> {
let creator_id = self.creator_id;
let creator = Person::read(&mut context.pool(), creator_id).await?;
@ -113,7 +117,7 @@ impl Object for ApubComment {
let parent_comment = Comment::read(&mut context.pool(), comment_id).await?;
parent_comment.ap_id.into()
} else {
post.ap_id.into()
post.ap_id.clone().into()
};
let language = Some(LanguageTag::new_single(self.language_id, &mut context.pool()).await?);
let maa = collect_non_local_mentions(&self, context).await?;

View file

@ -81,6 +81,10 @@ impl Object for ApubCommunity {
type Kind = Group;
type Error = LemmyError;
fn id(&self) -> &Url {
self.ap_id.inner()
}
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
Some(self.last_refreshed_at)
}
@ -105,6 +109,10 @@ impl Object for ApubCommunity {
Ok(())
}
fn is_deleted(&self) -> bool {
self.removed || self.deleted
}
async fn into_json(self, data: &Data<Self::DataType>) -> LemmyResult<Group> {
let community_id = self.id;
let langs = CommunityLanguage::read(&mut data.pool(), community_id).await?;
@ -112,7 +120,7 @@ impl Object for ApubCommunity {
let group = Group {
kind: GroupType::Group,
id: self.id().into(),
id: self.id().clone().into(),
preferred_username: self.name.clone(),
name: Some(self.title.clone()),
content: self.sidebar.as_ref().map(|d| markdown_to_html(d)),
@ -243,10 +251,6 @@ impl Object for ApubCommunity {
}
impl Actor for ApubCommunity {
fn id(&self) -> Url {
self.ap_id.inner().clone()
}
fn public_key_pem(&self) -> &str {
&self.public_key
}

View file

@ -69,6 +69,10 @@ impl Object for ApubSite {
type Kind = Instance;
type Error = LemmyError;
fn id(&self) -> &Url {
self.ap_id.inner()
}
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
Some(self.last_refreshed_at)
}
@ -92,7 +96,7 @@ impl Object for ApubSite {
let instance = Instance {
kind: ApplicationType::Application,
id: self.id().into(),
id: self.id().clone().into(),
name: self.name.clone(),
preferred_username: Some(data.domain().to_string()),
content: self.sidebar.as_ref().map(|d| markdown_to_html(d)),
@ -169,10 +173,6 @@ impl Object for ApubSite {
}
impl Actor for ApubSite {
fn id(&self) -> Url {
self.ap_id.inner().clone()
}
fn public_key_pem(&self) -> &str {
&self.public_key
}

View file

@ -43,6 +43,10 @@ impl Object for ApubMultiCommunity {
type Kind = Feed;
type Error = LemmyError;
fn id(&self) -> &Url {
self.ap_id.inner()
}
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
Some(self.last_refreshed_at)
}
@ -62,6 +66,10 @@ impl Object for ApubMultiCommunity {
Err(LemmyErrorType::NotFound.into())
}
fn is_deleted(&self) -> bool {
self.deleted
}
async fn into_json(self, context: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
let site_view = SiteView::read_local(&mut context.pool()).await?;
let site = ApubSite(site_view.site.clone());
@ -115,10 +123,6 @@ impl Object for ApubMultiCommunity {
}
impl Actor for ApubMultiCommunity {
fn id(&self) -> Url {
self.ap_id.inner().clone()
}
fn public_key_pem(&self) -> &str {
&self.public_key
}

View file

@ -65,6 +65,10 @@ impl Object for ApubPerson {
type Kind = Person;
type Error = LemmyError;
fn id(&self) -> &Url {
self.ap_id.inner()
}
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
Some(self.last_refreshed_at)
}
@ -89,6 +93,10 @@ impl Object for ApubPerson {
Ok(())
}
fn is_deleted(&self) -> bool {
self.deleted
}
async fn into_json(self, _context: &Data<Self::DataType>) -> LemmyResult<Person> {
let kind = if self.bot_account {
UserTypes::Service
@ -181,10 +189,6 @@ impl Object for ApubPerson {
}
impl Actor for ApubPerson {
fn id(&self) -> Url {
self.ap_id.inner().clone()
}
fn public_key_pem(&self) -> &str {
&self.public_key
}

View file

@ -27,7 +27,7 @@ use activitypub_federation::{
traits::Object,
};
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use chrono::Utc;
use html2text::{from_read_with_decorator, render::TrivialDecorator};
use lemmy_api_utils::{
context::LemmyContext,
@ -82,8 +82,8 @@ impl Object for ApubPost {
type Kind = Page;
type Error = LemmyError;
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
None
fn id(&self) -> &Url {
self.ap_id.inner()
}
async fn read_from_id(
@ -108,6 +108,10 @@ impl Object for ApubPost {
Ok(())
}
fn is_deleted(&self) -> bool {
self.removed || self.deleted
}
// Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
async fn into_json(self, context: &Data<Self::DataType>) -> LemmyResult<Page> {

View file

@ -14,7 +14,7 @@ use activitypub_federation::{
},
traits::Object,
};
use chrono::{DateTime, Utc};
use chrono::Utc;
use lemmy_api_utils::{
context::LemmyContext,
plugins::{plugin_hook_after, plugin_hook_before},
@ -59,8 +59,8 @@ impl Object for ApubPrivateMessage {
type Kind = PrivateMessage;
type Error = LemmyError;
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
None
fn id(&self) -> &Url {
self.ap_id.inner()
}
async fn read_from_id(
@ -79,6 +79,10 @@ impl Object for ApubPrivateMessage {
Err(LemmyErrorType::NotFound.into())
}
fn is_deleted(&self) -> bool {
self.removed || self.deleted
}
async fn into_json(self, context: &Data<Self::DataType>) -> LemmyResult<PrivateMessage> {
let creator_id = self.creator_id;
let creator = Person::read(&mut context.pool(), creator_id).await?;

View file

@ -5,7 +5,6 @@ pub mod note;
pub mod page;
pub mod person;
pub mod private_message;
pub mod tombstone;
#[cfg(test)]
mod tests {
@ -16,9 +15,9 @@ mod tests {
page::Page,
person::Person,
private_message::PrivateMessage,
tombstone::Tombstone,
};
use crate::utils::test::{test_json, test_parse_lemmy_item};
use activitypub_federation::protocol::tombstone::Tombstone;
use lemmy_utils::error::LemmyResult;
#[test]

View file

@ -20,7 +20,7 @@ use activitypub_federation::{
helpers::{deserialize_one_or_many, deserialize_skip_error},
values::MediaTypeMarkdownOrHtml,
},
traits::{ActivityHandler, Object},
traits::{Activity, Object},
};
use chrono::{DateTime, Utc};
use itertools::Itertools;
@ -200,7 +200,7 @@ impl Attachment {
// Used for community outbox, so that it can be compatible with Pleroma/Mastodon.
#[async_trait::async_trait]
impl ActivityHandler for Page {
impl Activity for Page {
type DataType = LemmyContext;
type Error = LemmyError;
fn id(&self) -> &Url {

View file

@ -3,7 +3,7 @@ use activitypub_federation::{
config::Data,
fetch::webfinger::webfinger_resolve_actor,
kinds::link::MentionType,
traits::Actor,
traits::Object,
};
use lemmy_api_utils::context::LemmyContext;
use lemmy_db_schema::{
@ -47,7 +47,7 @@ pub async fn collect_non_local_mentions(
context: &Data<LemmyContext>,
) -> LemmyResult<MentionsAndAddresses> {
let parent_creator = get_comment_parent_creator(&mut context.pool(), comment).await?;
let mut addressed_ccs: Vec<Url> = vec![parent_creator.id()];
let mut addressed_ccs: Vec<Url> = vec![parent_creator.id().clone()];
// Add the mention tag
let parent_creator_tag = Mention {
@ -77,7 +77,7 @@ pub async fn collect_non_local_mentions(
addressed_ccs.push(person.ap_id.to_string().parse()?);
let mention_tag = Mention {
href: person.id(),
href: person.id().clone(),
name: Some(mention.full_name()),
kind: MentionType::Mention,
};

View file

@ -3,7 +3,7 @@ use activitypub_federation::{
config::Data,
fetch::object_id::ObjectId,
kinds::object::ImageType,
protocol::values::MediaTypeMarkdown,
protocol::{tombstone::Tombstone, values::MediaTypeMarkdown},
};
use lemmy_api_utils::context::LemmyContext;
use lemmy_db_schema::{
@ -198,5 +198,11 @@ pub struct Endpoints {
}
pub trait Id {
fn object_id(&self) -> &Url;
fn id(&self) -> &Url;
}
impl Id for Tombstone {
fn id(&self) -> &Url {
&self.id
}
}

View file

@ -3,7 +3,7 @@ use activitypub_federation::{
activity_sending::SendActivityTask,
config::Data,
protocol::context::WithContext,
traits::ActivityHandler,
traits::Activity,
};
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
@ -166,7 +166,7 @@ struct DummyActivity {
}
#[async_trait::async_trait]
impl ActivityHandler for DummyActivity {
impl Activity for DummyActivity {
type DataType = LemmyContext;
type Error = LemmyError;