]> Untitled Git - lemmy.git/blob - crates/api/src/site.rs
Use .map_err in api code (fixes #1573) (#1575)
[lemmy.git] / crates / api / src / site.rs
1 use crate::Perform;
2 use actix_web::web::Data;
3 use anyhow::Context;
4 use lemmy_api_common::{
5   blocking,
6   build_federated_instances,
7   get_local_user_settings_view_from_jwt,
8   get_local_user_view_from_jwt,
9   get_local_user_view_from_jwt_opt,
10   is_admin,
11   site::*,
12 };
13 use lemmy_apub::fetcher::search::search_by_apub_id;
14 use lemmy_db_queries::{source::site::Site_, Crud, SearchType, SortType};
15 use lemmy_db_schema::source::{moderator::*, site::Site};
16 use lemmy_db_views::{
17   comment_view::CommentQueryBuilder,
18   post_view::PostQueryBuilder,
19   site_view::SiteView,
20 };
21 use lemmy_db_views_actor::{
22   community_view::CommunityQueryBuilder,
23   person_view::{PersonQueryBuilder, PersonViewSafe},
24 };
25 use lemmy_db_views_moderator::{
26   mod_add_community_view::ModAddCommunityView,
27   mod_add_view::ModAddView,
28   mod_ban_from_community_view::ModBanFromCommunityView,
29   mod_ban_view::ModBanView,
30   mod_lock_post_view::ModLockPostView,
31   mod_remove_comment_view::ModRemoveCommentView,
32   mod_remove_community_view::ModRemoveCommunityView,
33   mod_remove_post_view::ModRemovePostView,
34   mod_sticky_post_view::ModStickyPostView,
35 };
36 use lemmy_utils::{
37   location_info,
38   settings::structs::Settings,
39   version,
40   ApiError,
41   ConnectionId,
42   LemmyError,
43 };
44 use lemmy_websocket::LemmyContext;
45 use log::debug;
46 use std::str::FromStr;
47
48 #[async_trait::async_trait(?Send)]
49 impl Perform for GetModlog {
50   type Response = GetModlogResponse;
51
52   async fn perform(
53     &self,
54     context: &Data<LemmyContext>,
55     _websocket_id: Option<ConnectionId>,
56   ) -> Result<GetModlogResponse, LemmyError> {
57     let data: &GetModlog = &self;
58
59     let community_id = data.community_id;
60     let mod_person_id = data.mod_person_id;
61     let page = data.page;
62     let limit = data.limit;
63     let removed_posts = blocking(context.pool(), move |conn| {
64       ModRemovePostView::list(conn, community_id, mod_person_id, page, limit)
65     })
66     .await??;
67
68     let locked_posts = blocking(context.pool(), move |conn| {
69       ModLockPostView::list(conn, community_id, mod_person_id, page, limit)
70     })
71     .await??;
72
73     let stickied_posts = blocking(context.pool(), move |conn| {
74       ModStickyPostView::list(conn, community_id, mod_person_id, page, limit)
75     })
76     .await??;
77
78     let removed_comments = blocking(context.pool(), move |conn| {
79       ModRemoveCommentView::list(conn, community_id, mod_person_id, page, limit)
80     })
81     .await??;
82
83     let banned_from_community = blocking(context.pool(), move |conn| {
84       ModBanFromCommunityView::list(conn, community_id, mod_person_id, page, limit)
85     })
86     .await??;
87
88     let added_to_community = blocking(context.pool(), move |conn| {
89       ModAddCommunityView::list(conn, community_id, mod_person_id, page, limit)
90     })
91     .await??;
92
93     // These arrays are only for the full modlog, when a community isn't given
94     let (removed_communities, banned, added) = if data.community_id.is_none() {
95       blocking(context.pool(), move |conn| {
96         Ok((
97           ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?,
98           ModBanView::list(conn, mod_person_id, page, limit)?,
99           ModAddView::list(conn, mod_person_id, page, limit)?,
100         )) as Result<_, LemmyError>
101       })
102       .await??
103     } else {
104       (Vec::new(), Vec::new(), Vec::new())
105     };
106
107     // Return the jwt
108     Ok(GetModlogResponse {
109       removed_posts,
110       locked_posts,
111       stickied_posts,
112       removed_comments,
113       removed_communities,
114       banned_from_community,
115       banned,
116       added_to_community,
117       added,
118     })
119   }
120 }
121
122 #[async_trait::async_trait(?Send)]
123 impl Perform for Search {
124   type Response = SearchResponse;
125
126   async fn perform(
127     &self,
128     context: &Data<LemmyContext>,
129     _websocket_id: Option<ConnectionId>,
130   ) -> Result<SearchResponse, LemmyError> {
131     let data: &Search = &self;
132
133     match search_by_apub_id(&data.q, context).await {
134       Ok(r) => return Ok(r),
135       Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
136     }
137
138     let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
139     let person_id = local_user_view.map(|u| u.person.id);
140
141     let type_ = SearchType::from_str(&data.type_)?;
142
143     let mut posts = Vec::new();
144     let mut comments = Vec::new();
145     let mut communities = Vec::new();
146     let mut users = Vec::new();
147
148     // TODO no clean / non-nsfw searching rn
149
150     let q = data.q.to_owned();
151     let page = data.page;
152     let limit = data.limit;
153     let sort = SortType::from_str(&data.sort)?;
154     let community_id = data.community_id;
155     let community_name = data.community_name.to_owned();
156     match type_ {
157       SearchType::Posts => {
158         posts = blocking(context.pool(), move |conn| {
159           PostQueryBuilder::create(conn)
160             .sort(&sort)
161             .show_nsfw(true)
162             .community_id(community_id)
163             .community_name(community_name)
164             .my_person_id(person_id)
165             .search_term(q)
166             .page(page)
167             .limit(limit)
168             .list()
169         })
170         .await??;
171       }
172       SearchType::Comments => {
173         comments = blocking(context.pool(), move |conn| {
174           CommentQueryBuilder::create(&conn)
175             .sort(&sort)
176             .search_term(q)
177             .my_person_id(person_id)
178             .page(page)
179             .limit(limit)
180             .list()
181         })
182         .await??;
183       }
184       SearchType::Communities => {
185         communities = blocking(context.pool(), move |conn| {
186           CommunityQueryBuilder::create(conn)
187             .sort(&sort)
188             .search_term(q)
189             .my_person_id(person_id)
190             .page(page)
191             .limit(limit)
192             .list()
193         })
194         .await??;
195       }
196       SearchType::Users => {
197         users = blocking(context.pool(), move |conn| {
198           PersonQueryBuilder::create(conn)
199             .sort(&sort)
200             .search_term(q)
201             .page(page)
202             .limit(limit)
203             .list()
204         })
205         .await??;
206       }
207       SearchType::All => {
208         posts = blocking(context.pool(), move |conn| {
209           PostQueryBuilder::create(conn)
210             .sort(&sort)
211             .show_nsfw(true)
212             .community_id(community_id)
213             .community_name(community_name)
214             .my_person_id(person_id)
215             .search_term(q)
216             .page(page)
217             .limit(limit)
218             .list()
219         })
220         .await??;
221
222         let q = data.q.to_owned();
223         let sort = SortType::from_str(&data.sort)?;
224
225         comments = blocking(context.pool(), move |conn| {
226           CommentQueryBuilder::create(conn)
227             .sort(&sort)
228             .search_term(q)
229             .my_person_id(person_id)
230             .page(page)
231             .limit(limit)
232             .list()
233         })
234         .await??;
235
236         let q = data.q.to_owned();
237         let sort = SortType::from_str(&data.sort)?;
238
239         communities = blocking(context.pool(), move |conn| {
240           CommunityQueryBuilder::create(conn)
241             .sort(&sort)
242             .search_term(q)
243             .my_person_id(person_id)
244             .page(page)
245             .limit(limit)
246             .list()
247         })
248         .await??;
249
250         let q = data.q.to_owned();
251         let sort = SortType::from_str(&data.sort)?;
252
253         users = blocking(context.pool(), move |conn| {
254           PersonQueryBuilder::create(conn)
255             .sort(&sort)
256             .search_term(q)
257             .page(page)
258             .limit(limit)
259             .list()
260         })
261         .await??;
262       }
263       SearchType::Url => {
264         posts = blocking(context.pool(), move |conn| {
265           PostQueryBuilder::create(conn)
266             .sort(&sort)
267             .show_nsfw(true)
268             .my_person_id(person_id)
269             .community_id(community_id)
270             .community_name(community_name)
271             .url_search(q)
272             .page(page)
273             .limit(limit)
274             .list()
275         })
276         .await??;
277       }
278     };
279
280     // Return the jwt
281     Ok(SearchResponse {
282       type_: data.type_.to_owned(),
283       comments,
284       posts,
285       communities,
286       users,
287     })
288   }
289 }
290
291 #[async_trait::async_trait(?Send)]
292 impl Perform for TransferSite {
293   type Response = GetSiteResponse;
294
295   async fn perform(
296     &self,
297     context: &Data<LemmyContext>,
298     _websocket_id: Option<ConnectionId>,
299   ) -> Result<GetSiteResponse, LemmyError> {
300     let data: &TransferSite = &self;
301     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
302
303     is_admin(&local_user_view)?;
304
305     let read_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
306
307     // Make sure user is the creator
308     if read_site.creator_id != local_user_view.person.id {
309       return Err(ApiError::err("not_an_admin").into());
310     }
311
312     let new_creator_id = data.person_id;
313     let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
314     if blocking(context.pool(), transfer_site).await?.is_err() {
315       return Err(ApiError::err("couldnt_update_site").into());
316     };
317
318     // Mod tables
319     let form = ModAddForm {
320       mod_person_id: local_user_view.person.id,
321       other_person_id: data.person_id,
322       removed: Some(false),
323     };
324
325     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
326
327     let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
328
329     let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
330     let creator_index = admins
331       .iter()
332       .position(|r| r.person.id == site_view.creator.id)
333       .context(location_info!())?;
334     let creator_person = admins.remove(creator_index);
335     admins.insert(0, creator_person);
336
337     let banned = blocking(context.pool(), move |conn| PersonViewSafe::banned(conn)).await??;
338     let federated_instances = build_federated_instances(context.pool()).await?;
339
340     let my_user = Some(get_local_user_settings_view_from_jwt(&data.auth, context.pool()).await?);
341
342     Ok(GetSiteResponse {
343       site_view: Some(site_view),
344       admins,
345       banned,
346       online: 0,
347       version: version::VERSION.to_string(),
348       my_user,
349       federated_instances,
350     })
351   }
352 }
353
354 #[async_trait::async_trait(?Send)]
355 impl Perform for GetSiteConfig {
356   type Response = GetSiteConfigResponse;
357
358   async fn perform(
359     &self,
360     context: &Data<LemmyContext>,
361     _websocket_id: Option<ConnectionId>,
362   ) -> Result<GetSiteConfigResponse, LemmyError> {
363     let data: &GetSiteConfig = &self;
364     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
365
366     // Only let admins read this
367     is_admin(&local_user_view)?;
368
369     let config_hjson = Settings::read_config_file()?;
370
371     Ok(GetSiteConfigResponse { config_hjson })
372   }
373 }
374
375 #[async_trait::async_trait(?Send)]
376 impl Perform for SaveSiteConfig {
377   type Response = GetSiteConfigResponse;
378
379   async fn perform(
380     &self,
381     context: &Data<LemmyContext>,
382     _websocket_id: Option<ConnectionId>,
383   ) -> Result<GetSiteConfigResponse, LemmyError> {
384     let data: &SaveSiteConfig = &self;
385     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
386
387     // Only let admins read this
388     is_admin(&local_user_view)?;
389
390     // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
391     let config_hjson = Settings::save_config_file(&data.config_hjson)
392       .map_err(|_| ApiError::err("couldnt_update_site"))?;
393
394     Ok(GetSiteConfigResponse { config_hjson })
395   }
396 }