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