]> Untitled Git - lemmy.git/blob - crates/apub/src/objects/private_message.rs
Move object and collection structs to protocol folder
[lemmy.git] / crates / apub / src / objects / private_message.rs
1 use crate::{
2   fetcher::object_id::ObjectId,
3   protocol::{
4     objects::chat_message::{ChatMessage, ChatMessageType},
5     Source,
6   },
7 };
8 use chrono::NaiveDateTime;
9 use html2md::parse_html;
10 use lemmy_api_common::blocking;
11 use lemmy_apub_lib::{
12   traits::ApubObject,
13   values::{MediaTypeHtml, MediaTypeMarkdown},
14 };
15 use lemmy_db_schema::{
16   source::{
17     person::Person,
18     private_message::{PrivateMessage, PrivateMessageForm},
19   },
20   traits::Crud,
21 };
22 use lemmy_utils::{utils::convert_datetime, LemmyError};
23 use lemmy_websocket::LemmyContext;
24 use std::ops::Deref;
25 use url::Url;
26
27 #[derive(Clone, Debug)]
28 pub struct ApubPrivateMessage(PrivateMessage);
29
30 impl Deref for ApubPrivateMessage {
31   type Target = PrivateMessage;
32   fn deref(&self) -> &Self::Target {
33     &self.0
34   }
35 }
36
37 impl From<PrivateMessage> for ApubPrivateMessage {
38   fn from(pm: PrivateMessage) -> Self {
39     ApubPrivateMessage { 0: pm }
40   }
41 }
42
43 #[async_trait::async_trait(?Send)]
44 impl ApubObject for ApubPrivateMessage {
45   type DataType = LemmyContext;
46   type ApubType = ChatMessage;
47   type TombstoneType = ();
48
49   fn last_refreshed_at(&self) -> Option<NaiveDateTime> {
50     None
51   }
52
53   async fn read_from_apub_id(
54     object_id: Url,
55     context: &LemmyContext,
56   ) -> Result<Option<Self>, LemmyError> {
57     Ok(
58       blocking(context.pool(), move |conn| {
59         PrivateMessage::read_from_apub_id(conn, object_id)
60       })
61       .await??
62       .map(Into::into),
63     )
64   }
65
66   async fn delete(self, _context: &LemmyContext) -> Result<(), LemmyError> {
67     // do nothing, because pm can't be fetched over http
68     unimplemented!()
69   }
70
71   async fn to_apub(&self, context: &LemmyContext) -> Result<ChatMessage, LemmyError> {
72     let creator_id = self.creator_id;
73     let creator = blocking(context.pool(), move |conn| Person::read(conn, creator_id)).await??;
74
75     let recipient_id = self.recipient_id;
76     let recipient =
77       blocking(context.pool(), move |conn| Person::read(conn, recipient_id)).await??;
78
79     let note = ChatMessage {
80       r#type: ChatMessageType::ChatMessage,
81       id: self.ap_id.clone().into(),
82       attributed_to: ObjectId::new(creator.actor_id),
83       to: [ObjectId::new(recipient.actor_id)],
84       content: self.content.clone(),
85       media_type: Some(MediaTypeHtml::Html),
86       source: Some(Source {
87         content: self.content.clone(),
88         media_type: MediaTypeMarkdown::Markdown,
89       }),
90       published: Some(convert_datetime(self.published)),
91       updated: self.updated.map(convert_datetime),
92       unparsed: Default::default(),
93     };
94     Ok(note)
95   }
96
97   fn to_tombstone(&self) -> Result<(), LemmyError> {
98     unimplemented!()
99   }
100
101   async fn from_apub(
102     note: &ChatMessage,
103     context: &LemmyContext,
104     expected_domain: &Url,
105     request_counter: &mut i32,
106   ) -> Result<ApubPrivateMessage, LemmyError> {
107     let ap_id = Some(note.id(expected_domain)?.clone().into());
108     let creator = note
109       .attributed_to
110       .dereference(context, request_counter)
111       .await?;
112     let recipient = note.to[0].dereference(context, request_counter).await?;
113     let content = if let Some(source) = &note.source {
114       source.content.clone()
115     } else {
116       parse_html(&note.content)
117     };
118
119     let form = PrivateMessageForm {
120       creator_id: creator.id,
121       recipient_id: recipient.id,
122       content,
123       published: note.published.map(|u| u.to_owned().naive_local()),
124       updated: note.updated.map(|u| u.to_owned().naive_local()),
125       deleted: None,
126       read: None,
127       ap_id,
128       local: Some(false),
129     };
130     let pm = blocking(context.pool(), move |conn| {
131       PrivateMessage::upsert(conn, &form)
132     })
133     .await??;
134     Ok(pm.into())
135   }
136 }
137
138 #[cfg(test)]
139 mod tests {
140   use super::*;
141   use crate::objects::{
142     person::ApubPerson,
143     tests::{file_to_json_object, init_context},
144   };
145   use assert_json_diff::assert_json_include;
146   use serial_test::serial;
147
148   async fn prepare_comment_test(url: &Url, context: &LemmyContext) -> (ApubPerson, ApubPerson) {
149     let lemmy_person = file_to_json_object("assets/lemmy-person.json");
150     let person1 = ApubPerson::from_apub(&lemmy_person, context, url, &mut 0)
151       .await
152       .unwrap();
153     let pleroma_person = file_to_json_object("assets/pleroma-person.json");
154     let pleroma_url = Url::parse("https://queer.hacktivis.me/users/lanodan").unwrap();
155     let person2 = ApubPerson::from_apub(&pleroma_person, context, &pleroma_url, &mut 0)
156       .await
157       .unwrap();
158     (person1, person2)
159   }
160
161   fn cleanup(data: (ApubPerson, ApubPerson), context: &LemmyContext) {
162     Person::delete(&*context.pool().get().unwrap(), data.0.id).unwrap();
163     Person::delete(&*context.pool().get().unwrap(), data.1.id).unwrap();
164   }
165
166   #[actix_rt::test]
167   #[serial]
168   async fn test_parse_lemmy_pm() {
169     let context = init_context();
170     let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621").unwrap();
171     let data = prepare_comment_test(&url, &context).await;
172     let json = file_to_json_object("assets/lemmy-private-message.json");
173     let mut request_counter = 0;
174     let pm = ApubPrivateMessage::from_apub(&json, &context, &url, &mut request_counter)
175       .await
176       .unwrap();
177
178     assert_eq!(pm.ap_id.clone().into_inner(), url);
179     assert_eq!(pm.content.len(), 20);
180     assert_eq!(request_counter, 0);
181
182     let to_apub = pm.to_apub(&context).await.unwrap();
183     assert_json_include!(actual: json, expected: to_apub);
184
185     PrivateMessage::delete(&*context.pool().get().unwrap(), pm.id).unwrap();
186     cleanup(data, &context);
187   }
188
189   #[actix_rt::test]
190   #[serial]
191   async fn test_parse_pleroma_pm() {
192     let context = init_context();
193     let url = Url::parse("https://enterprise.lemmy.ml/private_message/1621").unwrap();
194     let data = prepare_comment_test(&url, &context).await;
195     let pleroma_url = Url::parse("https://queer.hacktivis.me/objects/2").unwrap();
196     let json = file_to_json_object("assets/pleroma-private-message.json");
197     let mut request_counter = 0;
198     let pm = ApubPrivateMessage::from_apub(&json, &context, &pleroma_url, &mut request_counter)
199       .await
200       .unwrap();
201
202     assert_eq!(pm.ap_id.clone().into_inner(), pleroma_url);
203     assert_eq!(pm.content.len(), 3);
204     assert_eq!(request_counter, 0);
205
206     PrivateMessage::delete(&*context.pool().get().unwrap(), pm.id).unwrap();
207     cleanup(data, &context);
208   }
209 }