]> Untitled Git - lemmy.git/blob - crates/api/src/lib.rs
Split api crate into api_structs and api
[lemmy.git] / crates / api / src / lib.rs
1 use actix_web::{web, web::Data};
2 use lemmy_api_common::{comment::*, community::*, person::*, post::*, site::*, websocket::*};
3 use lemmy_utils::{ConnectionId, LemmyError};
4 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
5 use serde::Deserialize;
6 use std::{env, process::Command};
7
8 mod comment;
9 mod comment_report;
10 mod community;
11 mod local_user;
12 mod post;
13 mod post_report;
14 mod private_message;
15 pub mod routes;
16 mod site;
17 mod websocket;
18
19 #[async_trait::async_trait(?Send)]
20 pub trait Perform {
21   type Response: serde::ser::Serialize + Send;
22
23   async fn perform(
24     &self,
25     context: &Data<LemmyContext>,
26     websocket_id: Option<ConnectionId>,
27   ) -> Result<Self::Response, LemmyError>;
28 }
29
30 pub async fn match_websocket_operation(
31   context: LemmyContext,
32   id: ConnectionId,
33   op: UserOperation,
34   data: &str,
35 ) -> Result<String, LemmyError> {
36   //TODO: handle commented out actions in crud crate
37
38   match op {
39     // User ops
40     UserOperation::Login => {
41       //do_websocket_operation::<Login>(context, id, op, data).await
42       todo!()
43     }
44     UserOperation::Register => {
45       //do_websocket_operation::<Register>(context, id, op, data).await
46       todo!()
47     }
48     UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
49     UserOperation::GetPersonDetails => {
50       //do_websocket_operation::<GetPersonDetails>(context, id, op, data).await
51       todo!()
52     }
53     UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
54     UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
55     UserOperation::BanPerson => do_websocket_operation::<BanPerson>(context, id, op, data).await,
56     UserOperation::GetPersonMentions => {
57       //do_websocket_operation::<GetPersonMentions>(context, id, op, data).await
58       todo!()
59     }
60     UserOperation::MarkPersonMentionAsRead => {
61       do_websocket_operation::<MarkPersonMentionAsRead>(context, id, op, data).await
62     }
63     UserOperation::MarkAllAsRead => {
64       do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
65     }
66     UserOperation::DeleteAccount => {
67       //do_websocket_operation::<DeleteAccount>(context, id, op, data).await
68       todo!()
69     }
70     UserOperation::PasswordReset => {
71       do_websocket_operation::<PasswordReset>(context, id, op, data).await
72     }
73     UserOperation::PasswordChange => {
74       do_websocket_operation::<PasswordChange>(context, id, op, data).await
75     }
76     UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
77     UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
78     UserOperation::CommunityJoin => {
79       do_websocket_operation::<CommunityJoin>(context, id, op, data).await
80     }
81     UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
82     UserOperation::SaveUserSettings => {
83       do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
84     }
85     UserOperation::GetReportCount => {
86       do_websocket_operation::<GetReportCount>(context, id, op, data).await
87     }
88
89     // Private Message ops
90     UserOperation::CreatePrivateMessage => {
91       //do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
92       todo!()
93     }
94     UserOperation::EditPrivateMessage => {
95       //do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
96       todo!()
97     }
98     UserOperation::DeletePrivateMessage => {
99       //do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
100       todo!()
101     }
102     UserOperation::MarkPrivateMessageAsRead => {
103       do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
104     }
105     UserOperation::GetPrivateMessages => {
106       //do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
107       todo!()
108     }
109
110     // Site ops
111     UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
112     UserOperation::CreateSite => {
113       //do_websocket_operation::<CreateSite>(context, id, op, data).await
114       todo!()
115     }
116     UserOperation::EditSite => {
117       //do_websocket_operation::<EditSite>(context, id, op, data).await
118       todo!()
119     }
120     UserOperation::GetSite => {
121       //do_websocket_operation::<GetSite>(context, id, op, data).await
122       todo!()
123     }
124     UserOperation::GetSiteConfig => {
125       do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
126     }
127     UserOperation::SaveSiteConfig => {
128       do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
129     }
130     UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
131     UserOperation::TransferCommunity => {
132       do_websocket_operation::<TransferCommunity>(context, id, op, data).await
133     }
134     UserOperation::TransferSite => {
135       do_websocket_operation::<TransferSite>(context, id, op, data).await
136     }
137
138     // Community ops
139     UserOperation::GetCommunity => {
140       //do_websocket_operation::<GetCommunity>(context, id, op, data).await
141       todo!()
142     }
143     UserOperation::ListCommunities => {
144       //do_websocket_operation::<ListCommunities>(context, id, op, data).await
145       todo!()
146     }
147     UserOperation::CreateCommunity => {
148       //do_websocket_operation::<CreateCommunity>(context, id, op, data).await
149       todo!()
150     }
151     UserOperation::EditCommunity => {
152       //do_websocket_operation::<EditCommunity>(context, id, op, data).await
153       todo!()
154     }
155     UserOperation::DeleteCommunity => {
156       //do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
157       todo!()
158     }
159     UserOperation::RemoveCommunity => {
160       //do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
161       todo!()
162     }
163     UserOperation::FollowCommunity => {
164       do_websocket_operation::<FollowCommunity>(context, id, op, data).await
165     }
166     UserOperation::GetFollowedCommunities => {
167       do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
168     }
169     UserOperation::BanFromCommunity => {
170       do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
171     }
172     UserOperation::AddModToCommunity => {
173       do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
174     }
175
176     // Post ops
177     UserOperation::CreatePost => {
178       //do_websocket_operation::<CreatePost>(context, id, op, data).await
179       todo!()
180     }
181     UserOperation::GetPost => {
182       //do_websocket_operation::<GetPost>(context, id, op, data).await
183       todo!()
184     }
185     UserOperation::GetPosts => {
186       //do_websocket_operation::<GetPosts>(context, id, op, data).await
187       todo!()
188     }
189     UserOperation::EditPost => {
190       //do_websocket_operation::<EditPost>(context, id, op, data).await
191       todo!()
192     }
193     UserOperation::DeletePost => {
194       //do_websocket_operation::<DeletePost>(context, id, op, data).await
195       todo!()
196     }
197     UserOperation::RemovePost => {
198       //do_websocket_operation::<RemovePost>(context, id, op, data).await
199       todo!()
200     }
201     UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
202     UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
203     UserOperation::CreatePostLike => {
204       do_websocket_operation::<CreatePostLike>(context, id, op, data).await
205     }
206     UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
207     UserOperation::CreatePostReport => {
208       do_websocket_operation::<CreatePostReport>(context, id, op, data).await
209     }
210     UserOperation::ListPostReports => {
211       do_websocket_operation::<ListPostReports>(context, id, op, data).await
212     }
213     UserOperation::ResolvePostReport => {
214       do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
215     }
216
217     // Comment ops
218     UserOperation::CreateComment => {
219       //do_websocket_operation::<CreateComment>(context, id, op, data).await
220       todo!()
221     }
222     UserOperation::EditComment => {
223       //do_websocket_operation::<EditComment>(context, id, op, data).await
224       todo!()
225     }
226     UserOperation::DeleteComment => {
227       //do_websocket_operation::<DeleteComment>(context, id, op, data).await
228       todo!()
229     }
230     UserOperation::RemoveComment => {
231       //do_websocket_operation::<RemoveComment>(context, id, op, data).await
232       todo!()
233     }
234     UserOperation::MarkCommentAsRead => {
235       do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
236     }
237     UserOperation::SaveComment => {
238       do_websocket_operation::<SaveComment>(context, id, op, data).await
239     }
240     UserOperation::GetComments => {
241       //do_websocket_operation::<GetComments>(context, id, op, data).await
242       todo!()
243     }
244     UserOperation::CreateCommentLike => {
245       do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
246     }
247     UserOperation::CreateCommentReport => {
248       do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
249     }
250     UserOperation::ListCommentReports => {
251       do_websocket_operation::<ListCommentReports>(context, id, op, data).await
252     }
253     UserOperation::ResolveCommentReport => {
254       do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
255     }
256   }
257 }
258
259 async fn do_websocket_operation<'a, 'b, Data>(
260   context: LemmyContext,
261   id: ConnectionId,
262   op: UserOperation,
263   data: &str,
264 ) -> Result<String, LemmyError>
265 where
266   for<'de> Data: Deserialize<'de> + 'a,
267   Data: Perform,
268 {
269   let parsed_data: Data = serde_json::from_str(&data)?;
270   let res = parsed_data
271     .perform(&web::Data::new(context), Some(id))
272     .await?;
273   serialize_websocket_message(&op, &res)
274 }
275
276 pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
277   let mut built_text = String::new();
278
279   // Building proper speech text for espeak
280   for mut c in captcha.chars() {
281     let new_str = if c.is_alphabetic() {
282       if c.is_lowercase() {
283         c.make_ascii_uppercase();
284         format!("lower case {} ... ", c)
285       } else {
286         c.make_ascii_uppercase();
287         format!("capital {} ... ", c)
288       }
289     } else {
290       format!("{} ...", c)
291     };
292
293     built_text.push_str(&new_str);
294   }
295
296   espeak_wav_base64(&built_text)
297 }
298
299 pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
300   // Make a temp file path
301   let uuid = uuid::Uuid::new_v4().to_string();
302   let file_path = format!(
303     "{}/lemmy_espeak_{}.wav",
304     env::temp_dir().to_string_lossy(),
305     &uuid
306   );
307
308   // Write the wav file
309   Command::new("espeak")
310     .arg("-w")
311     .arg(&file_path)
312     .arg(text)
313     .status()?;
314
315   // Read the wav file bytes
316   let bytes = std::fs::read(&file_path)?;
317
318   // Delete the file
319   std::fs::remove_file(file_path)?;
320
321   // Convert to base64
322   let base64 = base64::encode(bytes);
323
324   Ok(base64)
325 }
326
327 #[cfg(test)]
328 mod tests {
329   use crate::{captcha_espeak_wav_base64, check_validator_time};
330   use lemmy_api_common::check_validator_time;
331   use lemmy_db_queries::{establish_unpooled_connection, source::local_user::LocalUser_, Crud};
332   use lemmy_db_schema::source::{
333     local_user::{LocalUser, LocalUserForm},
334     person::{Person, PersonForm},
335   };
336   use lemmy_utils::claims::Claims;
337
338   #[test]
339   fn test_should_not_validate_user_token_after_password_change() {
340     let conn = establish_unpooled_connection();
341
342     let new_person = PersonForm {
343       name: "Gerry9812".into(),
344       preferred_username: None,
345       avatar: None,
346       banner: None,
347       banned: None,
348       deleted: None,
349       published: None,
350       updated: None,
351       actor_id: None,
352       bio: None,
353       local: None,
354       private_key: None,
355       public_key: None,
356       last_refreshed_at: None,
357       inbox_url: None,
358       shared_inbox_url: None,
359     };
360
361     let inserted_person = Person::create(&conn, &new_person).unwrap();
362
363     let local_user_form = LocalUserForm {
364       person_id: inserted_person.id,
365       email: None,
366       matrix_user_id: None,
367       password_encrypted: "123456".to_string(),
368       admin: None,
369       show_nsfw: None,
370       theme: None,
371       default_sort_type: None,
372       default_listing_type: None,
373       lang: None,
374       show_avatars: None,
375       send_notifications_to_email: None,
376     };
377
378     let inserted_local_user = LocalUser::create(&conn, &local_user_form).unwrap();
379
380     let jwt = Claims::jwt(inserted_local_user.id.0).unwrap();
381     let claims = Claims::decode(&jwt).unwrap().claims;
382     let check = check_validator_time(&inserted_local_user.validator_time, &claims);
383     assert!(check.is_ok());
384
385     // The check should fail, since the validator time is now newer than the jwt issue time
386     let updated_local_user =
387       LocalUser::update_password(&conn, inserted_local_user.id, &"password111").unwrap();
388     let check_after = check_validator_time(&updated_local_user.validator_time, &claims);
389     assert!(check_after.is_err());
390
391     let num_deleted = Person::delete(&conn, inserted_person.id).unwrap();
392     assert_eq!(1, num_deleted);
393   }
394
395   #[test]
396   fn test_espeak() {
397     assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())
398   }
399 }