mirror of https://github.com/LemmyNet/lemmy
Move object and collection structs to protocol folder
parent
358ef99ea2
commit
5ff044346f
@ -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);
|
||||||
|
@ -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>>,
|
||||||
|
}
|
@ -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>,
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
pub(crate) mod group_followers;
|
||||||
|
pub(crate) mod group_moderators;
|
||||||
|
pub(crate) mod group_outbox;
|
||||||
|
pub(crate) mod person_outbox;
|
@ -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,
|
||||||
|
}
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
@ -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),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
@ -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 New Issue