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