]> Untitled Git - lemmy.git/blob - crates/api_common/src/lib.rs
Merge pull request #1937 from LemmyNet/disable-edit-email-notifications
[lemmy.git] / crates / api_common / src / lib.rs
1 pub mod comment;
2 pub mod community;
3 pub mod person;
4 pub mod post;
5 pub mod site;
6 pub mod websocket;
7
8 use crate::site::FederatedInstances;
9 use lemmy_db_schema::{
10   newtypes::{CommunityId, LocalUserId, PersonId, PostId},
11   source::{
12     community::Community,
13     person_block::PersonBlock,
14     post::{Post, PostRead, PostReadForm},
15     secret::Secret,
16     site::Site,
17   },
18   traits::{Crud, Readable},
19   DbPool,
20 };
21 use lemmy_db_views::local_user_view::{LocalUserSettingsView, LocalUserView};
22 use lemmy_db_views_actor::{
23   community_person_ban_view::CommunityPersonBanView,
24   community_view::CommunityView,
25 };
26 use lemmy_utils::{claims::Claims, settings::structs::FederationConfig, ApiError, LemmyError};
27 use url::Url;
28
29 pub async fn blocking<F, T>(pool: &DbPool, f: F) -> Result<T, LemmyError>
30 where
31   F: FnOnce(&diesel::PgConnection) -> T + Send + 'static,
32   T: Send + 'static,
33 {
34   let pool = pool.clone();
35   let res = actix_web::web::block(move || {
36     let conn = pool.get()?;
37     let res = (f)(&conn);
38     Ok(res) as Result<T, LemmyError>
39   })
40   .await?;
41
42   res
43 }
44
45 pub async fn is_mod_or_admin(
46   pool: &DbPool,
47   person_id: PersonId,
48   community_id: CommunityId,
49 ) -> Result<(), LemmyError> {
50   let is_mod_or_admin = blocking(pool, move |conn| {
51     CommunityView::is_mod_or_admin(conn, person_id, community_id)
52   })
53   .await?;
54   if !is_mod_or_admin {
55     return Err(ApiError::err_plain("not_a_mod_or_admin").into());
56   }
57   Ok(())
58 }
59
60 pub fn is_admin(local_user_view: &LocalUserView) -> Result<(), LemmyError> {
61   if !local_user_view.person.admin {
62     return Err(ApiError::err_plain("not_an_admin").into());
63   }
64   Ok(())
65 }
66
67 pub async fn get_post(post_id: PostId, pool: &DbPool) -> Result<Post, LemmyError> {
68   blocking(pool, move |conn| Post::read(conn, post_id))
69     .await?
70     .map_err(|_| ApiError::err_plain("couldnt_find_post").into())
71 }
72
73 pub async fn mark_post_as_read(
74   person_id: PersonId,
75   post_id: PostId,
76   pool: &DbPool,
77 ) -> Result<PostRead, LemmyError> {
78   let post_read_form = PostReadForm { post_id, person_id };
79
80   blocking(pool, move |conn| {
81     PostRead::mark_as_read(conn, &post_read_form)
82   })
83   .await?
84   .map_err(|e| ApiError::err("couldnt_mark_post_as_read", e).into())
85 }
86
87 pub async fn mark_post_as_unread(
88   person_id: PersonId,
89   post_id: PostId,
90   pool: &DbPool,
91 ) -> Result<usize, LemmyError> {
92   let post_read_form = PostReadForm { post_id, person_id };
93
94   blocking(pool, move |conn| {
95     PostRead::mark_as_unread(conn, &post_read_form)
96   })
97   .await?
98   .map_err(|e| ApiError::err("couldnt_mark_post_as_read", e).into())
99 }
100
101 pub async fn get_local_user_view_from_jwt(
102   jwt: &str,
103   pool: &DbPool,
104   secret: &Secret,
105 ) -> Result<LocalUserView, LemmyError> {
106   let claims = Claims::decode(jwt, &secret.jwt_secret)
107     .map_err(|e| ApiError::err("not_logged_in", e))?
108     .claims;
109   let local_user_id = LocalUserId(claims.sub);
110   let local_user_view =
111     blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
112   // Check for a site ban
113   if local_user_view.person.banned {
114     return Err(ApiError::err_plain("site_ban").into());
115   }
116
117   // Check for user deletion
118   if local_user_view.person.deleted {
119     return Err(ApiError::err_plain("deleted").into());
120   }
121
122   check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
123
124   Ok(local_user_view)
125 }
126
127 /// Checks if user's token was issued before user's password reset.
128 pub fn check_validator_time(
129   validator_time: &chrono::NaiveDateTime,
130   claims: &Claims,
131 ) -> Result<(), LemmyError> {
132   let user_validation_time = validator_time.timestamp();
133   if user_validation_time > claims.iat {
134     Err(ApiError::err_plain("not_logged_in").into())
135   } else {
136     Ok(())
137   }
138 }
139
140 pub async fn get_local_user_view_from_jwt_opt(
141   jwt: &Option<String>,
142   pool: &DbPool,
143   secret: &Secret,
144 ) -> Result<Option<LocalUserView>, LemmyError> {
145   match jwt {
146     Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool, secret).await?)),
147     None => Ok(None),
148   }
149 }
150
151 pub async fn get_local_user_settings_view_from_jwt(
152   jwt: &str,
153   pool: &DbPool,
154   secret: &Secret,
155 ) -> Result<LocalUserSettingsView, LemmyError> {
156   let claims = Claims::decode(jwt, &secret.jwt_secret)
157     .map_err(|e| ApiError::err("not_logged_in", e))?
158     .claims;
159   let local_user_id = LocalUserId(claims.sub);
160   let local_user_view = blocking(pool, move |conn| {
161     LocalUserSettingsView::read(conn, local_user_id)
162   })
163   .await??;
164   // Check for a site ban
165   if local_user_view.person.banned {
166     return Err(ApiError::err_plain("site_ban").into());
167   }
168
169   check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
170
171   Ok(local_user_view)
172 }
173
174 pub async fn get_local_user_settings_view_from_jwt_opt(
175   jwt: &Option<String>,
176   pool: &DbPool,
177   secret: &Secret,
178 ) -> Result<Option<LocalUserSettingsView>, LemmyError> {
179   match jwt {
180     Some(jwt) => Ok(Some(
181       get_local_user_settings_view_from_jwt(jwt, pool, secret).await?,
182     )),
183     None => Ok(None),
184   }
185 }
186
187 pub async fn check_community_ban(
188   person_id: PersonId,
189   community_id: CommunityId,
190   pool: &DbPool,
191 ) -> Result<(), LemmyError> {
192   let is_banned =
193     move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
194   if blocking(pool, is_banned).await? {
195     Err(ApiError::err_plain("community_ban").into())
196   } else {
197     Ok(())
198   }
199 }
200
201 pub async fn check_community_deleted_or_removed(
202   community_id: CommunityId,
203   pool: &DbPool,
204 ) -> Result<(), LemmyError> {
205   let community = blocking(pool, move |conn| Community::read(conn, community_id))
206     .await?
207     .map_err(|e| ApiError::err("couldnt_find_community", e))?;
208   if community.deleted || community.removed {
209     Err(ApiError::err_plain("deleted").into())
210   } else {
211     Ok(())
212   }
213 }
214
215 pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> {
216   if post.deleted || post.removed {
217     Err(ApiError::err_plain("deleted").into())
218   } else {
219     Ok(())
220   }
221 }
222
223 pub async fn check_person_block(
224   my_id: PersonId,
225   potential_blocker_id: PersonId,
226   pool: &DbPool,
227 ) -> Result<(), LemmyError> {
228   let is_blocked = move |conn: &'_ _| PersonBlock::read(conn, potential_blocker_id, my_id).is_ok();
229   if blocking(pool, is_blocked).await? {
230     Err(ApiError::err_plain("person_block").into())
231   } else {
232     Ok(())
233   }
234 }
235
236 pub async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
237   if score == -1 {
238     let site = blocking(pool, Site::read_simple).await??;
239     if !site.enable_downvotes {
240       return Err(ApiError::err_plain("downvotes_disabled").into());
241     }
242   }
243   Ok(())
244 }
245
246 pub async fn build_federated_instances(
247   pool: &DbPool,
248   federation_config: &FederationConfig,
249   hostname: &str,
250 ) -> Result<Option<FederatedInstances>, LemmyError> {
251   let federation = federation_config.to_owned();
252   if federation.enabled {
253     let distinct_communities = blocking(pool, move |conn| {
254       Community::distinct_federated_communities(conn)
255     })
256     .await??;
257
258     let allowed = federation.allowed_instances;
259     let blocked = federation.blocked_instances;
260
261     let mut linked = distinct_communities
262       .iter()
263       .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
264       .collect::<Result<Vec<String>, LemmyError>>()?;
265
266     if let Some(allowed) = allowed.as_ref() {
267       linked.extend_from_slice(allowed);
268     }
269
270     if let Some(blocked) = blocked.as_ref() {
271       linked.retain(|a| !blocked.contains(a) && !a.eq(hostname));
272     }
273
274     // Sort and remove dupes
275     linked.sort_unstable();
276     linked.dedup();
277
278     Ok(Some(FederatedInstances {
279       linked,
280       allowed,
281       blocked,
282     }))
283   } else {
284     Ok(None)
285   }
286 }
287
288 /// Checks the password length
289 pub fn password_length_check(pass: &str) -> Result<(), LemmyError> {
290   if !(10..=60).contains(&pass.len()) {
291     Err(ApiError::err_plain("invalid_password").into())
292   } else {
293     Ok(())
294   }
295 }
296
297 /// Checks the site description length
298 pub fn site_description_length_check(description: &str) -> Result<(), LemmyError> {
299   if description.len() > 150 {
300     Err(ApiError::err_plain("site_description_length_overflow").into())
301   } else {
302     Ok(())
303   }
304 }
305
306 /// Checks for a honeypot. If this field is filled, fail the rest of the function
307 pub fn honeypot_check(honeypot: &Option<String>) -> Result<(), LemmyError> {
308   if honeypot.is_some() {
309     Err(ApiError::err_plain("honeypot_fail").into())
310   } else {
311     Ok(())
312   }
313 }