]> Untitled Git - lemmy.git/blob - lemmy_api/src/lib.rs
Fixing some clippy warnings.
[lemmy.git] / lemmy_api / src / lib.rs
1 use crate::claims::Claims;
2 use actix_web::{web, web::Data};
3 use lemmy_db::{
4   source::{
5     community::{Community, CommunityModerator},
6     post::Post,
7     site::Site,
8     user::User_,
9   },
10   views::community::community_user_ban_view::CommunityUserBanView,
11   Crud,
12   DbPool,
13 };
14 use lemmy_structs::{blocking, comment::*, community::*, post::*, site::*, user::*};
15 use lemmy_utils::{settings::Settings, APIError, ConnectionId, LemmyError};
16 use lemmy_websocket::{serialize_websocket_message, LemmyContext, UserOperation};
17 use serde::Deserialize;
18 use std::process::Command;
19 use url::Url;
20
21 pub mod claims;
22 pub mod comment;
23 pub mod community;
24 pub mod post;
25 pub mod site;
26 pub mod user;
27 pub mod version;
28
29 #[async_trait::async_trait(?Send)]
30 pub trait Perform {
31   type Response: serde::ser::Serialize + Send;
32
33   async fn perform(
34     &self,
35     context: &Data<LemmyContext>,
36     websocket_id: Option<ConnectionId>,
37   ) -> Result<Self::Response, LemmyError>;
38 }
39
40 pub(crate) async fn is_mod_or_admin(
41   pool: &DbPool,
42   user_id: i32,
43   community_id: i32,
44 ) -> Result<(), LemmyError> {
45   let is_mod_or_admin = blocking(pool, move |conn| {
46     Community::is_mod_or_admin(conn, user_id, community_id)
47   })
48   .await?;
49   if !is_mod_or_admin {
50     return Err(APIError::err("not_a_mod_or_admin").into());
51   }
52   Ok(())
53 }
54 pub async fn is_admin(pool: &DbPool, user_id: i32) -> Result<(), LemmyError> {
55   let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
56   if !user.admin {
57     return Err(APIError::err("not_an_admin").into());
58   }
59   Ok(())
60 }
61
62 pub(crate) async fn get_post(post_id: i32, pool: &DbPool) -> Result<Post, LemmyError> {
63   match blocking(pool, move |conn| Post::read(conn, post_id)).await? {
64     Ok(post) => Ok(post),
65     Err(_e) => Err(APIError::err("couldnt_find_post").into()),
66   }
67 }
68
69 pub(crate) async fn get_user_from_jwt(jwt: &str, pool: &DbPool) -> Result<User_, LemmyError> {
70   let claims = match Claims::decode(&jwt) {
71     Ok(claims) => claims.claims,
72     Err(_e) => return Err(APIError::err("not_logged_in").into()),
73   };
74   let user_id = claims.id;
75   let user = blocking(pool, move |conn| User_::read(conn, user_id)).await??;
76   // Check for a site ban
77   if user.banned {
78     return Err(APIError::err("site_ban").into());
79   }
80   Ok(user)
81 }
82
83 pub(crate) async fn get_user_from_jwt_opt(
84   jwt: &Option<String>,
85   pool: &DbPool,
86 ) -> Result<Option<User_>, LemmyError> {
87   match jwt {
88     Some(jwt) => Ok(Some(get_user_from_jwt(jwt, pool).await?)),
89     None => Ok(None),
90   }
91 }
92
93 pub(crate) async fn check_community_ban(
94   user_id: i32,
95   community_id: i32,
96   pool: &DbPool,
97 ) -> Result<(), LemmyError> {
98   let is_banned = move |conn: &'_ _| CommunityUserBanView::get(conn, user_id, community_id).is_ok();
99   if blocking(pool, is_banned).await? {
100     Err(APIError::err("community_ban").into())
101   } else {
102     Ok(())
103   }
104 }
105
106 pub(crate) async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
107   if score == -1 {
108     let site = blocking(pool, move |conn| Site::read_simple(conn)).await??;
109     if !site.enable_downvotes {
110       return Err(APIError::err("downvotes_disabled").into());
111     }
112   }
113   Ok(())
114 }
115
116 /// Returns a list of communities that the user moderates
117 /// or if a community_id is supplied validates the user is a moderator
118 /// of that community and returns the community id in a vec
119 ///
120 /// * `user_id` - the user id of the moderator
121 /// * `community_id` - optional community id to check for moderator privileges
122 /// * `pool` - the diesel db pool
123 pub(crate) async fn collect_moderated_communities(
124   user_id: i32,
125   community_id: Option<i32>,
126   pool: &DbPool,
127 ) -> Result<Vec<i32>, LemmyError> {
128   if let Some(community_id) = community_id {
129     // if the user provides a community_id, just check for mod/admin privileges
130     is_mod_or_admin(pool, user_id, community_id).await?;
131     Ok(vec![community_id])
132   } else {
133     let ids = blocking(pool, move |conn: &'_ _| {
134       CommunityModerator::get_user_moderated_communities(conn, user_id)
135     })
136     .await??;
137     Ok(ids)
138   }
139 }
140
141 pub(crate) fn check_optional_url(item: &Option<Option<String>>) -> Result<(), LemmyError> {
142   if let Some(Some(item)) = &item {
143     if Url::parse(item).is_err() {
144       return Err(APIError::err("invalid_url").into());
145     }
146   }
147   Ok(())
148 }
149
150 pub(crate) async fn linked_instances(pool: &DbPool) -> Result<Vec<String>, LemmyError> {
151   let mut instances: Vec<String> = Vec::new();
152
153   if Settings::get().federation.enabled {
154     let distinct_communities = blocking(pool, move |conn| {
155       Community::distinct_federated_communities(conn)
156     })
157     .await??;
158
159     instances = distinct_communities
160       .iter()
161       .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
162       .collect::<Result<Vec<String>, LemmyError>>()?;
163
164     instances.append(&mut Settings::get().get_allowed_instances());
165     instances.retain(|a| {
166       !Settings::get().get_blocked_instances().contains(a)
167         && !a.eq("")
168         && !a.eq(&Settings::get().hostname)
169     });
170
171     // Sort and remove dupes
172     instances.sort_unstable();
173     instances.dedup();
174   }
175
176   Ok(instances)
177 }
178
179 pub async fn match_websocket_operation(
180   context: LemmyContext,
181   id: ConnectionId,
182   op: UserOperation,
183   data: &str,
184 ) -> Result<String, LemmyError> {
185   match op {
186     // User ops
187     UserOperation::Login => do_websocket_operation::<Login>(context, id, op, data).await,
188     UserOperation::Register => do_websocket_operation::<Register>(context, id, op, data).await,
189     UserOperation::GetCaptcha => do_websocket_operation::<GetCaptcha>(context, id, op, data).await,
190     UserOperation::GetUserDetails => {
191       do_websocket_operation::<GetUserDetails>(context, id, op, data).await
192     }
193     UserOperation::GetReplies => do_websocket_operation::<GetReplies>(context, id, op, data).await,
194     UserOperation::AddAdmin => do_websocket_operation::<AddAdmin>(context, id, op, data).await,
195     UserOperation::BanUser => do_websocket_operation::<BanUser>(context, id, op, data).await,
196     UserOperation::GetUserMentions => {
197       do_websocket_operation::<GetUserMentions>(context, id, op, data).await
198     }
199     UserOperation::MarkUserMentionAsRead => {
200       do_websocket_operation::<MarkUserMentionAsRead>(context, id, op, data).await
201     }
202     UserOperation::MarkAllAsRead => {
203       do_websocket_operation::<MarkAllAsRead>(context, id, op, data).await
204     }
205     UserOperation::DeleteAccount => {
206       do_websocket_operation::<DeleteAccount>(context, id, op, data).await
207     }
208     UserOperation::PasswordReset => {
209       do_websocket_operation::<PasswordReset>(context, id, op, data).await
210     }
211     UserOperation::PasswordChange => {
212       do_websocket_operation::<PasswordChange>(context, id, op, data).await
213     }
214     UserOperation::UserJoin => do_websocket_operation::<UserJoin>(context, id, op, data).await,
215     UserOperation::PostJoin => do_websocket_operation::<PostJoin>(context, id, op, data).await,
216     UserOperation::CommunityJoin => {
217       do_websocket_operation::<CommunityJoin>(context, id, op, data).await
218     }
219     UserOperation::ModJoin => do_websocket_operation::<ModJoin>(context, id, op, data).await,
220     UserOperation::SaveUserSettings => {
221       do_websocket_operation::<SaveUserSettings>(context, id, op, data).await
222     }
223     UserOperation::GetReportCount => {
224       do_websocket_operation::<GetReportCount>(context, id, op, data).await
225     }
226
227     // Private Message ops
228     UserOperation::CreatePrivateMessage => {
229       do_websocket_operation::<CreatePrivateMessage>(context, id, op, data).await
230     }
231     UserOperation::EditPrivateMessage => {
232       do_websocket_operation::<EditPrivateMessage>(context, id, op, data).await
233     }
234     UserOperation::DeletePrivateMessage => {
235       do_websocket_operation::<DeletePrivateMessage>(context, id, op, data).await
236     }
237     UserOperation::MarkPrivateMessageAsRead => {
238       do_websocket_operation::<MarkPrivateMessageAsRead>(context, id, op, data).await
239     }
240     UserOperation::GetPrivateMessages => {
241       do_websocket_operation::<GetPrivateMessages>(context, id, op, data).await
242     }
243
244     // Site ops
245     UserOperation::GetModlog => do_websocket_operation::<GetModlog>(context, id, op, data).await,
246     UserOperation::CreateSite => do_websocket_operation::<CreateSite>(context, id, op, data).await,
247     UserOperation::EditSite => do_websocket_operation::<EditSite>(context, id, op, data).await,
248     UserOperation::GetSite => do_websocket_operation::<GetSite>(context, id, op, data).await,
249     UserOperation::GetSiteConfig => {
250       do_websocket_operation::<GetSiteConfig>(context, id, op, data).await
251     }
252     UserOperation::SaveSiteConfig => {
253       do_websocket_operation::<SaveSiteConfig>(context, id, op, data).await
254     }
255     UserOperation::Search => do_websocket_operation::<Search>(context, id, op, data).await,
256     UserOperation::TransferCommunity => {
257       do_websocket_operation::<TransferCommunity>(context, id, op, data).await
258     }
259     UserOperation::TransferSite => {
260       do_websocket_operation::<TransferSite>(context, id, op, data).await
261     }
262     UserOperation::ListCategories => {
263       do_websocket_operation::<ListCategories>(context, id, op, data).await
264     }
265
266     // Community ops
267     UserOperation::GetCommunity => {
268       do_websocket_operation::<GetCommunity>(context, id, op, data).await
269     }
270     UserOperation::ListCommunities => {
271       do_websocket_operation::<ListCommunities>(context, id, op, data).await
272     }
273     UserOperation::CreateCommunity => {
274       do_websocket_operation::<CreateCommunity>(context, id, op, data).await
275     }
276     UserOperation::EditCommunity => {
277       do_websocket_operation::<EditCommunity>(context, id, op, data).await
278     }
279     UserOperation::DeleteCommunity => {
280       do_websocket_operation::<DeleteCommunity>(context, id, op, data).await
281     }
282     UserOperation::RemoveCommunity => {
283       do_websocket_operation::<RemoveCommunity>(context, id, op, data).await
284     }
285     UserOperation::FollowCommunity => {
286       do_websocket_operation::<FollowCommunity>(context, id, op, data).await
287     }
288     UserOperation::GetFollowedCommunities => {
289       do_websocket_operation::<GetFollowedCommunities>(context, id, op, data).await
290     }
291     UserOperation::BanFromCommunity => {
292       do_websocket_operation::<BanFromCommunity>(context, id, op, data).await
293     }
294     UserOperation::AddModToCommunity => {
295       do_websocket_operation::<AddModToCommunity>(context, id, op, data).await
296     }
297
298     // Post ops
299     UserOperation::CreatePost => do_websocket_operation::<CreatePost>(context, id, op, data).await,
300     UserOperation::GetPost => do_websocket_operation::<GetPost>(context, id, op, data).await,
301     UserOperation::GetPosts => do_websocket_operation::<GetPosts>(context, id, op, data).await,
302     UserOperation::EditPost => do_websocket_operation::<EditPost>(context, id, op, data).await,
303     UserOperation::DeletePost => do_websocket_operation::<DeletePost>(context, id, op, data).await,
304     UserOperation::RemovePost => do_websocket_operation::<RemovePost>(context, id, op, data).await,
305     UserOperation::LockPost => do_websocket_operation::<LockPost>(context, id, op, data).await,
306     UserOperation::StickyPost => do_websocket_operation::<StickyPost>(context, id, op, data).await,
307     UserOperation::CreatePostLike => {
308       do_websocket_operation::<CreatePostLike>(context, id, op, data).await
309     }
310     UserOperation::SavePost => do_websocket_operation::<SavePost>(context, id, op, data).await,
311     UserOperation::CreatePostReport => {
312       do_websocket_operation::<CreatePostReport>(context, id, op, data).await
313     }
314     UserOperation::ListPostReports => {
315       do_websocket_operation::<ListPostReports>(context, id, op, data).await
316     }
317     UserOperation::ResolvePostReport => {
318       do_websocket_operation::<ResolvePostReport>(context, id, op, data).await
319     }
320
321     // Comment ops
322     UserOperation::CreateComment => {
323       do_websocket_operation::<CreateComment>(context, id, op, data).await
324     }
325     UserOperation::EditComment => {
326       do_websocket_operation::<EditComment>(context, id, op, data).await
327     }
328     UserOperation::DeleteComment => {
329       do_websocket_operation::<DeleteComment>(context, id, op, data).await
330     }
331     UserOperation::RemoveComment => {
332       do_websocket_operation::<RemoveComment>(context, id, op, data).await
333     }
334     UserOperation::MarkCommentAsRead => {
335       do_websocket_operation::<MarkCommentAsRead>(context, id, op, data).await
336     }
337     UserOperation::SaveComment => {
338       do_websocket_operation::<SaveComment>(context, id, op, data).await
339     }
340     UserOperation::GetComments => {
341       do_websocket_operation::<GetComments>(context, id, op, data).await
342     }
343     UserOperation::CreateCommentLike => {
344       do_websocket_operation::<CreateCommentLike>(context, id, op, data).await
345     }
346     UserOperation::CreateCommentReport => {
347       do_websocket_operation::<CreateCommentReport>(context, id, op, data).await
348     }
349     UserOperation::ListCommentReports => {
350       do_websocket_operation::<ListCommentReports>(context, id, op, data).await
351     }
352     UserOperation::ResolveCommentReport => {
353       do_websocket_operation::<ResolveCommentReport>(context, id, op, data).await
354     }
355   }
356 }
357
358 async fn do_websocket_operation<'a, 'b, Data>(
359   context: LemmyContext,
360   id: ConnectionId,
361   op: UserOperation,
362   data: &str,
363 ) -> Result<String, LemmyError>
364 where
365   for<'de> Data: Deserialize<'de> + 'a,
366   Data: Perform,
367 {
368   let parsed_data: Data = serde_json::from_str(&data)?;
369   let res = parsed_data
370     .perform(&web::Data::new(context), Some(id))
371     .await?;
372   serialize_websocket_message(&op, &res)
373 }
374
375 pub(crate) fn captcha_espeak_wav_base64(captcha: &str) -> Result<String, LemmyError> {
376   let mut built_text = String::new();
377
378   // Building proper speech text for espeak
379   for mut c in captcha.chars() {
380     let new_str = if c.is_alphabetic() {
381       if c.is_lowercase() {
382         c.make_ascii_uppercase();
383         format!("lower case {} ... ", c)
384       } else {
385         c.make_ascii_uppercase();
386         format!("capital {} ... ", c)
387       }
388     } else {
389       format!("{} ...", c)
390     };
391
392     built_text.push_str(&new_str);
393   }
394
395   espeak_wav_base64(&built_text)
396 }
397
398 pub(crate) fn espeak_wav_base64(text: &str) -> Result<String, LemmyError> {
399   // Make a temp file path
400   let uuid = uuid::Uuid::new_v4().to_string();
401   let file_path = format!("/tmp/lemmy_espeak_{}.wav", &uuid);
402
403   // Write the wav file
404   Command::new("espeak")
405     .arg("-w")
406     .arg(&file_path)
407     .arg(text)
408     .status()?;
409
410   // Read the wav file bytes
411   let bytes = std::fs::read(&file_path)?;
412
413   // Delete the file
414   std::fs::remove_file(file_path)?;
415
416   // Convert to base64
417   let base64 = base64::encode(bytes);
418
419   Ok(base64)
420 }
421
422 #[cfg(test)]
423 mod tests {
424   use crate::captcha_espeak_wav_base64;
425
426   #[test]
427   fn test_espeak() {
428     assert!(captcha_espeak_wav_base64("WxRt2l").is_ok())
429   }
430 }