]> Untitled Git - lemmy.git/blob - crates/api_common/src/lib.rs
Major refactor, adding newtypes for apub crate
[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(|_| ApiError::err_plain("couldnt_mark_post_as_read").into())
85 }
86
87 pub async fn get_local_user_view_from_jwt(
88   jwt: &str,
89   pool: &DbPool,
90   secret: &Secret,
91 ) -> Result<LocalUserView, LemmyError> {
92   let claims = Claims::decode(jwt, &secret.jwt_secret)
93     .map_err(|e| ApiError::err("not_logged_in", e))?
94     .claims;
95   let local_user_id = LocalUserId(claims.sub);
96   let local_user_view =
97     blocking(pool, move |conn| LocalUserView::read(conn, local_user_id)).await??;
98   // Check for a site ban
99   if local_user_view.person.banned {
100     return Err(ApiError::err_plain("site_ban").into());
101   }
102
103   // Check for user deletion
104   if local_user_view.person.deleted {
105     return Err(ApiError::err_plain("deleted").into());
106   }
107
108   check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
109
110   Ok(local_user_view)
111 }
112
113 /// Checks if user's token was issued before user's password reset.
114 pub fn check_validator_time(
115   validator_time: &chrono::NaiveDateTime,
116   claims: &Claims,
117 ) -> Result<(), LemmyError> {
118   let user_validation_time = validator_time.timestamp();
119   if user_validation_time > claims.iat {
120     Err(ApiError::err_plain("not_logged_in").into())
121   } else {
122     Ok(())
123   }
124 }
125
126 pub async fn get_local_user_view_from_jwt_opt(
127   jwt: &Option<String>,
128   pool: &DbPool,
129   secret: &Secret,
130 ) -> Result<Option<LocalUserView>, LemmyError> {
131   match jwt {
132     Some(jwt) => Ok(Some(get_local_user_view_from_jwt(jwt, pool, secret).await?)),
133     None => Ok(None),
134   }
135 }
136
137 pub async fn get_local_user_settings_view_from_jwt(
138   jwt: &str,
139   pool: &DbPool,
140   secret: &Secret,
141 ) -> Result<LocalUserSettingsView, LemmyError> {
142   let claims = Claims::decode(jwt, &secret.jwt_secret)
143     .map_err(|e| ApiError::err("not_logged_in", e))?
144     .claims;
145   let local_user_id = LocalUserId(claims.sub);
146   let local_user_view = blocking(pool, move |conn| {
147     LocalUserSettingsView::read(conn, local_user_id)
148   })
149   .await??;
150   // Check for a site ban
151   if local_user_view.person.banned {
152     return Err(ApiError::err_plain("site_ban").into());
153   }
154
155   check_validator_time(&local_user_view.local_user.validator_time, &claims)?;
156
157   Ok(local_user_view)
158 }
159
160 pub async fn get_local_user_settings_view_from_jwt_opt(
161   jwt: &Option<String>,
162   pool: &DbPool,
163   secret: &Secret,
164 ) -> Result<Option<LocalUserSettingsView>, LemmyError> {
165   match jwt {
166     Some(jwt) => Ok(Some(
167       get_local_user_settings_view_from_jwt(jwt, pool, secret).await?,
168     )),
169     None => Ok(None),
170   }
171 }
172
173 pub async fn check_community_ban(
174   person_id: PersonId,
175   community_id: CommunityId,
176   pool: &DbPool,
177 ) -> Result<(), LemmyError> {
178   let is_banned =
179     move |conn: &'_ _| CommunityPersonBanView::get(conn, person_id, community_id).is_ok();
180   if blocking(pool, is_banned).await? {
181     Err(ApiError::err_plain("community_ban").into())
182   } else {
183     Ok(())
184   }
185 }
186
187 pub async fn check_community_deleted_or_removed(
188   community_id: CommunityId,
189   pool: &DbPool,
190 ) -> Result<(), LemmyError> {
191   let community = blocking(pool, move |conn| Community::read(conn, community_id))
192     .await?
193     .map_err(|e| ApiError::err("couldnt_find_community", e))?;
194   if community.deleted || community.removed {
195     Err(ApiError::err_plain("deleted").into())
196   } else {
197     Ok(())
198   }
199 }
200
201 pub fn check_post_deleted_or_removed(post: &Post) -> Result<(), LemmyError> {
202   if post.deleted || post.removed {
203     Err(ApiError::err_plain("deleted").into())
204   } else {
205     Ok(())
206   }
207 }
208
209 pub async fn check_person_block(
210   my_id: PersonId,
211   potential_blocker_id: PersonId,
212   pool: &DbPool,
213 ) -> Result<(), LemmyError> {
214   let is_blocked = move |conn: &'_ _| PersonBlock::read(conn, potential_blocker_id, my_id).is_ok();
215   if blocking(pool, is_blocked).await? {
216     Err(ApiError::err_plain("person_block").into())
217   } else {
218     Ok(())
219   }
220 }
221
222 pub async fn check_downvotes_enabled(score: i16, pool: &DbPool) -> Result<(), LemmyError> {
223   if score == -1 {
224     let site = blocking(pool, Site::read_simple).await??;
225     if !site.enable_downvotes {
226       return Err(ApiError::err_plain("downvotes_disabled").into());
227     }
228   }
229   Ok(())
230 }
231
232 pub async fn build_federated_instances(
233   pool: &DbPool,
234   federation_config: &FederationConfig,
235   hostname: &str,
236 ) -> Result<Option<FederatedInstances>, LemmyError> {
237   let federation = federation_config.to_owned();
238   if federation.enabled {
239     let distinct_communities = blocking(pool, move |conn| {
240       Community::distinct_federated_communities(conn)
241     })
242     .await??;
243
244     let allowed = federation.allowed_instances;
245     let blocked = federation.blocked_instances;
246
247     let mut linked = distinct_communities
248       .iter()
249       .map(|actor_id| Ok(Url::parse(actor_id)?.host_str().unwrap_or("").to_string()))
250       .collect::<Result<Vec<String>, LemmyError>>()?;
251
252     if let Some(allowed) = allowed.as_ref() {
253       linked.extend_from_slice(allowed);
254     }
255
256     if let Some(blocked) = blocked.as_ref() {
257       linked.retain(|a| !blocked.contains(a) && !a.eq(hostname));
258     }
259
260     // Sort and remove dupes
261     linked.sort_unstable();
262     linked.dedup();
263
264     Ok(Some(FederatedInstances {
265       linked,
266       allowed,
267       blocked,
268     }))
269   } else {
270     Ok(None)
271   }
272 }
273
274 /// Checks the password length
275 pub fn password_length_check(pass: &str) -> Result<(), LemmyError> {
276   if !(10..=60).contains(&pass.len()) {
277     Err(ApiError::err_plain("invalid_password").into())
278   } else {
279     Ok(())
280   }
281 }
282
283 /// Checks the site description length
284 pub fn site_description_length_check(description: &str) -> Result<(), LemmyError> {
285   if description.len() > 150 {
286     Err(ApiError::err_plain("site_description_length_overflow").into())
287   } else {
288     Ok(())
289   }
290 }
291
292 /// Checks for a honeypot. If this field is filled, fail the rest of the function
293 pub fn honeypot_check(honeypot: &Option<String>) -> Result<(), LemmyError> {
294   if honeypot.is_some() {
295     Err(ApiError::err_plain("honeypot_fail").into())
296   } else {
297     Ok(())
298   }
299 }