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
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
cargo +nightly fmt
|
cargo +nightly fmt -- --check
|
||||||
|
|
||||||
cargo +nightly clippy --workspace --tests --all-targets --all-features -- \
|
cargo +nightly clippy --workspace --tests --all-targets --all-features -- \
|
||||||
-D warnings -D deprecated -D clippy::perf -D clippy::complexity -D clippy::dbg_macro
|
-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::{
|
use crate::{
|
||||||
activities::{
|
activities::{
|
||||||
check_community_deleted_or_removed,
|
check_community_deleted_or_removed,
|
||||||
|
@ -13,27 +30,9 @@ use crate::{
|
||||||
CreateOrUpdateType,
|
CreateOrUpdateType,
|
||||||
},
|
},
|
||||||
fetcher::object_id::ObjectId,
|
fetcher::object_id::ObjectId,
|
||||||
objects::{
|
objects::{comment::ApubComment, community::ApubCommunity, person::ApubPerson},
|
||||||
comment::{ApubComment, Note},
|
protocol::objects::note::Note,
|
||||||
community::ApubCommunity,
|
|
||||||
person::ApubPerson,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
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)]
|
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
@ -153,10 +152,12 @@ impl GetCommunity for CreateOrUpdateComment {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use crate::objects::tests::file_to_json_object;
|
|
||||||
use serial_test::serial;
|
use serial_test::serial;
|
||||||
|
|
||||||
|
use crate::objects::tests::file_to_json_object;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn test_parse_pleroma_create_comment() {
|
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::{
|
use crate::{
|
||||||
activities::{
|
activities::{
|
||||||
community::{
|
community::{
|
||||||
|
@ -11,25 +27,9 @@ use crate::{
|
||||||
verify_person_in_community,
|
verify_person_in_community,
|
||||||
},
|
},
|
||||||
fetcher::object_id::ObjectId,
|
fetcher::object_id::ObjectId,
|
||||||
objects::{
|
objects::{community::ApubCommunity, person::ApubPerson},
|
||||||
community::{ApubCommunity, Group},
|
protocol::objects::group::Group,
|
||||||
person::ApubPerson,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
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
|
/// This activity is received from a remote community mod, and updates the description or other
|
||||||
/// fields of a local community.
|
/// 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::{
|
use crate::{
|
||||||
activities::{
|
activities::{
|
||||||
check_community_deleted_or_removed,
|
check_community_deleted_or_removed,
|
||||||
|
@ -13,25 +28,9 @@ use crate::{
|
||||||
CreateOrUpdateType,
|
CreateOrUpdateType,
|
||||||
},
|
},
|
||||||
fetcher::object_id::ObjectId,
|
fetcher::object_id::ObjectId,
|
||||||
objects::{
|
objects::{community::ApubCommunity, person::ApubPerson, post::ApubPost},
|
||||||
community::ApubCommunity,
|
protocol::objects::page::Page,
|
||||||
person::ApubPerson,
|
|
||||||
post::{ApubPost, 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)]
|
#[derive(Clone, Debug, Deserialize, Serialize, ActivityFields)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
|
|
|
@ -7,10 +7,8 @@ use crate::{
|
||||||
CreateOrUpdateType,
|
CreateOrUpdateType,
|
||||||
},
|
},
|
||||||
fetcher::object_id::ObjectId,
|
fetcher::object_id::ObjectId,
|
||||||
objects::{
|
objects::{person::ApubPerson, private_message::ApubPrivateMessage},
|
||||||
person::ApubPerson,
|
protocol::objects::chat_message::ChatMessage,
|
||||||
private_message::{ApubPrivateMessage, ChatMessage},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use activitystreams::unparsed::Unparsed;
|
use activitystreams::unparsed::Unparsed;
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
||||||
fetcher::object_id::ObjectId,
|
fetcher::object_id::ObjectId,
|
||||||
generate_moderators_url,
|
generate_moderators_url,
|
||||||
objects::person::ApubPerson,
|
objects::person::ApubPerson,
|
||||||
|
protocol::collections::group_moderators::GroupModerators,
|
||||||
};
|
};
|
||||||
use activitystreams::{chrono::NaiveDateTime, collection::kind::OrderedCollectionType};
|
use activitystreams::{chrono::NaiveDateTime, collection::kind::OrderedCollectionType};
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
|
@ -13,17 +14,8 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
|
use lemmy_db_views_actor::community_moderator_view::CommunityModeratorView;
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use url::Url;
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct ApubCommunityModerators(pub(crate) Vec<CommunityModeratorView>);
|
pub(crate) struct ApubCommunityModerators(pub(crate) Vec<CommunityModeratorView>);
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ use crate::{
|
||||||
collections::CommunityContext,
|
collections::CommunityContext,
|
||||||
generate_outbox_url,
|
generate_outbox_url,
|
||||||
objects::{person::ApubPerson, post::ApubPost},
|
objects::{person::ApubPerson, post::ApubPost},
|
||||||
|
protocol::collections::group_outbox::GroupOutbox,
|
||||||
};
|
};
|
||||||
use activitystreams::collection::kind::OrderedCollectionType;
|
use activitystreams::collection::kind::OrderedCollectionType;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
|
@ -17,18 +18,8 @@ use lemmy_db_schema::{
|
||||||
traits::Crud,
|
traits::Crud,
|
||||||
};
|
};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use url::Url;
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct ApubCommunityOutbox(Vec<ApubPost>);
|
pub(crate) struct ApubCommunityOutbox(Vec<ApubPost>);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use crate::objects::community::ApubCommunity;
|
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
|
|
||||||
pub(crate) mod community_followers;
|
use crate::objects::community::ApubCommunity;
|
||||||
|
|
||||||
pub(crate) mod community_moderators;
|
pub(crate) mod community_moderators;
|
||||||
pub(crate) mod community_outbox;
|
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.
|
/// Put community in the data, so we dont have to read it again from the database.
|
||||||
pub(crate) struct CommunityContext(pub ApubCommunity, pub LemmyContext);
|
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 chrono::NaiveDateTime;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use lemmy_apub_lib::traits::ApubObject;
|
use lemmy_apub_lib::traits::ApubObject;
|
||||||
use lemmy_db_schema::source::{comment::CommentForm, post::PostForm};
|
use lemmy_db_schema::source::{comment::CommentForm, post::PostForm};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
use lemmy_websocket::LemmyContext;
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PostOrComment {
|
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 anyhow::anyhow;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::NaiveDateTime;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_apub_lib::{
|
use lemmy_apub_lib::{
|
||||||
traits::ApubObject,
|
traits::ApubObject,
|
||||||
|
@ -21,8 +15,17 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_utils::LemmyError;
|
use lemmy_utils::LemmyError;
|
||||||
use lemmy_websocket::LemmyContext;
|
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.
|
/// 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::{
|
use crate::{
|
||||||
activities::{
|
activities::{
|
||||||
community::announce::{AnnouncableActivities, AnnounceActivity, GetCommunity},
|
community::announce::{AnnouncableActivities, AnnounceActivity, GetCommunity},
|
||||||
|
@ -6,7 +19,6 @@ use crate::{
|
||||||
verify_person_in_community,
|
verify_person_in_community,
|
||||||
},
|
},
|
||||||
collections::{
|
collections::{
|
||||||
community_followers::CommunityFollowers,
|
|
||||||
community_moderators::ApubCommunityModerators,
|
community_moderators::ApubCommunityModerators,
|
||||||
community_outbox::ApubCommunityOutbox,
|
community_outbox::ApubCommunityOutbox,
|
||||||
CommunityContext,
|
CommunityContext,
|
||||||
|
@ -21,18 +33,8 @@ use crate::{
|
||||||
receive_activity,
|
receive_activity,
|
||||||
},
|
},
|
||||||
objects::community::ApubCommunity,
|
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)]
|
#[derive(Deserialize)]
|
||||||
pub(crate) struct CommunityQuery {
|
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::{
|
use crate::{
|
||||||
activities::{
|
activities::{
|
||||||
community::announce::{AnnouncableActivities, AnnounceActivity},
|
community::announce::{AnnouncableActivities, AnnounceActivity},
|
||||||
|
@ -8,7 +18,6 @@ use crate::{
|
||||||
undo_delete::UndoDeletePrivateMessage,
|
undo_delete::UndoDeletePrivateMessage,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
collections::user_outbox::UserOutbox,
|
|
||||||
context::WithContext,
|
context::WithContext,
|
||||||
http::{
|
http::{
|
||||||
create_apub_response,
|
create_apub_response,
|
||||||
|
@ -17,15 +26,8 @@ use crate::{
|
||||||
receive_activity,
|
receive_activity,
|
||||||
},
|
},
|
||||||
objects::person::ApubPerson,
|
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)]
|
#[derive(Deserialize)]
|
||||||
pub struct PersonQuery {
|
pub struct PersonQuery {
|
||||||
|
|
|
@ -5,6 +5,7 @@ pub mod fetcher;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod migrations;
|
pub mod migrations;
|
||||||
pub mod objects;
|
pub mod objects;
|
||||||
|
pub(crate) mod protocol;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
|
@ -1,27 +1,17 @@
|
||||||
use crate::{
|
use std::ops::Deref;
|
||||||
activities::{verify_is_public, verify_person_in_community},
|
|
||||||
fetcher::object_id::ObjectId,
|
use activitystreams::{object::kind::NoteType, public};
|
||||||
objects::{
|
|
||||||
community::ApubCommunity,
|
|
||||||
person::ApubPerson,
|
|
||||||
post::ApubPost,
|
|
||||||
tombstone::Tombstone,
|
|
||||||
Source,
|
|
||||||
},
|
|
||||||
PostOrComment,
|
|
||||||
};
|
|
||||||
use activitystreams::{object::kind::NoteType, public, unparsed::Unparsed};
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
use chrono::NaiveDateTime;
|
||||||
use html2md::parse_html;
|
use html2md::parse_html;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_apub_lib::{
|
use lemmy_apub_lib::{
|
||||||
traits::ApubObject,
|
traits::ApubObject,
|
||||||
values::{MediaTypeHtml, MediaTypeMarkdown},
|
values::{MediaTypeHtml, MediaTypeMarkdown},
|
||||||
verify::verify_domains_match,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
newtypes::CommentId,
|
|
||||||
source::{
|
source::{
|
||||||
comment::{Comment, CommentForm},
|
comment::{Comment, CommentForm},
|
||||||
community::Community,
|
community::Community,
|
||||||
|
@ -35,100 +25,19 @@ use lemmy_utils::{
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_with::skip_serializing_none;
|
|
||||||
use std::ops::Deref;
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
#[skip_serializing_none]
|
use crate::{
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
activities::verify_person_in_community,
|
||||||
#[serde(rename_all = "camelCase")]
|
fetcher::object_id::ObjectId,
|
||||||
pub struct Note {
|
protocol::{
|
||||||
r#type: NoteType,
|
objects::{
|
||||||
id: Url,
|
note::{Note, SourceCompat},
|
||||||
pub(crate) attributed_to: ObjectId<ApubPerson>,
|
tombstone::Tombstone,
|
||||||
/// 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
|
Source,
|
||||||
/// the post in [`in_reply_to`]).
|
},
|
||||||
to: Vec<Url>,
|
PostOrComment,
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ApubComment(Comment);
|
pub struct ApubComment(Comment);
|
||||||
|
@ -277,13 +186,16 @@ impl ApubObject for ApubComment {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use super::*;
|
use assert_json_diff::assert_json_include;
|
||||||
|
use serial_test::serial;
|
||||||
|
|
||||||
use crate::objects::{
|
use crate::objects::{
|
||||||
community::ApubCommunity,
|
community::ApubCommunity,
|
||||||
tests::{file_to_json_object, init_context},
|
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(
|
pub(crate) async fn prepare_comment_test(
|
||||||
url: &Url,
|
url: &Url,
|
||||||
|
|
|
@ -1,117 +1,40 @@
|
||||||
use crate::{
|
use std::ops::Deref;
|
||||||
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 activitystreams::{
|
use activitystreams::{
|
||||||
actor::{kind::GroupType, Endpoints},
|
actor::{kind::GroupType, Endpoints},
|
||||||
object::kind::ImageType,
|
object::kind::ImageType,
|
||||||
unparsed::Unparsed,
|
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
use chrono::NaiveDateTime;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use log::debug;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_apub_lib::{
|
use lemmy_apub_lib::{
|
||||||
signatures::PublicKey,
|
|
||||||
traits::{ActorType, ApubObject},
|
traits::{ActorType, ApubObject},
|
||||||
values::MediaTypeMarkdown,
|
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_db_views_actor::community_follower_view::CommunityFollowerView;
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
settings::structs::Settings,
|
settings::structs::Settings,
|
||||||
utils::{check_slurs, check_slurs_opt, convert_datetime, markdown_to_html},
|
utils::{convert_datetime, markdown_to_html},
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
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]
|
use crate::{
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
check_is_apub_id_valid,
|
||||||
#[serde(rename_all = "camelCase")]
|
collections::{community_moderators::ApubCommunityModerators, CommunityContext},
|
||||||
pub struct Group {
|
fetcher::object_id::ObjectId,
|
||||||
#[serde(rename = "type")]
|
generate_moderators_url,
|
||||||
kind: GroupType,
|
generate_outbox_url,
|
||||||
pub(crate) id: Url,
|
protocol::{
|
||||||
/// username, set at account creation and can never be changed
|
objects::{group::Group, tombstone::Tombstone},
|
||||||
preferred_username: String,
|
ImageObject,
|
||||||
/// title (can be changed at any time)
|
Source,
|
||||||
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),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ApubCommunity(Community);
|
pub struct ApubCommunity(Community);
|
||||||
|
@ -300,12 +223,15 @@ impl ApubCommunity {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
|
||||||
use crate::objects::tests::{file_to_json_object, init_context};
|
|
||||||
use assert_json_diff::assert_json_include;
|
use assert_json_diff::assert_json_include;
|
||||||
use lemmy_db_schema::traits::Crud;
|
|
||||||
use serial_test::serial;
|
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]
|
#[actix_rt::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn test_parse_lemmy_community() {
|
async fn test_parse_lemmy_community() {
|
||||||
|
|
|
@ -1,32 +1,13 @@
|
||||||
use activitystreams::object::kind::ImageType;
|
use crate::protocol::Source;
|
||||||
use html2md::parse_html;
|
use html2md::parse_html;
|
||||||
use lemmy_apub_lib::values::MediaTypeMarkdown;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
pub mod comment;
|
pub mod comment;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod post;
|
pub mod post;
|
||||||
pub mod private_message;
|
pub mod private_message;
|
||||||
pub mod tombstone;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
pub(crate) fn get_summary_from_string_or_source(
|
||||||
#[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(
|
|
||||||
raw: &Option<String>,
|
raw: &Option<String>,
|
||||||
source: &Option<Source>,
|
source: &Option<Source>,
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
check_is_apub_id_valid,
|
check_is_apub_id_valid,
|
||||||
generate_outbox_url,
|
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 activitystreams::{actor::Endpoints, object::kind::ImageType, unparsed::Unparsed};
|
||||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
activities::{verify_is_public, verify_person_in_community},
|
activities::verify_person_in_community,
|
||||||
fetcher::object_id::ObjectId,
|
fetcher::object_id::ObjectId,
|
||||||
objects::{
|
protocol::{
|
||||||
community::ApubCommunity,
|
objects::{page::Page, tombstone::Tombstone},
|
||||||
person::ApubPerson,
|
|
||||||
tombstone::Tombstone,
|
|
||||||
ImageObject,
|
ImageObject,
|
||||||
Source,
|
Source,
|
||||||
},
|
},
|
||||||
|
@ -12,15 +10,12 @@ use crate::{
|
||||||
use activitystreams::{
|
use activitystreams::{
|
||||||
object::kind::{ImageType, PageType},
|
object::kind::{ImageType, PageType},
|
||||||
public,
|
public,
|
||||||
unparsed::Unparsed,
|
|
||||||
};
|
};
|
||||||
use anyhow::anyhow;
|
use chrono::NaiveDateTime;
|
||||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_apub_lib::{
|
use lemmy_apub_lib::{
|
||||||
traits::ApubObject,
|
traits::ApubObject,
|
||||||
values::{MediaTypeHtml, MediaTypeMarkdown},
|
values::{MediaTypeHtml, MediaTypeMarkdown},
|
||||||
verify::verify_domains_match,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
self,
|
self,
|
||||||
|
@ -33,97 +28,13 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_utils::{
|
use lemmy_utils::{
|
||||||
request::fetch_site_data,
|
request::fetch_site_data,
|
||||||
utils::{check_slurs, convert_datetime, markdown_to_html, remove_slurs},
|
utils::{convert_datetime, markdown_to_html, remove_slurs},
|
||||||
LemmyError,
|
LemmyError,
|
||||||
};
|
};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_with::skip_serializing_none;
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use url::Url;
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ApubPost(Post);
|
pub struct ApubPost(Post);
|
||||||
|
|
||||||
|
@ -283,6 +194,8 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::objects::{
|
use crate::objects::{
|
||||||
community::ApubCommunity,
|
community::ApubCommunity,
|
||||||
|
person::ApubPerson,
|
||||||
|
post::ApubPost,
|
||||||
tests::{file_to_json_object, init_context},
|
tests::{file_to_json_object, init_context},
|
||||||
};
|
};
|
||||||
use assert_json_diff::assert_json_include;
|
use assert_json_diff::assert_json_include;
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
fetcher::object_id::ObjectId,
|
fetcher::object_id::ObjectId,
|
||||||
objects::{person::ApubPerson, Source},
|
protocol::{
|
||||||
|
objects::chat_message::{ChatMessage, ChatMessageType},
|
||||||
|
Source,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use activitystreams::unparsed::Unparsed;
|
use chrono::NaiveDateTime;
|
||||||
use anyhow::anyhow;
|
|
||||||
use chrono::{DateTime, FixedOffset, NaiveDateTime};
|
|
||||||
use html2md::parse_html;
|
use html2md::parse_html;
|
||||||
use lemmy_api_common::blocking;
|
use lemmy_api_common::blocking;
|
||||||
use lemmy_apub_lib::{
|
use lemmy_apub_lib::{
|
||||||
traits::ApubObject,
|
traits::ApubObject,
|
||||||
values::{MediaTypeHtml, MediaTypeMarkdown},
|
values::{MediaTypeHtml, MediaTypeMarkdown},
|
||||||
verify::verify_domains_match,
|
|
||||||
};
|
};
|
||||||
use lemmy_db_schema::{
|
use lemmy_db_schema::{
|
||||||
source::{
|
source::{
|
||||||
|
@ -21,60 +21,9 @@ use lemmy_db_schema::{
|
||||||
};
|
};
|
||||||
use lemmy_utils::{utils::convert_datetime, LemmyError};
|
use lemmy_utils::{utils::convert_datetime, LemmyError};
|
||||||
use lemmy_websocket::LemmyContext;
|
use lemmy_websocket::LemmyContext;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_with::skip_serializing_none;
|
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use url::Url;
|
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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ApubPrivateMessage(PrivateMessage);
|
pub struct ApubPrivateMessage(PrivateMessage);
|
||||||
|
|
||||||
|
@ -189,7 +138,10 @@ impl ApubObject for ApubPrivateMessage {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
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 assert_json_diff::assert_json_include;
|
||||||
use serial_test::serial;
|
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