mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-30 00:01:25 +00:00
Move object and collection structs to protocol folder
This commit is contained in:
parent
358ef99ea2
commit
5ff044346f
31 changed files with 605 additions and 508 deletions
|
@ -1,7 +1,7 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
cargo +nightly fmt
|
||||
cargo +nightly fmt -- --check
|
||||
|
||||
cargo +nightly clippy --workspace --tests --all-targets --all-features -- \
|
||||
-D warnings -D deprecated -D clippy::perf -D clippy::complexity -D clippy::dbg_macro
|
||||
|
|
|
@ -1,3 +1,20 @@
|
|||
use activitystreams::{link::Mention, public, unparsed::Unparsed};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use lemmy_api_common::{blocking, check_post_deleted_or_removed};
|
||||
use lemmy_apub_lib::{
|
||||
data::Data,
|
||||
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
|
||||
verify::verify_domains_match,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{community::Community, post::Post},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperationCrud};
|
||||
|
||||
use crate::{
|
||||
activities::{
|
||||
check_community_deleted_or_removed,
|
||||
|
@ -13,27 +30,9 @@ use crate::{
|
|||
CreateOrUpdateType,
|
||||
},
|
||||
fetcher::object_id::ObjectId,
|
||||
objects::{
|
||||
comment::{ApubComment, Note},
|
||||
community::ApubCommunity,
|
||||
person::ApubPerson,
|
||||
},
|
||||
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson},
|
||||
protocol::objects::note::Note,
|
||||
};
|
||||
use activitystreams::{link::Mention, public, unparsed::Unparsed};
|
||||
use lemmy_api_common::{blocking, check_post_deleted_or_removed};
|
||||
use lemmy_apub_lib::{
|
||||
data::Data,
|
||||
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
|
||||
verify::verify_domains_match,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{community::Community, post::Post},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{send::send_comment_ws_message, LemmyContext, UserOperationCrud};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -153,10 +152,12 @@ impl GetCommunity for CreateOrUpdateComment {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::objects::tests::file_to_json_object;
|
||||
use serial_test::serial;
|
||||
|
||||
use crate::objects::tests::file_to_json_object;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
#[serial]
|
||||
async fn test_parse_pleroma_create_comment() {
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
use activitystreams::{activity::kind::UpdateType, public, unparsed::Unparsed};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
data::Data,
|
||||
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::community::{Community, CommunityForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperationCrud};
|
||||
|
||||
use crate::{
|
||||
activities::{
|
||||
community::{
|
||||
|
@ -11,25 +27,9 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
},
|
||||
fetcher::object_id::ObjectId,
|
||||
objects::{
|
||||
community::{ApubCommunity, Group},
|
||||
person::ApubPerson,
|
||||
},
|
||||
objects::{community::ApubCommunity, person::ApubPerson},
|
||||
protocol::objects::group::Group,
|
||||
};
|
||||
use activitystreams::{activity::kind::UpdateType, public, unparsed::Unparsed};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
data::Data,
|
||||
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::community::{Community, CommunityForm},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{send::send_community_ws_message, LemmyContext, UserOperationCrud};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
/// This activity is received from a remote community mod, and updates the description or other
|
||||
/// fields of a local community.
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
use activitystreams::{public, unparsed::Unparsed};
|
||||
use anyhow::anyhow;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
data::Data,
|
||||
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
|
||||
verify::{verify_domains_match, verify_urls_match},
|
||||
};
|
||||
use lemmy_db_schema::{source::community::Community, traits::Crud};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud};
|
||||
|
||||
use crate::{
|
||||
activities::{
|
||||
check_community_deleted_or_removed,
|
||||
|
@ -13,25 +28,9 @@ use crate::{
|
|||
CreateOrUpdateType,
|
||||
},
|
||||
fetcher::object_id::ObjectId,
|
||||
objects::{
|
||||
community::ApubCommunity,
|
||||
person::ApubPerson,
|
||||
post::{ApubPost, Page},
|
||||
},
|
||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||
protocol::objects::page::Page,
|
||||
};
|
||||
use activitystreams::{public, unparsed::Unparsed};
|
||||
use anyhow::anyhow;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
data::Data,
|
||||
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
|
||||
verify::{verify_domains_match, verify_urls_match},
|
||||
};
|
||||
use lemmy_db_schema::{source::community::Community, traits::Crud};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::{send::send_post_ws_message, LemmyContext, UserOperationCrud};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
|
@ -7,10 +7,8 @@ use crate::{
|
|||
CreateOrUpdateType,
|
||||
},
|
||||
fetcher::object_id::ObjectId,
|
||||
objects::{
|
||||
person::ApubPerson,
|
||||
private_message::{ApubPrivateMessage, ChatMessage},
|
||||
},
|
||||
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
|
||||
protocol::objects::chat_message::ChatMessage,
|
||||
};
|
||||
use activitystreams::unparsed::Unparsed;
|
||||
use lemmy_api_common::blocking;
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
fetcher::object_id::ObjectId,
|
||||
generate_moderators_url,
|
||||
objects::person::ApubPerson,
|
||||
protocol::collections::group_moderators::GroupModerators,
|
||||
};
|
||||
use activitystreams::{chrono::NaiveDateTime, collection::kind::OrderedCollectionType};
|
||||
use lemmy_api_common::blocking;
|
||||
|
@ -13,17 +14,8 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
|
||||
use lemmy_utils::LemmyError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GroupModerators {
|
||||
r#type: OrderedCollectionType,
|
||||
id: Url,
|
||||
ordered_items: Vec<ObjectId<ApubPerson>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ApubCommunityModerators(pub(crate) Vec<CommunityModeratorView>);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
|||
collections::CommunityContext,
|
||||
generate_outbox_url,
|
||||
objects::{person::ApubPerson, post::ApubPost},
|
||||
protocol::collections::group_outbox::GroupOutbox,
|
||||
};
|
||||
use activitystreams::collection::kind::OrderedCollectionType;
|
||||
use chrono::NaiveDateTime;
|
||||
|
@ -17,18 +18,8 @@ use lemmy_db_schema::{
|
|||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GroupOutbox {
|
||||
r#type: OrderedCollectionType,
|
||||
id: Url,
|
||||
total_items: i32,
|
||||
ordered_items: Vec<CreateOrUpdatePost>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct ApubCommunityOutbox(Vec<ApubPost>);
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
use crate::objects::community::ApubCommunity;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
pub(crate) mod community_followers;
|
||||
use crate::objects::community::ApubCommunity;
|
||||
|
||||
pub(crate) mod community_moderators;
|
||||
pub(crate) mod community_outbox;
|
||||
pub(crate) mod user_outbox;
|
||||
|
||||
/// Put community in the data, so we dont have to read it again from the database.
|
||||
pub(crate) struct CommunityContext(pub ApubCommunity, pub LemmyContext);
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
use crate::objects::{
|
||||
comment::{ApubComment, Note},
|
||||
post::{ApubPost, Page},
|
||||
};
|
||||
use chrono::NaiveDateTime;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use lemmy_apub_lib::traits::ApubObject;
|
||||
use lemmy_db_schema::source::{comment::CommentForm, post::PostForm};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
objects::{comment::ApubComment, post::ApubPost},
|
||||
protocol::objects::{note::Note, page::Page},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PostOrComment {
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
use crate::{
|
||||
fetcher::object_id::ObjectId,
|
||||
objects::{
|
||||
comment::{ApubComment, Note},
|
||||
community::{ApubCommunity, Group},
|
||||
person::{ApubPerson, Person},
|
||||
post::{ApubPost, Page},
|
||||
},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use chrono::NaiveDateTime;
|
||||
use itertools::Itertools;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
traits::ApubObject,
|
||||
|
@ -21,8 +15,17 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
fetcher::object_id::ObjectId,
|
||||
objects::{
|
||||
comment::ApubComment,
|
||||
community::ApubCommunity,
|
||||
person::{ApubPerson, Person},
|
||||
post::ApubPost,
|
||||
},
|
||||
protocol::objects::{group::Group, note::Note, page::Page},
|
||||
};
|
||||
|
||||
/// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
|
||||
///
|
||||
|
|
|
@ -1,3 +1,16 @@
|
|||
use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
|
||||
verify::verify_domains_match,
|
||||
};
|
||||
use lemmy_db_schema::source::community::Community;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
use crate::{
|
||||
activities::{
|
||||
community::announce::{AnnouncableActivities, AnnounceActivity, GetCommunity},
|
||||
|
@ -6,7 +19,6 @@ use crate::{
|
|||
verify_person_in_community,
|
||||
},
|
||||
collections::{
|
||||
community_followers::CommunityFollowers,
|
||||
community_moderators::ApubCommunityModerators,
|
||||
community_outbox::ApubCommunityOutbox,
|
||||
CommunityContext,
|
||||
|
@ -21,18 +33,8 @@ use crate::{
|
|||
receive_activity,
|
||||
},
|
||||
objects::community::ApubCommunity,
|
||||
protocol::collections::group_followers::CommunityFollowers,
|
||||
};
|
||||
use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
traits::{ActivityFields, ActivityHandler, ActorType, ApubObject},
|
||||
verify::verify_domains_match,
|
||||
};
|
||||
use lemmy_db_schema::source::community::Community;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct CommunityQuery {
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::traits::{ActivityFields, ActivityHandler, ApubObject};
|
||||
use lemmy_db_schema::source::person::Person;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
|
||||
use crate::{
|
||||
activities::{
|
||||
community::announce::{AnnouncableActivities, AnnounceActivity},
|
||||
|
@ -8,7 +18,6 @@ use crate::{
|
|||
undo_delete::UndoDeletePrivateMessage,
|
||||
},
|
||||
},
|
||||
collections::user_outbox::UserOutbox,
|
||||
context::WithContext,
|
||||
http::{
|
||||
create_apub_response,
|
||||
|
@ -17,15 +26,8 @@ use crate::{
|
|||
receive_activity,
|
||||
},
|
||||
objects::person::ApubPerson,
|
||||
protocol::collections::person_outbox::UserOutbox,
|
||||
};
|
||||
use actix_web::{body::Body, web, web::Payload, HttpRequest, HttpResponse};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::traits::{ActivityFields, ActivityHandler, ApubObject};
|
||||
use lemmy_db_schema::source::person::Person;
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::info;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct PersonQuery {
|
||||
|
|
|
@ -5,6 +5,7 @@ pub mod fetcher;
|
|||
pub mod http;
|
||||
pub mod migrations;
|
||||
pub mod objects;
|
||||
pub(crate) mod protocol;
|
||||
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
|
|
@ -1,27 +1,17 @@
|
|||
use crate::{
|
||||
activities::{verify_is_public, verify_person_in_community},
|
||||
fetcher::object_id::ObjectId,
|
||||
objects::{
|
||||
community::ApubCommunity,
|
||||
person::ApubPerson,
|
||||
post::ApubPost,
|
||||
tombstone::Tombstone,
|
||||
Source,
|
||||
},
|
||||
PostOrComment,
|
||||
};
|
||||
use activitystreams::{object::kind::NoteType, public, unparsed::Unparsed};
|
||||
use std::ops::Deref;
|
||||
|
||||
use activitystreams::{object::kind::NoteType, public};
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
||||
use chrono::NaiveDateTime;
|
||||
use html2md::parse_html;
|
||||
use url::Url;
|
||||
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
traits::ApubObject,
|
||||
values::{MediaTypeHtml, MediaTypeMarkdown},
|
||||
verify::verify_domains_match,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::CommentId,
|
||||
source::{
|
||||
comment::{Comment, CommentForm},
|
||||
community::Community,
|
||||
|
@ -35,100 +25,19 @@ use lemmy_utils::{
|
|||
LemmyError,
|
||||
};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use std::ops::Deref;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Note {
|
||||
r#type: NoteType,
|
||||
id: Url,
|
||||
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
||||
/// Indicates that the object is publicly readable. Unlike [`Post.to`], this one doesn't contain
|
||||
/// the community ID, as it would be incompatible with Pleroma (and we can get the community from
|
||||
/// the post in [`in_reply_to`]).
|
||||
to: Vec<Url>,
|
||||
content: String,
|
||||
media_type: Option<MediaTypeHtml>,
|
||||
source: SourceCompat,
|
||||
in_reply_to: ObjectId<PostOrComment>,
|
||||
published: Option<DateTime<FixedOffset>>,
|
||||
updated: Option<DateTime<FixedOffset>>,
|
||||
#[serde(flatten)]
|
||||
unparsed: Unparsed,
|
||||
}
|
||||
|
||||
/// Pleroma puts a raw string in the source, so we have to handle it here for deserialization to work
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
enum SourceCompat {
|
||||
Lemmy(Source),
|
||||
Pleroma(String),
|
||||
}
|
||||
|
||||
impl Note {
|
||||
pub(crate) fn id_unchecked(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||
verify_domains_match(&self.id, expected_domain)?;
|
||||
Ok(&self.id)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_parents(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(ApubPost, Option<CommentId>), LemmyError> {
|
||||
// Fetch parent comment chain in a box, otherwise it can cause a stack overflow.
|
||||
let parent = Box::pin(
|
||||
self
|
||||
.in_reply_to
|
||||
.dereference(context, request_counter)
|
||||
.await?,
|
||||
);
|
||||
match parent.deref() {
|
||||
PostOrComment::Post(p) => {
|
||||
// Workaround because I cant figure out how to get the post out of the box (and we dont
|
||||
// want to stackoverflow in a deep comment hierarchy).
|
||||
let post_id = p.id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
Ok((post.into(), None))
|
||||
}
|
||||
PostOrComment::Comment(c) => {
|
||||
let post_id = c.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
Ok((post.into(), Some(c.id)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let (post, _parent_comment_id) = self.get_parents(context, request_counter).await?;
|
||||
let community_id = post.community_id;
|
||||
let community: ApubCommunity = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
|
||||
if post.locked {
|
||||
return Err(anyhow!("Post is locked").into());
|
||||
}
|
||||
verify_domains_match(self.attributed_to.inner(), &self.id)?;
|
||||
verify_person_in_community(&self.attributed_to, &community, context, request_counter).await?;
|
||||
verify_is_public(&self.to)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
use crate::{
|
||||
activities::verify_person_in_community,
|
||||
fetcher::object_id::ObjectId,
|
||||
protocol::{
|
||||
objects::{
|
||||
note::{Note, SourceCompat},
|
||||
tombstone::Tombstone,
|
||||
},
|
||||
Source,
|
||||
},
|
||||
PostOrComment,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ApubComment(Comment);
|
||||
|
@ -277,13 +186,16 @@ impl ApubObject for ApubComment {
|
|||
|
||||
#[cfg(test)]
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use assert_json_diff::assert_json_include;
|
||||
use serial_test::serial;
|
||||
|
||||
use crate::objects::{
|
||||
community::ApubCommunity,
|
||||
tests::{file_to_json_object, init_context},
|
||||
};
|
||||
use assert_json_diff::assert_json_include;
|
||||
use serial_test::serial;
|
||||
|
||||
use super::*;
|
||||
use crate::objects::{person::ApubPerson, post::ApubPost};
|
||||
|
||||
pub(crate) async fn prepare_comment_test(
|
||||
url: &Url,
|
||||
|
|
|
@ -1,117 +1,40 @@
|
|||
use crate::{
|
||||
check_is_apub_id_valid,
|
||||
collections::{
|
||||
community_moderators::ApubCommunityModerators,
|
||||
community_outbox::ApubCommunityOutbox,
|
||||
CommunityContext,
|
||||
},
|
||||
fetcher::object_id::ObjectId,
|
||||
generate_moderators_url,
|
||||
generate_outbox_url,
|
||||
objects::{get_summary_from_string_or_source, tombstone::Tombstone, ImageObject, Source},
|
||||
};
|
||||
use std::ops::Deref;
|
||||
|
||||
use activitystreams::{
|
||||
actor::{kind::GroupType, Endpoints},
|
||||
object::kind::ImageType,
|
||||
unparsed::Unparsed,
|
||||
};
|
||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
||||
use chrono::NaiveDateTime;
|
||||
use itertools::Itertools;
|
||||
use log::debug;
|
||||
use url::Url;
|
||||
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
signatures::PublicKey,
|
||||
traits::{ActorType, ApubObject},
|
||||
values::MediaTypeMarkdown,
|
||||
verify::verify_domains_match,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
naive_now,
|
||||
source::community::{Community, CommunityForm},
|
||||
DbPool,
|
||||
};
|
||||
use lemmy_db_schema::{source::community::Community, DbPool};
|
||||
use lemmy_db_views_actor::community_follower_view::CommunityFollowerView;
|
||||
use lemmy_utils::{
|
||||
settings::structs::Settings,
|
||||
utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html},
|
||||
utils::{convert_datetime, markdown_to_html},
|
||||
LemmyError,
|
||||
};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use log::debug;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use std::ops::Deref;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Group {
|
||||
#[serde(rename = "type")]
|
||||
kind: GroupType,
|
||||
pub(crate) id: Url,
|
||||
/// username, set at account creation and can never be changed
|
||||
preferred_username: String,
|
||||
/// title (can be changed at any time)
|
||||
name: String,
|
||||
summary: Option<String>,
|
||||
source: Option<Source>,
|
||||
icon: Option<ImageObject>,
|
||||
/// banner
|
||||
image: Option<ImageObject>,
|
||||
// lemmy extension
|
||||
sensitive: Option<bool>,
|
||||
// lemmy extension
|
||||
pub(crate) moderators: Option<ObjectId<ApubCommunityModerators>>,
|
||||
inbox: Url,
|
||||
pub(crate) outbox: ObjectId<ApubCommunityOutbox>,
|
||||
followers: Url,
|
||||
endpoints: Endpoints<Url>,
|
||||
public_key: PublicKey,
|
||||
published: Option<DateTime<FixedOffset>>,
|
||||
updated: Option<DateTime<FixedOffset>>,
|
||||
#[serde(flatten)]
|
||||
unparsed: Unparsed,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
pub(crate) async fn from_apub_to_form(
|
||||
group: &Group,
|
||||
expected_domain: &Url,
|
||||
settings: &Settings,
|
||||
) -> Result<CommunityForm, LemmyError> {
|
||||
verify_domains_match(expected_domain, &group.id)?;
|
||||
let name = group.preferred_username.clone();
|
||||
let title = group.name.clone();
|
||||
let description = get_summary_from_string_or_source(&group.summary, &group.source);
|
||||
let shared_inbox = group.endpoints.shared_inbox.clone().map(|s| s.into());
|
||||
|
||||
let slur_regex = &settings.slur_regex();
|
||||
check_slurs(&name, slur_regex)?;
|
||||
check_slurs(&title, slur_regex)?;
|
||||
check_slurs_opt(&description, slur_regex)?;
|
||||
|
||||
Ok(CommunityForm {
|
||||
name,
|
||||
title,
|
||||
description,
|
||||
removed: None,
|
||||
published: group.published.map(|u| u.naive_local()),
|
||||
updated: group.updated.map(|u| u.naive_local()),
|
||||
deleted: None,
|
||||
nsfw: Some(group.sensitive.unwrap_or(false)),
|
||||
actor_id: Some(group.id.clone().into()),
|
||||
local: Some(false),
|
||||
private_key: None,
|
||||
public_key: Some(group.public_key.public_key_pem.clone()),
|
||||
last_refreshed_at: Some(naive_now()),
|
||||
icon: Some(group.icon.clone().map(|i| i.url.into())),
|
||||
banner: Some(group.image.clone().map(|i| i.url.into())),
|
||||
followers_url: Some(group.followers.clone().into()),
|
||||
inbox_url: Some(group.inbox.clone().into()),
|
||||
shared_inbox_url: Some(shared_inbox),
|
||||
})
|
||||
}
|
||||
}
|
||||
use crate::{
|
||||
check_is_apub_id_valid,
|
||||
collections::{community_moderators::ApubCommunityModerators, CommunityContext},
|
||||
fetcher::object_id::ObjectId,
|
||||
generate_moderators_url,
|
||||
generate_outbox_url,
|
||||
protocol::{
|
||||
objects::{group::Group, tombstone::Tombstone},
|
||||
ImageObject,
|
||||
Source,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ApubCommunity(Community);
|
||||
|
@ -300,12 +223,15 @@ impl ApubCommunity {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::objects::tests::{file_to_json_object, init_context};
|
||||
use assert_json_diff::assert_json_include;
|
||||
use lemmy_db_schema::traits::Crud;
|
||||
use serial_test::serial;
|
||||
|
||||
use lemmy_db_schema::traits::Crud;
|
||||
|
||||
use crate::objects::tests::{file_to_json_object, init_context};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
#[serial]
|
||||
async fn test_parse_lemmy_community() {
|
||||
|
|
|
@ -1,32 +1,13 @@
|
|||
use activitystreams::object::kind::ImageType;
|
||||
use crate::protocol::Source;
|
||||
use html2md::parse_html;
|
||||
use lemmy_apub_lib::values::MediaTypeMarkdown;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
pub mod comment;
|
||||
pub mod community;
|
||||
pub mod person;
|
||||
pub mod post;
|
||||
pub mod private_message;
|
||||
pub mod tombstone;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Source {
|
||||
content: String,
|
||||
media_type: MediaTypeMarkdown,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ImageObject {
|
||||
#[serde(rename = "type")]
|
||||
kind: ImageType,
|
||||
url: Url,
|
||||
}
|
||||
|
||||
fn get_summary_from_string_or_source(
|
||||
pub(crate) fn get_summary_from_string_or_source(
|
||||
raw: &Option<String>,
|
||||
source: &Option<Source>,
|
||||
) -> Option<String> {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use crate::{
|
||||
check_is_apub_id_valid,
|
||||
generate_outbox_url,
|
||||
objects::{get_summary_from_string_or_source, ImageObject, Source},
|
||||
objects::get_summary_from_string_or_source,
|
||||
protocol::{ImageObject, Source},
|
||||
};
|
||||
use activitystreams::{actor::Endpoints, object::kind::ImageType, unparsed::Unparsed};
|
||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
use crate::{
|
||||
activities::{verify_is_public, verify_person_in_community},
|
||||
activities::verify_person_in_community,
|
||||
fetcher::object_id::ObjectId,
|
||||
objects::{
|
||||
community::ApubCommunity,
|
||||
person::ApubPerson,
|
||||
tombstone::Tombstone,
|
||||
protocol::{
|
||||
objects::{page::Page, tombstone::Tombstone},
|
||||
ImageObject,
|
||||
Source,
|
||||
},
|
||||
|
@ -12,15 +10,12 @@ use crate::{
|
|||
use activitystreams::{
|
||||
object::kind::{ImageType, PageType},
|
||||
public,
|
||||
unparsed::Unparsed,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
||||
use chrono::NaiveDateTime;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
traits::ApubObject,
|
||||
values::{MediaTypeHtml, MediaTypeMarkdown},
|
||||
verify::verify_domains_match,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
self,
|
||||
|
@ -33,97 +28,13 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_utils::{
|
||||
request::fetch_site_data,
|
||||
utils::{check_slurs, convert_datetime, markdown_to_html, remove_slurs},
|
||||
utils::{convert_datetime, markdown_to_html, remove_slurs},
|
||||
LemmyError,
|
||||
};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use std::ops::Deref;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Page {
|
||||
r#type: PageType,
|
||||
id: Url,
|
||||
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
||||
to: Vec<Url>,
|
||||
name: String,
|
||||
content: Option<String>,
|
||||
media_type: Option<MediaTypeHtml>,
|
||||
source: Option<Source>,
|
||||
url: Option<Url>,
|
||||
image: Option<ImageObject>,
|
||||
pub(crate) comments_enabled: Option<bool>,
|
||||
sensitive: Option<bool>,
|
||||
pub(crate) stickied: Option<bool>,
|
||||
published: Option<DateTime<FixedOffset>>,
|
||||
updated: Option<DateTime<FixedOffset>>,
|
||||
#[serde(flatten)]
|
||||
unparsed: Unparsed,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub(crate) fn id_unchecked(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||
verify_domains_match(&self.id, expected_domain)?;
|
||||
Ok(&self.id)
|
||||
}
|
||||
|
||||
/// Only mods can change the post's stickied/locked status. So if either of these is changed from
|
||||
/// the current value, it is a mod action and needs to be verified as such.
|
||||
///
|
||||
/// Both stickied and locked need to be false on a newly created post (verified in [[CreatePost]].
|
||||
pub(crate) async fn is_mod_action(&self, context: &LemmyContext) -> Result<bool, LemmyError> {
|
||||
let old_post = ObjectId::<ApubPost>::new(self.id.clone())
|
||||
.dereference_local(context)
|
||||
.await;
|
||||
|
||||
let is_mod_action = if let Ok(old_post) = old_post {
|
||||
self.stickied != Some(old_post.stickied) || self.comments_enabled != Some(!old_post.locked)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Ok(is_mod_action)
|
||||
}
|
||||
|
||||
pub(crate) async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community = self.extract_community(context, request_counter).await?;
|
||||
|
||||
check_slurs(&self.name, &context.settings().slur_regex())?;
|
||||
verify_domains_match(self.attributed_to.inner(), &self.id.clone())?;
|
||||
verify_person_in_community(&self.attributed_to, &community, context, request_counter).await?;
|
||||
verify_is_public(&self.to.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn extract_community(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let mut to_iter = self.to.iter();
|
||||
loop {
|
||||
if let Some(cid) = to_iter.next() {
|
||||
let cid = ObjectId::new(cid.clone());
|
||||
if let Ok(c) = cid.dereference(context, request_counter).await {
|
||||
break Ok(c);
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow!("No community found in cc").into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ApubPost(Post);
|
||||
|
||||
|
@ -283,6 +194,8 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::objects::{
|
||||
community::ApubCommunity,
|
||||
person::ApubPerson,
|
||||
post::ApubPost,
|
||||
tests::{file_to_json_object, init_context},
|
||||
};
|
||||
use assert_json_diff::assert_json_include;
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
use crate::{
|
||||
fetcher::object_id::ObjectId,
|
||||
objects::{person::ApubPerson, Source},
|
||||
protocol::{
|
||||
objects::chat_message::{ChatMessage, ChatMessageType},
|
||||
Source,
|
||||
},
|
||||
};
|
||||
use activitystreams::unparsed::Unparsed;
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
||||
use chrono::NaiveDateTime;
|
||||
use html2md::parse_html;
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{
|
||||
traits::ApubObject,
|
||||
values::{MediaTypeHtml, MediaTypeMarkdown},
|
||||
verify::verify_domains_match,
|
||||
};
|
||||
use lemmy_db_schema::{
|
||||
source::{
|
||||
|
@ -21,60 +21,9 @@ use lemmy_db_schema::{
|
|||
};
|
||||
use lemmy_utils::{utils::convert_datetime, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use std::ops::Deref;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ChatMessage {
|
||||
r#type: ChatMessageType,
|
||||
id: Url,
|
||||
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
||||
to: [ObjectId<ApubPerson>; 1],
|
||||
content: String,
|
||||
media_type: Option<MediaTypeHtml>,
|
||||
source: Option<Source>,
|
||||
published: Option<DateTime<FixedOffset>>,
|
||||
updated: Option<DateTime<FixedOffset>>,
|
||||
#[serde(flatten)]
|
||||
unparsed: Unparsed,
|
||||
}
|
||||
|
||||
/// https://docs.pleroma.social/backend/development/ap_extensions/#chatmessages
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum ChatMessageType {
|
||||
ChatMessage,
|
||||
}
|
||||
|
||||
impl ChatMessage {
|
||||
pub(crate) fn id_unchecked(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||
verify_domains_match(&self.id, expected_domain)?;
|
||||
Ok(&self.id)
|
||||
}
|
||||
|
||||
pub(crate) async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_domains_match(self.attributed_to.inner(), &self.id)?;
|
||||
let person = self
|
||||
.attributed_to
|
||||
.dereference(context, request_counter)
|
||||
.await?;
|
||||
if person.banned {
|
||||
return Err(anyhow!("Person is banned from site").into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ApubPrivateMessage(PrivateMessage);
|
||||
|
||||
|
@ -189,7 +138,10 @@ impl ApubObject for ApubPrivateMessage {
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::objects::tests::{file_to_json_object, init_context};
|
||||
use crate::objects::{
|
||||
person::ApubPerson,
|
||||
tests::{file_to_json_object, init_context},
|
||||
};
|
||||
use assert_json_diff::assert_json_include;
|
||||
use serial_test::serial;
|
||||
|
||||
|
|
12
crates/apub/src/protocol/collections/group_moderators.rs
Normal file
12
crates/apub/src/protocol/collections/group_moderators.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use crate::{fetcher::object_id::ObjectId, objects::person::ApubPerson};
|
||||
use activitystreams::collection::kind::OrderedCollectionType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GroupModerators {
|
||||
pub(crate) r#type: OrderedCollectionType,
|
||||
pub(crate) id: Url,
|
||||
pub(crate) ordered_items: Vec<ObjectId<ApubPerson>>,
|
||||
}
|
13
crates/apub/src/protocol/collections/group_outbox.rs
Normal file
13
crates/apub/src/protocol/collections/group_outbox.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
use crate::activities::post::create_or_update::CreateOrUpdatePost;
|
||||
use activitystreams::collection::kind::OrderedCollectionType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct GroupOutbox {
|
||||
pub(crate) r#type: OrderedCollectionType,
|
||||
pub(crate) id: Url,
|
||||
pub(crate) total_items: i32,
|
||||
pub(crate) ordered_items: Vec<CreateOrUpdatePost>,
|
||||
}
|
4
crates/apub/src/protocol/collections/mod.rs
Normal file
4
crates/apub/src/protocol/collections/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub(crate) mod group_followers;
|
||||
pub(crate) mod group_moderators;
|
||||
pub(crate) mod group_outbox;
|
||||
pub(crate) mod person_outbox;
|
23
crates/apub/src/protocol/mod.rs
Normal file
23
crates/apub/src/protocol/mod.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use activitystreams::object::kind::ImageType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
use lemmy_apub_lib::values::MediaTypeMarkdown;
|
||||
|
||||
pub(crate) mod collections;
|
||||
pub(crate) mod objects;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Source {
|
||||
pub(crate) content: String,
|
||||
pub(crate) media_type: MediaTypeMarkdown,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ImageObject {
|
||||
#[serde(rename = "type")]
|
||||
pub(crate) kind: ImageType,
|
||||
pub(crate) url: Url,
|
||||
}
|
61
crates/apub/src/protocol/objects/chat_message.rs
Normal file
61
crates/apub/src/protocol/objects/chat_message.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use crate::{fetcher::object_id::ObjectId, objects::person::ApubPerson, protocol::Source};
|
||||
use activitystreams::{
|
||||
chrono::{DateTime, FixedOffset},
|
||||
unparsed::Unparsed,
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ChatMessage {
|
||||
pub(crate) r#type: ChatMessageType,
|
||||
pub(crate) id: Url,
|
||||
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
||||
pub(crate) to: [ObjectId<ApubPerson>; 1],
|
||||
pub(crate) content: String,
|
||||
pub(crate) media_type: Option<MediaTypeHtml>,
|
||||
pub(crate) source: Option<Source>,
|
||||
pub(crate) published: Option<DateTime<FixedOffset>>,
|
||||
pub(crate) updated: Option<DateTime<FixedOffset>>,
|
||||
#[serde(flatten)]
|
||||
pub(crate) unparsed: Unparsed,
|
||||
}
|
||||
|
||||
/// https://docs.pleroma.social/backend/development/ap_extensions/#chatmessages
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum ChatMessageType {
|
||||
ChatMessage,
|
||||
}
|
||||
|
||||
impl ChatMessage {
|
||||
pub(crate) fn id_unchecked(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||
verify_domains_match(&self.id, expected_domain)?;
|
||||
Ok(&self.id)
|
||||
}
|
||||
|
||||
pub(crate) async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
verify_domains_match(self.attributed_to.inner(), &self.id)?;
|
||||
let person = self
|
||||
.attributed_to
|
||||
.dereference(context, request_counter)
|
||||
.await?;
|
||||
if person.banned {
|
||||
return Err(anyhow!("Person is banned from site").into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
95
crates/apub/src/protocol/objects/group.rs
Normal file
95
crates/apub/src/protocol/objects/group.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use crate::{
|
||||
collections::{
|
||||
community_moderators::ApubCommunityModerators,
|
||||
community_outbox::ApubCommunityOutbox,
|
||||
},
|
||||
fetcher::object_id::ObjectId,
|
||||
objects::get_summary_from_string_or_source,
|
||||
protocol::{ImageObject, Source},
|
||||
};
|
||||
use activitystreams::{
|
||||
actor::{kind::GroupType, Endpoints},
|
||||
unparsed::Unparsed,
|
||||
};
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use lemmy_apub_lib::{signatures::PublicKey, verify::verify_domains_match};
|
||||
use lemmy_db_schema::{naive_now, source::community::CommunityForm};
|
||||
use lemmy_utils::{
|
||||
settings::structs::Settings,
|
||||
utils::{check_slurs, check_slurs_opt},
|
||||
LemmyError,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Group {
|
||||
#[serde(rename = "type")]
|
||||
pub(crate) kind: GroupType,
|
||||
pub(crate) id: Url,
|
||||
/// username, set at account creation and can never be changed
|
||||
pub(crate) preferred_username: String,
|
||||
/// title (can be changed at any time)
|
||||
pub(crate) name: String,
|
||||
pub(crate) summary: Option<String>,
|
||||
pub(crate) source: Option<Source>,
|
||||
pub(crate) icon: Option<ImageObject>,
|
||||
/// banner
|
||||
pub(crate) image: Option<ImageObject>,
|
||||
// lemmy extension
|
||||
pub(crate) sensitive: Option<bool>,
|
||||
// lemmy extension
|
||||
pub(crate) moderators: Option<ObjectId<ApubCommunityModerators>>,
|
||||
pub(crate) inbox: Url,
|
||||
pub(crate) outbox: ObjectId<ApubCommunityOutbox>,
|
||||
pub(crate) followers: Url,
|
||||
pub(crate) endpoints: Endpoints<Url>,
|
||||
pub(crate) public_key: PublicKey,
|
||||
pub(crate) published: Option<DateTime<FixedOffset>>,
|
||||
pub(crate) updated: Option<DateTime<FixedOffset>>,
|
||||
#[serde(flatten)]
|
||||
pub(crate) unparsed: Unparsed,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
pub(crate) async fn from_apub_to_form(
|
||||
group: &Group,
|
||||
expected_domain: &Url,
|
||||
settings: &Settings,
|
||||
) -> Result<CommunityForm, LemmyError> {
|
||||
verify_domains_match(expected_domain, &group.id)?;
|
||||
let name = group.preferred_username.clone();
|
||||
let title = group.name.clone();
|
||||
let description = get_summary_from_string_or_source(&group.summary, &group.source);
|
||||
let shared_inbox = group.endpoints.shared_inbox.clone().map(|s| s.into());
|
||||
|
||||
let slur_regex = &settings.slur_regex();
|
||||
check_slurs(&name, slur_regex)?;
|
||||
check_slurs(&title, slur_regex)?;
|
||||
check_slurs_opt(&description, slur_regex)?;
|
||||
|
||||
Ok(CommunityForm {
|
||||
name,
|
||||
title,
|
||||
description,
|
||||
removed: None,
|
||||
published: group.published.map(|u| u.naive_local()),
|
||||
updated: group.updated.map(|u| u.naive_local()),
|
||||
deleted: None,
|
||||
nsfw: Some(group.sensitive.unwrap_or(false)),
|
||||
actor_id: Some(group.id.clone().into()),
|
||||
local: Some(false),
|
||||
private_key: None,
|
||||
public_key: Some(group.public_key.public_key_pem.clone()),
|
||||
last_refreshed_at: Some(naive_now()),
|
||||
icon: Some(group.icon.clone().map(|i| i.url.into())),
|
||||
banner: Some(group.image.clone().map(|i| i.url.into())),
|
||||
followers_url: Some(group.followers.clone().into()),
|
||||
inbox_url: Some(group.inbox.clone().into()),
|
||||
shared_inbox_url: Some(shared_inbox),
|
||||
})
|
||||
}
|
||||
}
|
5
crates/apub/src/protocol/objects/mod.rs
Normal file
5
crates/apub/src/protocol/objects/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub(crate) mod chat_message;
|
||||
pub(crate) mod group;
|
||||
pub(crate) mod note;
|
||||
pub(crate) mod page;
|
||||
pub(crate) mod tombstone;
|
112
crates/apub/src/protocol/objects/note.rs
Normal file
112
crates/apub/src/protocol/objects/note.rs
Normal file
|
@ -0,0 +1,112 @@
|
|||
use crate::{
|
||||
activities::{verify_is_public, verify_person_in_community},
|
||||
fetcher::{object_id::ObjectId, post_or_comment::PostOrComment},
|
||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||
protocol::Source,
|
||||
};
|
||||
use activitystreams::{object::kind::NoteType, unparsed::Unparsed};
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use lemmy_api_common::blocking;
|
||||
use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match};
|
||||
use lemmy_db_schema::{
|
||||
newtypes::CommentId,
|
||||
source::{community::Community, post::Post},
|
||||
traits::Crud,
|
||||
};
|
||||
use lemmy_utils::LemmyError;
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use std::ops::Deref;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Note {
|
||||
pub(crate) r#type: NoteType,
|
||||
pub(crate) id: Url,
|
||||
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
||||
/// Indicates that the object is publicly readable. Unlike [`Post.to`], this one doesn't contain
|
||||
/// the community ID, as it would be incompatible with Pleroma (and we can get the community from
|
||||
/// the post in [`in_reply_to`]).
|
||||
pub(crate) to: Vec<Url>,
|
||||
pub(crate) content: String,
|
||||
pub(crate) media_type: Option<MediaTypeHtml>,
|
||||
pub(crate) source: SourceCompat,
|
||||
pub(crate) in_reply_to: ObjectId<PostOrComment>,
|
||||
pub(crate) published: Option<DateTime<FixedOffset>>,
|
||||
pub(crate) updated: Option<DateTime<FixedOffset>>,
|
||||
#[serde(flatten)]
|
||||
pub(crate) unparsed: Unparsed,
|
||||
}
|
||||
|
||||
/// Pleroma puts a raw string in the source, so we have to handle it here for deserialization to work
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(untagged)]
|
||||
pub(crate) enum SourceCompat {
|
||||
Lemmy(Source),
|
||||
Pleroma(String),
|
||||
}
|
||||
|
||||
impl Note {
|
||||
pub(crate) fn id_unchecked(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||
verify_domains_match(&self.id, expected_domain)?;
|
||||
Ok(&self.id)
|
||||
}
|
||||
|
||||
pub(crate) async fn get_parents(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(ApubPost, Option<CommentId>), LemmyError> {
|
||||
// Fetch parent comment chain in a box, otherwise it can cause a stack overflow.
|
||||
let parent = Box::pin(
|
||||
self
|
||||
.in_reply_to
|
||||
.dereference(context, request_counter)
|
||||
.await?,
|
||||
);
|
||||
match parent.deref() {
|
||||
PostOrComment::Post(p) => {
|
||||
// Workaround because I cant figure out how to get the post out of the box (and we dont
|
||||
// want to stackoverflow in a deep comment hierarchy).
|
||||
let post_id = p.id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
Ok((post.into(), None))
|
||||
}
|
||||
PostOrComment::Comment(c) => {
|
||||
let post_id = c.post_id;
|
||||
let post = blocking(context.pool(), move |conn| Post::read(conn, post_id)).await??;
|
||||
Ok((post.into(), Some(c.id)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let (post, _parent_comment_id) = self.get_parents(context, request_counter).await?;
|
||||
let community_id = post.community_id;
|
||||
let community: ApubCommunity = blocking(context.pool(), move |conn| {
|
||||
Community::read(conn, community_id)
|
||||
})
|
||||
.await??
|
||||
.into();
|
||||
|
||||
if post.locked {
|
||||
return Err(anyhow!("Post is locked").into());
|
||||
}
|
||||
verify_domains_match(self.attributed_to.inner(), &self.id)?;
|
||||
verify_person_in_community(&self.attributed_to, &community, context, request_counter).await?;
|
||||
verify_is_public(&self.to)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
97
crates/apub/src/protocol/objects/page.rs
Normal file
97
crates/apub/src/protocol/objects/page.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
use crate::{
|
||||
activities::{verify_is_public, verify_person_in_community},
|
||||
fetcher::object_id::ObjectId,
|
||||
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||
protocol::{ImageObject, Source},
|
||||
};
|
||||
use activitystreams::{object::kind::PageType, unparsed::Unparsed};
|
||||
use anyhow::anyhow;
|
||||
use chrono::{DateTime, FixedOffset};
|
||||
use lemmy_apub_lib::{values::MediaTypeHtml, verify::verify_domains_match};
|
||||
use lemmy_utils::{utils::check_slurs, LemmyError};
|
||||
use lemmy_websocket::LemmyContext;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::skip_serializing_none;
|
||||
use url::Url;
|
||||
|
||||
#[skip_serializing_none]
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct Page {
|
||||
pub(crate) r#type: PageType,
|
||||
pub(crate) id: Url,
|
||||
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
||||
pub(crate) to: Vec<Url>,
|
||||
pub(crate) name: String,
|
||||
pub(crate) content: Option<String>,
|
||||
pub(crate) media_type: Option<MediaTypeHtml>,
|
||||
pub(crate) source: Option<Source>,
|
||||
pub(crate) url: Option<Url>,
|
||||
pub(crate) image: Option<ImageObject>,
|
||||
pub(crate) comments_enabled: Option<bool>,
|
||||
pub(crate) sensitive: Option<bool>,
|
||||
pub(crate) stickied: Option<bool>,
|
||||
pub(crate) published: Option<DateTime<FixedOffset>>,
|
||||
pub(crate) updated: Option<DateTime<FixedOffset>>,
|
||||
#[serde(flatten)]
|
||||
pub(crate) unparsed: Unparsed,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub(crate) fn id_unchecked(&self) -> &Url {
|
||||
&self.id
|
||||
}
|
||||
pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
|
||||
verify_domains_match(&self.id, expected_domain)?;
|
||||
Ok(&self.id)
|
||||
}
|
||||
|
||||
/// Only mods can change the post's stickied/locked status. So if either of these is changed from
|
||||
/// the current value, it is a mod action and needs to be verified as such.
|
||||
///
|
||||
/// Both stickied and locked need to be false on a newly created post (verified in [[CreatePost]].
|
||||
pub(crate) async fn is_mod_action(&self, context: &LemmyContext) -> Result<bool, LemmyError> {
|
||||
let old_post = ObjectId::<ApubPost>::new(self.id.clone())
|
||||
.dereference_local(context)
|
||||
.await;
|
||||
|
||||
let is_mod_action = if let Ok(old_post) = old_post {
|
||||
self.stickied != Some(old_post.stickied) || self.comments_enabled != Some(!old_post.locked)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Ok(is_mod_action)
|
||||
}
|
||||
|
||||
pub(crate) async fn verify(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<(), LemmyError> {
|
||||
let community = self.extract_community(context, request_counter).await?;
|
||||
|
||||
check_slurs(&self.name, &context.settings().slur_regex())?;
|
||||
verify_domains_match(self.attributed_to.inner(), &self.id.clone())?;
|
||||
verify_person_in_community(&self.attributed_to, &community, context, request_counter).await?;
|
||||
verify_is_public(&self.to.clone())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn extract_community(
|
||||
&self,
|
||||
context: &LemmyContext,
|
||||
request_counter: &mut i32,
|
||||
) -> Result<ApubCommunity, LemmyError> {
|
||||
let mut to_iter = self.to.iter();
|
||||
loop {
|
||||
if let Some(cid) = to_iter.next() {
|
||||
let cid = ObjectId::new(cid.clone());
|
||||
if let Ok(c) = cid.dereference(context, request_counter).await {
|
||||
break Ok(c);
|
||||
}
|
||||
} else {
|
||||
return Err(anyhow!("No community found in cc").into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue