Use either instead of manually implemented enums (fixes #5513) (#5585)

* Use either instead of manually implemented enums (fixes #5513)

* reduce db reads
This commit is contained in:
Nutomic 2025-04-04 09:17:44 +00:00 committed by GitHub
parent cc39ffeef3
commit ad89b8e40a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 154 additions and 544 deletions

2
Cargo.lock generated
View file

@ -3212,6 +3212,7 @@ dependencies = [
"async-trait",
"chrono",
"diesel",
"either",
"enum_delegate",
"futures",
"html2md",
@ -3224,7 +3225,6 @@ dependencies = [
"lemmy_utils",
"moka",
"pretty_assertions",
"reqwest 0.12.12",
"semver",
"serde",
"serde_json",

View file

@ -38,7 +38,6 @@ itertools = { workspace = true }
uuid = { workspace = true }
async-trait = "0.1.86"
anyhow = { workspace = true }
reqwest = { workspace = true }
moka.workspace = true
serde_with.workspace = true
html2md = "0.2.15"
@ -46,6 +45,7 @@ html2text = "0.14.0"
stringreader = "0.1.1"
enum_delegate = "0.2.0"
semver = "1.0.25"
either = "1.15.0"
[dev-dependencies]
serial_test = { workspace = true }

View file

@ -1,7 +1,7 @@
use crate::{
activities::send_lemmy_activity,
activity_lists::AnnouncableActivities,
fetcher::post_or_comment::PostOrComment,
fetcher::PostOrComment,
objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson},
protocol::activities::community::announce::AnnounceActivity,
};
@ -96,8 +96,8 @@ async fn report_inboxes(
// also send report to user's home instance if possible
let object_creator_id = match object_id.dereference_local(context).await? {
PostOrComment::Post(p) => p.creator_id,
PostOrComment::Comment(c) => c.creator_id,
PostOrComment::Left(p) => p.creator_id,
PostOrComment::Right(c) => c.creator_id,
};
let object_creator = Person::read(&mut context.pool(), object_creator_id).await?;
let object_creator_site: Option<ApubSite> =

View file

@ -95,7 +95,7 @@ impl ActivityHandler for Report {
let actor = self.actor.dereference(context).await?;
let reason = self.reason()?;
match self.object.dereference(context).await? {
PostOrComment::Post(post) => {
PostOrComment::Left(post) => {
check_post_deleted_or_removed(&post)?;
let report_form = PostReportForm {
@ -109,7 +109,7 @@ impl ActivityHandler for Report {
};
PostReport::report(&mut context.pool(), &report_form).await?;
}
PostOrComment::Comment(comment) => {
PostOrComment::Right(comment) => {
check_comment_deleted_or_removed(&comment)?;
let report_form = CommentReportForm {

View file

@ -87,10 +87,10 @@ impl ActivityHandler for ResolveReport {
let reporter = self.object.actor.dereference(context).await?;
let actor = self.actor.dereference(context).await?;
match self.object.object.dereference(context).await? {
PostOrComment::Post(post) => {
PostOrComment::Left(post) => {
PostReport::resolve_apub(&mut context.pool(), post.id, reporter.id, actor.id).await?;
}
PostOrComment::Comment(comment) => {
PostOrComment::Right(comment) => {
CommentReport::resolve_apub(&mut context.pool(), comment.id, reporter.id, actor.id).await?;
}
};

View file

@ -1,5 +1,6 @@
use super::send_activity_from_user_or_community;
use crate::{
activities::{generate_activity_id, send_lemmy_activity},
activities::generate_activity_id,
insert_received_activity,
protocol::activities::following::{accept::AcceptFollow, follow::Follow},
};
@ -32,7 +33,7 @@ impl AcceptFollow {
)?,
};
let inbox = ActivitySendTargets::to_inbox(person.shared_inbox_or_inbox());
send_lemmy_activity(context, accept, &user_or_community, inbox, true).await
send_activity_from_user_or_community(context, accept, user_or_community, inbox).await
}
}

View file

@ -5,7 +5,7 @@ use crate::{
verify_person,
verify_person_in_community,
},
fetcher::user_or_community::UserOrCommunity,
fetcher::UserOrCommunity,
insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{accept::AcceptFollow, follow::Follow},
@ -79,7 +79,7 @@ impl ActivityHandler for Follow {
async fn verify(&self, context: &Data<LemmyContext>) -> LemmyResult<()> {
verify_person(&self.actor, context).await?;
let object = self.object.dereference(context).await?;
if let UserOrCommunity::Community(c) = object {
if let UserOrCommunity::Right(c) = object {
verify_person_in_community(&self.actor, &c, context).await?;
}
if let Some(to) = &self.to {
@ -94,12 +94,12 @@ impl ActivityHandler for Follow {
let actor = self.actor.dereference(context).await?;
let object = self.object.dereference(context).await?;
match object {
UserOrCommunity::User(u) => {
UserOrCommunity::Left(u) => {
let form = PersonFollowerForm::new(u.id, actor.id, false);
PersonActions::follow(&mut context.pool(), &form).await?;
AcceptFollow::send(self, context).await?;
}
UserOrCommunity::Community(c) => {
UserOrCommunity::Right(c) => {
if c.visibility == CommunityVisibility::Private {
let instance = Instance::read(&mut context.pool(), actor.instance_id).await?;
if [Some("kbin"), Some("mbin")].contains(&instance.software.as_deref()) {

View file

@ -1,5 +1,6 @@
use super::generate_activity_id;
use super::{generate_activity_id, send_lemmy_activity};
use crate::{
fetcher::UserOrCommunity,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{
accept::AcceptFollow,
@ -8,14 +9,15 @@ use crate::{
undo_follow::UndoFollow,
},
};
use activitypub_federation::{config::Data, kinds::activity::FollowType};
use activitypub_federation::{config::Data, kinds::activity::FollowType, traits::ActivityHandler};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
newtypes::{CommunityId, PersonId},
source::{community::Community, person::Person},
source::{activity::ActivitySendTargets, community::Community, person::Person},
traits::Crud,
};
use lemmy_utils::error::LemmyResult;
use lemmy_utils::error::{LemmyError, LemmyResult};
use serde::Serialize;
pub(crate) mod accept;
pub(crate) mod follow;
@ -62,3 +64,23 @@ pub async fn send_accept_or_reject_follow(
RejectFollow::send(follow, context).await
}
}
/// Wrapper type which is needed because we cant implement ActorT for Either.
async fn send_activity_from_user_or_community<Activity>(
context: &Data<LemmyContext>,
activity: Activity,
user_or_community: UserOrCommunity,
send_targets: ActivitySendTargets,
) -> LemmyResult<()>
where
Activity: ActivityHandler + Serialize + Send + Sync + Clone + ActivityHandler<Error = LemmyError>,
{
match user_or_community {
UserOrCommunity::Left(user) => {
send_lemmy_activity(context, activity, &user, send_targets, true).await
}
UserOrCommunity::Right(community) => {
send_lemmy_activity(context, activity, &community, send_targets, true).await
}
}
}

View file

@ -1,5 +1,6 @@
use super::send_activity_from_user_or_community;
use crate::{
activities::{generate_activity_id, send_lemmy_activity},
activities::generate_activity_id,
insert_received_activity,
protocol::activities::following::{follow::Follow, reject::RejectFollow},
};
@ -32,7 +33,7 @@ impl RejectFollow {
)?,
};
let inbox = ActivitySendTargets::to_inbox(person.shared_inbox_or_inbox());
send_lemmy_activity(context, reject, &user_or_community, inbox, true).await
send_activity_from_user_or_community(context, reject, user_or_community, inbox).await
}
}

View file

@ -1,6 +1,6 @@
use crate::{
activities::{generate_activity_id, send_lemmy_activity, verify_person},
fetcher::user_or_community::UserOrCommunity,
fetcher::UserOrCommunity,
insert_received_activity,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::activities::following::{follow::Follow, undo_follow::UndoFollow},
@ -74,10 +74,10 @@ impl ActivityHandler for UndoFollow {
let object = self.object.object.dereference(context).await?;
match object {
UserOrCommunity::User(u) => {
UserOrCommunity::Left(u) => {
PersonActions::unfollow(&mut context.pool(), person.id, u.id).await?;
}
UserOrCommunity::Community(c) => {
UserOrCommunity::Right(c) => {
CommunityActions::unfollow(&mut context.pool(), person.id, c.id).await?;
}
}

View file

@ -122,7 +122,11 @@ pub(crate) fn verify_is_public(to: &[Url], cc: &[Url]) -> LemmyResult<()> {
/// Returns an error if object visibility doesnt match community visibility
/// (ie content in private community must also be private).
pub(crate) fn verify_visibility(to: &[Url], cc: &[Url], community: &Community) -> LemmyResult<()> {
pub(crate) fn verify_visibility(
to: &[Url],
cc: &[Url],
community: &ApubCommunity,
) -> LemmyResult<()> {
use CommunityVisibility::*;
let object_is_public = [to, cc].iter().any(|set| set.contains(&public()));
match community.visibility {
@ -195,9 +199,8 @@ async fn send_lemmy_activity<Activity, ActorT>(
sensitive: bool,
) -> LemmyResult<()>
where
Activity: ActivityHandler + Serialize + Send + Sync + Clone,
Activity: ActivityHandler + Serialize + Send + Sync + Clone + ActivityHandler<Error = LemmyError>,
ActorT: Actor + GetActorType,
Activity: ActivityHandler<Error = LemmyError>,
{
info!("Saving outgoing activity to queue {}", activity.id());

View file

@ -1,7 +1,7 @@
use crate::{
activities::community::send_activity_in_community,
activity_lists::AnnouncableActivities,
fetcher::post_or_comment::PostOrComment,
fetcher::PostOrComment,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::activities::voting::{
undo_vote::UndoVote,

View file

@ -66,8 +66,8 @@ impl ActivityHandler for UndoVote {
let actor = self.actor.dereference(context).await?;
let object = self.object.object.dereference(context).await?;
match object {
PostOrComment::Post(p) => undo_vote_post(actor, &p, context).await,
PostOrComment::Comment(c) => undo_vote_comment(actor, &c, context).await,
PostOrComment::Left(p) => undo_vote_post(actor, &p, context).await,
PostOrComment::Right(c) => undo_vote_comment(actor, &c, context).await,
}
}
}

View file

@ -72,8 +72,8 @@ impl ActivityHandler for Vote {
.unwrap_or_default();
let (downvote_setting, upvote_setting) = match object {
PostOrComment::Post(_) => (local_site.post_downvotes, local_site.post_upvotes),
PostOrComment::Comment(_) => (local_site.comment_downvotes, local_site.comment_upvotes),
PostOrComment::Left(_) => (local_site.post_downvotes, local_site.post_upvotes),
PostOrComment::Right(_) => (local_site.comment_downvotes, local_site.comment_upvotes),
};
// Don't allow dislikes for either disabled, or local only votes
@ -83,14 +83,14 @@ impl ActivityHandler for Vote {
if downvote_fail || upvote_fail {
// If this is a rejection, undo the vote
match object {
PostOrComment::Post(p) => undo_vote_post(actor, &p, context).await,
PostOrComment::Comment(c) => undo_vote_comment(actor, &c, context).await,
PostOrComment::Left(p) => undo_vote_post(actor, &p, context).await,
PostOrComment::Right(c) => undo_vote_comment(actor, &c, context).await,
}
} else {
// Otherwise apply the vote normally
match object {
PostOrComment::Post(p) => vote_post(&self.kind, actor, &p, context).await,
PostOrComment::Comment(c) => vote_comment(&self.kind, actor, &c, context).await,
PostOrComment::Left(p) => vote_post(&self.kind, actor, &p, context).await,
PostOrComment::Right(c) => vote_comment(&self.kind, actor, &c, context).await,
}
}
}

View file

@ -1,7 +1,7 @@
use crate::fetcher::{
post_or_comment::PostOrComment,
search::{search_query_to_object_id, search_query_to_object_id_local, SearchableObjects},
user_or_community::UserOrCommunity,
PostOrComment,
UserOrCommunity,
};
use activitypub_federation::config::Data;
use actix_web::web::{Json, Query};
@ -59,21 +59,21 @@ async fn convert_response(
let local_instance_id = site_view.site.instance_id;
match object {
SearchableObjects::PostOrComment(pc) => match *pc {
PostOrComment::Post(p) => {
SearchableObjects::Left(pc) => match pc {
PostOrComment::Left(p) => {
res.post =
Some(PostView::read(pool, p.id, local_user.as_ref(), local_instance_id, is_admin).await?)
}
PostOrComment::Comment(c) => {
PostOrComment::Right(c) => {
res.comment =
Some(CommentView::read(pool, c.id, local_user.as_ref(), local_instance_id).await?)
}
},
SearchableObjects::PersonOrCommunity(pc) => match *pc {
UserOrCommunity::User(u) => {
SearchableObjects::Right(pc) => match pc {
UserOrCommunity::Left(u) => {
res.person = Some(PersonView::read(pool, u.id, local_instance_id, is_admin).await?)
}
UserOrCommunity::Community(c) => {
UserOrCommunity::Right(c) => {
res.community = Some(CommunityView::read(pool, c.id, local_user.as_ref(), is_admin).await?)
}
},

View file

@ -1,5 +1,5 @@
use super::{search::SearchableObjects, user_or_community::UserOrCommunity};
use crate::fetcher::post_or_comment::PostOrComment;
use super::{search::SearchableObjects, UserOrCommunity};
use crate::fetcher::PostOrComment;
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{newtypes::InstanceId, source::instance::Instance};
@ -57,17 +57,17 @@ pub(crate) async fn to_local_url(url: &str, context: &Data<LemmyContext>) -> Opt
}
let dereferenced = object_id.dereference_local(context).await.ok()?;
match dereferenced {
SearchableObjects::PostOrComment(pc) => match *pc {
PostOrComment::Post(post) => post.local_url(context.settings()),
PostOrComment::Comment(comment) => comment.local_url(context.settings()),
SearchableObjects::Left(pc) => match pc {
PostOrComment::Left(post) => post.local_url(context.settings()),
PostOrComment::Right(comment) => comment.local_url(context.settings()),
}
.ok()
.map(Into::into),
SearchableObjects::PersonOrCommunity(pc) => match *pc {
UserOrCommunity::User(user) => {
SearchableObjects::Right(pc) => match pc {
UserOrCommunity::Left(user) => {
format_actor_url(&user.name, "u", user.instance_id, context).await
}
UserOrCommunity::Community(community) => {
UserOrCommunity::Right(community) => {
format_actor_url(&community.name, "c", community.instance_id, context).await
}
}

View file

@ -1,20 +1,31 @@
use crate::objects::{
comment::ApubComment,
community::ApubCommunity,
instance::ApubSite,
person::ApubPerson,
post::ApubPost,
};
use activitypub_federation::{
config::Data,
fetch::webfinger::webfinger_resolve_actor,
traits::{Actor, Object},
};
use diesel::NotFound;
use either::Either;
use itertools::Itertools;
use lemmy_api_common::{context::LemmyContext, LemmyErrorType};
use lemmy_db_schema::traits::ApubActor;
use lemmy_db_schema::{newtypes::InstanceId, traits::ApubActor};
use lemmy_db_views::structs::LocalUserView;
use lemmy_utils::error::{LemmyError, LemmyResult};
pub(crate) mod markdown_links;
pub mod post_or_comment;
pub mod search;
pub mod site_or_community_or_user;
pub mod user_or_community;
pub(crate) type PostOrComment = Either<ApubPost, ApubComment>;
pub type SiteOrCommunityOrUser = Either<ApubSite, UserOrCommunity>;
pub type UserOrCommunity = Either<ApubPerson, ApubCommunity>;
/// Resolve actor identifier like `!news@example.com` to user or community object.
///
@ -67,3 +78,11 @@ where
)
}
}
pub(crate) fn get_instance_id(s: &SiteOrCommunityOrUser) -> InstanceId {
match s {
SiteOrCommunityOrUser::Left(s) => s.instance_id,
SiteOrCommunityOrUser::Right(UserOrCommunity::Left(u)) => u.instance_id,
SiteOrCommunityOrUser::Right(UserOrCommunity::Right(c)) => c.instance_id,
}
}

View file

@ -1,97 +0,0 @@
use crate::{
objects::{comment::ApubComment, community::ApubCommunity, post::ApubPost},
protocol::{
objects::{note::Note, page::Page},
InCommunity,
},
};
use activitypub_federation::{config::Data, traits::Object};
use chrono::{DateTime, Utc};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{
source::{community::Community, post::Post},
traits::Crud,
};
use lemmy_utils::error::{LemmyError, LemmyResult};
use serde::Deserialize;
use url::Url;
#[derive(Clone, Debug)]
pub enum PostOrComment {
Post(ApubPost),
Comment(ApubComment),
}
#[derive(Deserialize)]
#[serde(untagged)]
pub enum PageOrNote {
Page(Box<Page>),
Note(Box<Note>),
}
#[async_trait::async_trait]
impl Object for PostOrComment {
type DataType = LemmyContext;
type Kind = PageOrNote;
type Error = LemmyError;
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
None
}
async fn read_from_id(object_id: Url, data: &Data<Self::DataType>) -> LemmyResult<Option<Self>> {
let post = ApubPost::read_from_id(object_id.clone(), data).await?;
Ok(match post {
Some(o) => Some(PostOrComment::Post(o)),
None => ApubComment::read_from_id(object_id, data)
.await?
.map(PostOrComment::Comment),
})
}
async fn delete(self, data: &Data<Self::DataType>) -> LemmyResult<()> {
match self {
PostOrComment::Post(p) => p.delete(data).await,
PostOrComment::Comment(c) => c.delete(data).await,
}
}
async fn into_json(self, data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
Ok(match self {
PostOrComment::Post(p) => PageOrNote::Page(Box::new(p.into_json(data).await?)),
PostOrComment::Comment(c) => PageOrNote::Note(Box::new(c.into_json(data).await?)),
})
}
async fn verify(
apub: &Self::Kind,
expected_domain: &Url,
data: &Data<Self::DataType>,
) -> LemmyResult<()> {
match apub {
PageOrNote::Page(a) => ApubPost::verify(a, expected_domain, data).await,
PageOrNote::Note(a) => ApubComment::verify(a, expected_domain, data).await,
}
}
async fn from_json(apub: PageOrNote, context: &Data<LemmyContext>) -> LemmyResult<Self> {
Ok(match apub {
PageOrNote::Page(p) => PostOrComment::Post(ApubPost::from_json(*p, context).await?),
PageOrNote::Note(n) => PostOrComment::Comment(ApubComment::from_json(*n, context).await?),
})
}
}
impl InCommunity for PostOrComment {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let cid = match self {
PostOrComment::Post(p) => p.community_id,
PostOrComment::Comment(c) => {
Post::read(&mut context.pool(), c.post_id)
.await?
.community_id
}
};
Ok(Community::read(&mut context.pool(), cid).await?.into())
}
}

View file

@ -1,14 +1,12 @@
use super::post_or_comment::{PageOrNote, PostOrComment};
use crate::fetcher::user_or_community::{PersonOrGroup, UserOrCommunity};
use super::PostOrComment;
use crate::fetcher::UserOrCommunity;
use activitypub_federation::{
config::Data,
fetch::{object_id::ObjectId, webfinger::webfinger_resolve_actor},
traits::Object,
};
use chrono::{DateTime, Utc};
use either::Either;
use lemmy_api_common::context::LemmyContext;
use lemmy_utils::error::{LemmyError, LemmyResult};
use serde::Deserialize;
use lemmy_utils::error::LemmyResult;
use url::Url;
/// Converts search query to object id. The query can either be an URL, which will be treated as
@ -28,9 +26,9 @@ pub(crate) async fn search_query_to_object_id(
if query.starts_with('!') || query.starts_with('@') {
query.remove(0);
}
SearchableObjects::PersonOrCommunity(Box::new(
SearchableObjects::Right(
webfinger_resolve_actor::<LemmyContext, UserOrCommunity>(&query, context).await?,
))
)
}
})
}
@ -46,88 +44,4 @@ pub(crate) async fn search_query_to_object_id_local(
ObjectId::from(url).dereference_local(context).await
}
/// The types of ActivityPub objects that can be fetched directly by searching for their ID.
#[derive(Debug)]
pub(crate) enum SearchableObjects {
PostOrComment(Box<PostOrComment>),
PersonOrCommunity(Box<UserOrCommunity>),
}
#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum SearchableKinds {
PageOrNote(Box<PageOrNote>),
PersonOrGroup(Box<PersonOrGroup>),
}
#[async_trait::async_trait]
impl Object for SearchableObjects {
type DataType = LemmyContext;
type Kind = SearchableKinds;
type Error = LemmyError;
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
match self {
SearchableObjects::PostOrComment(p) => p.last_refreshed_at(),
SearchableObjects::PersonOrCommunity(p) => p.last_refreshed_at(),
}
}
// TODO: this is inefficient, because if the object is not in local db, it will run 4 db queries
// before finally returning an error. it would be nice if we could check all 4 tables in
// a single query.
// we could skip this and always return an error, but then it would always fetch objects
// over http, and not be able to mark objects as deleted that were deleted by remote server.
async fn read_from_id(
object_id: Url,
context: &Data<Self::DataType>,
) -> LemmyResult<Option<Self>> {
let uc = UserOrCommunity::read_from_id(object_id.clone(), context).await?;
if let Some(uc) = uc {
return Ok(Some(SearchableObjects::PersonOrCommunity(Box::new(uc))));
}
let pc = PostOrComment::read_from_id(object_id.clone(), context).await?;
if let Some(pc) = pc {
return Ok(Some(SearchableObjects::PostOrComment(Box::new(pc))));
}
Ok(None)
}
async fn delete(self, data: &Data<Self::DataType>) -> LemmyResult<()> {
match self {
SearchableObjects::PostOrComment(pc) => pc.delete(data).await,
SearchableObjects::PersonOrCommunity(pc) => pc.delete(data).await,
}
}
async fn into_json(self, data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
use SearchableObjects::*;
Ok(match self {
PostOrComment(pc) => SearchableKinds::PageOrNote(Box::new(pc.into_json(data).await?)),
PersonOrCommunity(pc) => SearchableKinds::PersonOrGroup(Box::new(pc.into_json(data).await?)),
})
}
async fn verify(
apub: &Self::Kind,
expected_domain: &Url,
data: &Data<Self::DataType>,
) -> LemmyResult<()> {
use SearchableKinds::*;
match apub {
PageOrNote(pn) => PostOrComment::verify(pn, expected_domain, data).await,
PersonOrGroup(pg) => UserOrCommunity::verify(pg, expected_domain, data).await,
}
}
async fn from_json(apub: Self::Kind, context: &Data<LemmyContext>) -> LemmyResult<Self> {
use SearchableKinds::*;
use SearchableObjects as SO;
Ok(match apub {
PageOrNote(pg) => SO::PostOrComment(Box::new(PostOrComment::from_json(*pg, context).await?)),
PersonOrGroup(pg) => {
SO::PersonOrCommunity(Box::new(UserOrCommunity::from_json(*pg, context).await?))
}
})
}
}
pub(crate) type SearchableObjects = Either<PostOrComment, UserOrCommunity>;

View file

@ -1,136 +0,0 @@
use crate::{
fetcher::user_or_community::{PersonOrGroup, UserOrCommunity},
objects::{community::ApubCommunity, instance::ApubSite, person::ApubPerson},
protocol::objects::instance::Instance,
};
use activitypub_federation::{
config::Data,
traits::{Actor, Object},
};
use chrono::{DateTime, Utc};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::newtypes::InstanceId;
use lemmy_utils::error::{LemmyError, LemmyResult};
use reqwest::Url;
use serde::{Deserialize, Serialize};
// todo: maybe this enum should be somewhere else?
#[derive(Debug)]
pub enum SiteOrCommunityOrUser {
Site(ApubSite),
UserOrCommunity(UserOrCommunity),
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum SiteOrPersonOrGroup {
Instance(Instance),
PersonOrGroup(PersonOrGroup),
}
#[async_trait::async_trait]
impl Object for SiteOrCommunityOrUser {
type DataType = LemmyContext;
type Kind = SiteOrPersonOrGroup;
type Error = LemmyError;
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
Some(match self {
SiteOrCommunityOrUser::Site(p) => p.last_refreshed_at,
SiteOrCommunityOrUser::UserOrCommunity(p) => p.last_refreshed_at()?,
})
}
async fn read_from_id(object_id: Url, data: &Data<Self::DataType>) -> LemmyResult<Option<Self>> {
let site = ApubSite::read_from_id(object_id.clone(), data).await?;
Ok(match site {
Some(o) => Some(SiteOrCommunityOrUser::Site(o)),
None => UserOrCommunity::read_from_id(object_id, data)
.await?
.map(SiteOrCommunityOrUser::UserOrCommunity),
})
}
async fn delete(self, data: &Data<Self::DataType>) -> LemmyResult<()> {
match self {
SiteOrCommunityOrUser::Site(p) => p.delete(data).await,
SiteOrCommunityOrUser::UserOrCommunity(p) => p.delete(data).await,
}
}
async fn into_json(self, data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
Ok(match self {
SiteOrCommunityOrUser::Site(p) => SiteOrPersonOrGroup::Instance(p.into_json(data).await?),
SiteOrCommunityOrUser::UserOrCommunity(p) => {
SiteOrPersonOrGroup::PersonOrGroup(p.into_json(data).await?)
}
})
}
async fn verify(
apub: &Self::Kind,
expected_domain: &Url,
data: &Data<Self::DataType>,
) -> LemmyResult<()> {
match apub {
SiteOrPersonOrGroup::Instance(a) => ApubSite::verify(a, expected_domain, data).await,
SiteOrPersonOrGroup::PersonOrGroup(a) => {
UserOrCommunity::verify(a, expected_domain, data).await
}
}
}
async fn from_json(apub: Self::Kind, data: &Data<Self::DataType>) -> LemmyResult<Self> {
Ok(match apub {
SiteOrPersonOrGroup::Instance(a) => {
SiteOrCommunityOrUser::Site(ApubSite::from_json(a, data).await?)
}
SiteOrPersonOrGroup::PersonOrGroup(a) => SiteOrCommunityOrUser::UserOrCommunity(match a {
PersonOrGroup::Person(p) => UserOrCommunity::User(ApubPerson::from_json(p, data).await?),
PersonOrGroup::Group(g) => {
UserOrCommunity::Community(ApubCommunity::from_json(g, data).await?)
}
}),
})
}
}
impl Actor for SiteOrCommunityOrUser {
fn id(&self) -> Url {
match self {
SiteOrCommunityOrUser::Site(u) => u.id(),
SiteOrCommunityOrUser::UserOrCommunity(c) => c.id(),
}
}
fn public_key_pem(&self) -> &str {
match self {
SiteOrCommunityOrUser::Site(p) => p.public_key_pem(),
SiteOrCommunityOrUser::UserOrCommunity(p) => p.public_key_pem(),
}
}
fn private_key_pem(&self) -> Option<String> {
match self {
SiteOrCommunityOrUser::Site(p) => p.private_key_pem(),
SiteOrCommunityOrUser::UserOrCommunity(p) => p.private_key_pem(),
}
}
fn inbox(&self) -> Url {
match self {
SiteOrCommunityOrUser::Site(u) => u.inbox(),
SiteOrCommunityOrUser::UserOrCommunity(c) => c.inbox(),
}
}
}
impl SiteOrCommunityOrUser {
pub fn instance_id(&self) -> InstanceId {
match self {
SiteOrCommunityOrUser::Site(s) => s.instance_id,
SiteOrCommunityOrUser::UserOrCommunity(UserOrCommunity::User(u)) => u.instance_id,
SiteOrCommunityOrUser::UserOrCommunity(UserOrCommunity::Community(c)) => c.instance_id,
}
}
}

View file

@ -1,131 +0,0 @@
use crate::{
activities::GetActorType,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::objects::{group::Group, person::Person},
};
use activitypub_federation::{
config::Data,
traits::{Actor, Object},
};
use chrono::{DateTime, Utc};
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema_file::enums::ActorType;
use lemmy_utils::error::{LemmyError, LemmyResult};
use serde::{Deserialize, Serialize};
use url::Url;
#[derive(Clone, Debug)]
pub enum UserOrCommunity {
User(ApubPerson),
Community(ApubCommunity),
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(untagged)]
pub enum PersonOrGroup {
Person(Person),
Group(Group),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub enum PersonOrGroupType {
Person,
Group,
}
#[async_trait::async_trait]
impl Object for UserOrCommunity {
type DataType = LemmyContext;
type Kind = PersonOrGroup;
type Error = LemmyError;
fn last_refreshed_at(&self) -> Option<DateTime<Utc>> {
Some(match self {
UserOrCommunity::User(p) => p.last_refreshed_at,
UserOrCommunity::Community(p) => p.last_refreshed_at,
})
}
async fn read_from_id(object_id: Url, data: &Data<Self::DataType>) -> LemmyResult<Option<Self>> {
let person = ApubPerson::read_from_id(object_id.clone(), data).await?;
Ok(match person {
Some(o) => Some(UserOrCommunity::User(o)),
None => ApubCommunity::read_from_id(object_id, data)
.await?
.map(UserOrCommunity::Community),
})
}
async fn delete(self, data: &Data<Self::DataType>) -> LemmyResult<()> {
match self {
UserOrCommunity::User(p) => p.delete(data).await,
UserOrCommunity::Community(p) => p.delete(data).await,
}
}
async fn into_json(self, data: &Data<Self::DataType>) -> LemmyResult<Self::Kind> {
Ok(match self {
UserOrCommunity::User(p) => PersonOrGroup::Person(p.into_json(data).await?),
UserOrCommunity::Community(p) => PersonOrGroup::Group(p.into_json(data).await?),
})
}
async fn verify(
apub: &Self::Kind,
expected_domain: &Url,
data: &Data<Self::DataType>,
) -> LemmyResult<()> {
match apub {
PersonOrGroup::Person(a) => ApubPerson::verify(a, expected_domain, data).await,
PersonOrGroup::Group(a) => ApubCommunity::verify(a, expected_domain, data).await,
}
}
async fn from_json(apub: Self::Kind, data: &Data<Self::DataType>) -> LemmyResult<Self> {
Ok(match apub {
PersonOrGroup::Person(p) => UserOrCommunity::User(ApubPerson::from_json(p, data).await?),
PersonOrGroup::Group(p) => {
UserOrCommunity::Community(ApubCommunity::from_json(p, data).await?)
}
})
}
}
impl Actor for UserOrCommunity {
fn id(&self) -> Url {
match self {
UserOrCommunity::User(u) => u.id(),
UserOrCommunity::Community(c) => c.id(),
}
}
fn public_key_pem(&self) -> &str {
match self {
UserOrCommunity::User(p) => p.public_key_pem(),
UserOrCommunity::Community(p) => p.public_key_pem(),
}
}
fn private_key_pem(&self) -> Option<String> {
match self {
UserOrCommunity::User(p) => p.private_key_pem(),
UserOrCommunity::Community(p) => p.private_key_pem(),
}
}
fn inbox(&self) -> Url {
match self {
UserOrCommunity::User(p) => p.inbox(),
UserOrCommunity::Community(p) => p.inbox(),
}
}
}
impl GetActorType for UserOrCommunity {
fn actor_type(&self) -> ActorType {
match self {
UserOrCommunity::User(p) => p.actor_type(),
UserOrCommunity::Community(p) => p.actor_type(),
}
}
}

View file

@ -6,7 +6,7 @@ use crate::{
community_moderators::ApubCommunityModerators,
community_outbox::ApubCommunityOutbox,
},
fetcher::site_or_community_or_user::SiteOrCommunityOrUser,
fetcher::{get_instance_id, SiteOrCommunityOrUser},
http::{check_community_fetchable, create_apub_response, create_apub_tombstone_response},
objects::community::ApubCommunity,
};
@ -90,12 +90,12 @@ async fn check_is_follower(
let signing_actor = signing_actor::<SiteOrCommunityOrUser>(&request, None, &context).await?;
CommunityFollowerView::check_has_followers_from_instance(
community.id,
signing_actor.instance_id(),
get_instance_id(&signing_actor),
&mut context.pool(),
)
.await?;
let instance_id = is_follower.dereference(&context).await?.instance_id();
let instance_id = get_instance_id(&is_follower.dereference(&context).await?);
let has_followers = CommunityFollowerView::check_has_followers_from_instance(
community.id,
instance_id,

View file

@ -1,6 +1,6 @@
use crate::{
activity_lists::SharedInboxActivities,
fetcher::{site_or_community_or_user::SiteOrCommunityOrUser, user_or_community::UserOrCommunity},
fetcher::{get_instance_id, SiteOrCommunityOrUser, UserOrCommunity},
protocol::objects::tombstone::Tombstone,
FEDERATION_CONTEXT,
};
@ -144,7 +144,7 @@ async fn check_community_content_fetchable(
Ok(
CommunityFollowerView::check_has_followers_from_instance(
community.id,
signing_actor.instance_id(),
get_instance_id(&signing_actor),
&mut context.pool(),
)
.await?,

View file

@ -1,4 +1,4 @@
use crate::fetcher::post_or_comment::PostOrComment;
use crate::fetcher::PostOrComment;
use activitypub_federation::{
config::{Data, UrlVerifier},
error::Error as ActivityPubError,

View file

@ -1,13 +1,10 @@
use super::{handle_community_moderators, person::ApubPerson};
use crate::{
activities::GetActorType,
fetcher::{
markdown_links::markdown_rewrite_remote_links_opt,
user_or_community::PersonOrGroupType,
},
fetcher::markdown_links::markdown_rewrite_remote_links_opt,
objects::{instance::fetch_instance_actor_for_object, read_from_string_or_source_opt},
protocol::{
objects::{group::Group, AttributedTo, LanguageTag},
objects::{group::Group, AttributedTo, LanguageTag, PersonOrGroupType},
ImageObject,
Source,
},

View file

@ -1,5 +1,5 @@
use crate::{
fetcher::post_or_comment::PostOrComment,
fetcher::PostOrComment,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::InCommunity,
};

View file

@ -1,4 +1,4 @@
use crate::{fetcher::user_or_community::UserOrCommunity, objects::person::ApubPerson};
use crate::{fetcher::UserOrCommunity, objects::person::ApubPerson};
use activitypub_federation::{
fetch::object_id::ObjectId,
kinds::activity::FollowType,

View file

@ -1,10 +1,13 @@
use crate::{
fetcher::post_or_comment::PostOrComment,
fetcher::PostOrComment,
objects::{community::ApubCommunity, person::ApubPerson},
protocol::InCommunity,
};
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
use either::Either;
use lemmy_api_common::context::LemmyContext;
use lemmy_db_schema::{source::community::Community, traits::Crud};
use lemmy_db_views::structs::{PostView, SiteView};
use lemmy_utils::error::{FederationError, LemmyError, LemmyResult};
use serde::{Deserialize, Serialize};
use strum::Display;
@ -49,12 +52,21 @@ impl From<&VoteType> for i16 {
impl InCommunity for Vote {
async fn community(&self, context: &Data<LemmyContext>) -> LemmyResult<ApubCommunity> {
let community = self
.object
.dereference(context)
.await?
.community(context)
.await?;
Ok(community)
let community = match self.object.dereference(context).await? {
Either::Left(p) => Community::read(&mut context.pool(), p.community_id).await?,
Either::Right(c) => {
let site_view = SiteView::read_local(&mut context.pool()).await?;
PostView::read(
&mut context.pool(),
c.post_id,
None,
site_view.instance.id,
false,
)
.await?
.community
}
};
Ok(community.into())
}
}

View file

@ -1,6 +1,6 @@
use crate::{
collections::community_moderators::ApubCommunityModerators,
fetcher::user_or_community::{PersonOrGroupType, UserOrCommunity},
fetcher::UserOrCommunity,
objects::person::ApubPerson,
};
use activitypub_federation::fetch::{collection_id::CollectionId, object_id::ObjectId};
@ -140,6 +140,12 @@ pub(crate) enum AttributedTo {
Peertube(Vec<AttributedToPeertube>),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub(crate) enum PersonOrGroupType {
Person,
Group,
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AttributedToPeertube {

View file

@ -1,5 +1,5 @@
use crate::{
fetcher::post_or_comment::PostOrComment,
fetcher::PostOrComment,
mentions::MentionOrValue,
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::{
@ -77,8 +77,8 @@ impl Note {
}
let parent = self.in_reply_to.dereference(context).await?;
match parent {
PostOrComment::Post(p) => Ok((p.clone(), None)),
PostOrComment::Comment(c) => {
PostOrComment::Left(p) => Ok((p.clone(), None)),
PostOrComment::Right(c) => {
let post_id = c.post_id;
let post = Post::read(&mut context.pool(), post_id).await?;
Ok((post.into(), Some(c.clone())))

View file

@ -1,8 +1,7 @@
use crate::{
fetcher::user_or_community::PersonOrGroupType,
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
protocol::{
objects::{AttributedTo, LanguageTag},
objects::{AttributedTo, LanguageTag, PersonOrGroupType},
ImageObject,
InCommunity,
Source,

View file

@ -3,7 +3,7 @@ use diesel::prelude::*;
use diesel_async::RunQueryDsl;
use lemmy_apub::{
activity_lists::SharedInboxActivities,
fetcher::{site_or_community_or_user::SiteOrCommunityOrUser, user_or_community::UserOrCommunity},
fetcher::{SiteOrCommunityOrUser, UserOrCommunity},
};
use lemmy_db_schema::{
newtypes::ActivityId,
@ -144,19 +144,19 @@ pub(crate) async fn get_actor_cached(
.try_get_with(actor_apub_id.clone(), async {
let url = actor_apub_id.clone().into();
let person = match actor_type {
ActorType::Site => SiteOrCommunityOrUser::Site(
ActorType::Site => SiteOrCommunityOrUser::Left(
Site::read_from_apub_id(pool, &url)
.await?
.context("apub site not found")?
.into(),
),
ActorType::Community => SiteOrCommunityOrUser::UserOrCommunity(UserOrCommunity::Community(
ActorType::Community => SiteOrCommunityOrUser::Right(UserOrCommunity::Right(
Community::read_from_apub_id(pool, &url)
.await?
.context("apub community not found")?
.into(),
)),
ActorType::Person => SiteOrCommunityOrUser::UserOrCommunity(UserOrCommunity::User(
ActorType::Person => SiteOrCommunityOrUser::Right(UserOrCommunity::Left(
Person::read_from_apub_id(pool, &url)
.await?
.context("apub person not found")?