]> Untitled Git - lemmy.git/blob - crates/apub/src/objects/private_message.rs
Merge pull request #1850 from LemmyNet/refactor-apub
[lemmy.git] / crates / apub / src / objects / private_message.rs
1 use crate::{
2   context::lemmy_context,
3   fetcher::object_id::ObjectId,
4   objects::{create_tombstone, person::ApubPerson, Source},
5 };
6 use activitystreams::{
7   base::AnyBase,
8   chrono::NaiveDateTime,
9   object::{kind::NoteType, Tombstone},
10   primitives::OneOrMany,
11   unparsed::Unparsed,
12 };
13 use anyhow::anyhow;
14 use chrono::{DateTime, FixedOffset};
15 use lemmy_api_common::blocking;
16 use lemmy_apub_lib::{
17   traits::{ApubObject, FromApub, ToApub},
18   values::{MediaTypeHtml, MediaTypeMarkdown},
19   verify::verify_domains_match,
20 };
21 use lemmy_db_schema::{
22   source::{
23     person::Person,
24     private_message::{PrivateMessage, PrivateMessageForm},
25   },
26   traits::Crud,
27   DbPool,
28 };
29 use lemmy_utils::{utils::convert_datetime, LemmyError};
30 use lemmy_websocket::LemmyContext;
31 use serde::{Deserialize, Serialize};
32 use serde_with::skip_serializing_none;
33 use std::ops::Deref;
34 use url::Url;
35
36 #[skip_serializing_none]
37 #[derive(Clone, Debug, Deserialize, Serialize)]
38 #[serde(rename_all = "camelCase")]
39 pub struct Note {
40   #[serde(rename = "@context")]
41   context: OneOrMany<AnyBase>,
42   r#type: NoteType,
43   id: Url,
44   pub(crate) attributed_to: ObjectId<ApubPerson>,
45   to: ObjectId<ApubPerson>,
46   content: String,
47   media_type: MediaTypeHtml,
48   source: Source,
49   published: DateTime<FixedOffset>,
50   updated: Option<DateTime<FixedOffset>>,
51   #[serde(flatten)]
52   unparsed: Unparsed,
53 }
54
55 impl Note {
56   pub(crate) fn id_unchecked(&self) -> &Url {
57     &self.id
58   }
59   pub(crate) fn id(&self, expected_domain: &Url) -> Result<&Url, LemmyError> {
60     verify_domains_match(&self.id, expected_domain)?;
61     Ok(&self.id)
62   }
63
64   pub(crate) async fn verify(
65     &self,
66     context: &LemmyContext,
67     request_counter: &mut i32,
68   ) -> Result<(), LemmyError> {
69     verify_domains_match(self.attributed_to.inner(), &self.id)?;
70     let person = self
71       .attributed_to
72       .dereference(context, request_counter)
73       .await?;
74     if person.banned {
75       return Err(anyhow!("Person is banned from site").into());
76     }
77     Ok(())
78   }
79 }
80
81 #[derive(Clone, Debug)]
82 pub struct ApubPrivateMessage(PrivateMessage);
83
84 impl Deref for ApubPrivateMessage {
85   type Target = PrivateMessage;
86   fn deref(&self) -> &Self::Target {
87     &self.0
88   }
89 }
90
91 impl From<PrivateMessage> for ApubPrivateMessage {
92   fn from(pm: PrivateMessage) -> Self {
93     ApubPrivateMessage { 0: pm }
94   }
95 }
96
97 #[async_trait::async_trait(?Send)]
98 impl ApubObject for ApubPrivateMessage {
99   type DataType = LemmyContext;
100
101   fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
102     None
103   }
104
105   async fn read_from_apub_id(
106     object_id: Url,
107     context: &LemmyContext,
108   ) -> Result<Option<Self>, LemmyError> {
109     Ok(
110       blocking(context.pool(), move |conn| {
111         PrivateMessage::read_from_apub_id(conn, object_id)
112       })
113       .await??
114       .map(Into::into),
115     )
116   }
117
118   async fn delete(self, _context: &LemmyContext) -> Result<(), LemmyError> {
119     // do nothing, because pm can't be fetched over http
120     unimplemented!()
121   }
122 }
123
124 #[async_trait::async_trait(?Send)]
125 impl ToApub for ApubPrivateMessage {
126   type ApubType = Note;
127   type TombstoneType = Tombstone;
128   type DataType = DbPool;
129
130   async fn to_apub(&self, pool: &DbPool) -> Result<Note, LemmyError> {
131     let creator_id = self.creator_id;
132     let creator = blocking(pool, move |conn| Person::read(conn, creator_id)).await??;
133
134     let recipient_id = self.recipient_id;
135     let recipient = blocking(pool, move |conn| Person::read(conn, recipient_id)).await??;
136
137     let note = Note {
138       context: lemmy_context(),
139       r#type: NoteType::Note,
140       id: self.ap_id.clone().into(),
141       attributed_to: ObjectId::new(creator.actor_id),
142       to: ObjectId::new(recipient.actor_id),
143       content: self.content.clone(),
144       media_type: MediaTypeHtml::Html,
145       source: Source {
146         content: self.content.clone(),
147         media_type: MediaTypeMarkdown::Markdown,
148       },
149       published: convert_datetime(self.published),
150       updated: self.updated.map(convert_datetime),
151       unparsed: Default::default(),
152     };
153     Ok(note)
154   }
155
156   fn to_tombstone(&self) -> Result<Tombstone, LemmyError> {
157     create_tombstone(
158       self.deleted,
159       self.ap_id.to_owned().into(),
160       self.updated,
161       NoteType::Note,
162     )
163   }
164 }
165
166 #[async_trait::async_trait(?Send)]
167 impl FromApub for ApubPrivateMessage {
168   type ApubType = Note;
169   type DataType = LemmyContext;
170
171   async fn from_apub(
172     note: &Note,
173     context: &LemmyContext,
174     expected_domain: &Url,
175     request_counter: &mut i32,
176   ) -> Result<ApubPrivateMessage, LemmyError> {
177     let ap_id = Some(note.id(expected_domain)?.clone().into());
178     let creator = note
179       .attributed_to
180       .dereference(context, request_counter)
181       .await?;
182     let recipient = note.to.dereference(context, request_counter).await?;
183
184     let form = PrivateMessageForm {
185       creator_id: creator.id,
186       recipient_id: recipient.id,
187       content: note.source.content.clone(),
188       published: Some(note.published.naive_local()),
189       updated: note.updated.map(|u| u.to_owned().naive_local()),
190       deleted: None,
191       read: None,
192       ap_id,
193       local: Some(false),
194     };
195     let pm = blocking(context.pool(), move |conn| {
196       PrivateMessage::upsert(conn, &form)
197     })
198     .await??;
199     Ok(pm.into())
200   }
201 }