]> Untitled Git - lemmy.git/blob - crates/api/src/site.rs
Removing the site creator, adding leave_admin. Fixes #1808 (#2052)
[lemmy.git] / crates / api / src / site.rs
1 use crate::Perform;
2 use actix_web::web::Data;
3 use diesel::NotFound;
4 use lemmy_api_common::{
5   blocking,
6   build_federated_instances,
7   check_private_instance,
8   get_local_user_view_from_jwt,
9   get_local_user_view_from_jwt_opt,
10   is_admin,
11   send_application_approved_email,
12   site::*,
13 };
14 use lemmy_apub::{
15   fetcher::{
16     search::{search_by_apub_id, SearchableObjects},
17     webfinger::webfinger_resolve,
18   },
19   objects::community::ApubCommunity,
20   EndpointType,
21 };
22 use lemmy_db_schema::{
23   diesel_option_overwrite,
24   from_opt_str_to_opt_enum,
25   newtypes::PersonId,
26   source::{
27     local_user::{LocalUser, LocalUserForm},
28     moderator::*,
29     person::Person,
30     registration_application::{RegistrationApplication, RegistrationApplicationForm},
31     site::Site,
32   },
33   traits::{Crud, DeleteableOrRemoveable},
34   DbPool,
35   ListingType,
36   SearchType,
37   SortType,
38 };
39 use lemmy_db_views::{
40   comment_view::{CommentQueryBuilder, CommentView},
41   local_user_view::LocalUserView,
42   post_view::{PostQueryBuilder, PostView},
43   registration_application_view::{
44     RegistrationApplicationQueryBuilder,
45     RegistrationApplicationView,
46   },
47   site_view::SiteView,
48 };
49 use lemmy_db_views_actor::{
50   community_view::{CommunityQueryBuilder, CommunityView},
51   person_view::{PersonQueryBuilder, PersonViewSafe},
52 };
53 use lemmy_db_views_moderator::{
54   mod_add_community_view::ModAddCommunityView,
55   mod_add_view::ModAddView,
56   mod_ban_from_community_view::ModBanFromCommunityView,
57   mod_ban_view::ModBanView,
58   mod_lock_post_view::ModLockPostView,
59   mod_remove_comment_view::ModRemoveCommentView,
60   mod_remove_community_view::ModRemoveCommunityView,
61   mod_remove_post_view::ModRemovePostView,
62   mod_sticky_post_view::ModStickyPostView,
63   mod_transfer_community_view::ModTransferCommunityView,
64 };
65 use lemmy_utils::{settings::structs::Settings, version, ConnectionId, LemmyError};
66 use lemmy_websocket::LemmyContext;
67
68 #[async_trait::async_trait(?Send)]
69 impl Perform for GetModlog {
70   type Response = GetModlogResponse;
71
72   #[tracing::instrument(skip(context, _websocket_id))]
73   async fn perform(
74     &self,
75     context: &Data<LemmyContext>,
76     _websocket_id: Option<ConnectionId>,
77   ) -> Result<GetModlogResponse, LemmyError> {
78     let data: &GetModlog = self;
79
80     let local_user_view =
81       get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
82         .await?;
83
84     check_private_instance(&local_user_view, context.pool()).await?;
85
86     let community_id = data.community_id;
87     let mod_person_id = data.mod_person_id;
88     let page = data.page;
89     let limit = data.limit;
90     let removed_posts = blocking(context.pool(), move |conn| {
91       ModRemovePostView::list(conn, community_id, mod_person_id, page, limit)
92     })
93     .await??;
94
95     let locked_posts = blocking(context.pool(), move |conn| {
96       ModLockPostView::list(conn, community_id, mod_person_id, page, limit)
97     })
98     .await??;
99
100     let stickied_posts = blocking(context.pool(), move |conn| {
101       ModStickyPostView::list(conn, community_id, mod_person_id, page, limit)
102     })
103     .await??;
104
105     let removed_comments = blocking(context.pool(), move |conn| {
106       ModRemoveCommentView::list(conn, community_id, mod_person_id, page, limit)
107     })
108     .await??;
109
110     let banned_from_community = blocking(context.pool(), move |conn| {
111       ModBanFromCommunityView::list(conn, community_id, mod_person_id, page, limit)
112     })
113     .await??;
114
115     let added_to_community = blocking(context.pool(), move |conn| {
116       ModAddCommunityView::list(conn, community_id, mod_person_id, page, limit)
117     })
118     .await??;
119
120     let transferred_to_community = blocking(context.pool(), move |conn| {
121       ModTransferCommunityView::list(conn, community_id, mod_person_id, page, limit)
122     })
123     .await??;
124
125     // These arrays are only for the full modlog, when a community isn't given
126     let (removed_communities, banned, added) = if data.community_id.is_none() {
127       blocking(context.pool(), move |conn| {
128         Ok((
129           ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?,
130           ModBanView::list(conn, mod_person_id, page, limit)?,
131           ModAddView::list(conn, mod_person_id, page, limit)?,
132         )) as Result<_, LemmyError>
133       })
134       .await??
135     } else {
136       (Vec::new(), Vec::new(), Vec::new())
137     };
138
139     // Return the jwt
140     Ok(GetModlogResponse {
141       removed_posts,
142       locked_posts,
143       stickied_posts,
144       removed_comments,
145       removed_communities,
146       banned_from_community,
147       banned,
148       added_to_community,
149       added,
150       transferred_to_community,
151     })
152   }
153 }
154
155 #[async_trait::async_trait(?Send)]
156 impl Perform for Search {
157   type Response = SearchResponse;
158
159   #[tracing::instrument(skip(context, _websocket_id))]
160   async fn perform(
161     &self,
162     context: &Data<LemmyContext>,
163     _websocket_id: Option<ConnectionId>,
164   ) -> Result<SearchResponse, LemmyError> {
165     let data: &Search = self;
166
167     let local_user_view =
168       get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
169         .await?;
170
171     check_private_instance(&local_user_view, context.pool()).await?;
172
173     let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
174     let show_bot_accounts = local_user_view
175       .as_ref()
176       .map(|t| t.local_user.show_bot_accounts);
177     let show_read_posts = local_user_view
178       .as_ref()
179       .map(|t| t.local_user.show_read_posts);
180
181     let person_id = local_user_view.map(|u| u.person.id);
182
183     let mut posts = Vec::new();
184     let mut comments = Vec::new();
185     let mut communities = Vec::new();
186     let mut users = Vec::new();
187
188     // TODO no clean / non-nsfw searching rn
189
190     let q = data.q.to_owned();
191     let page = data.page;
192     let limit = data.limit;
193     let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
194     let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.listing_type);
195     let search_type: SearchType = from_opt_str_to_opt_enum(&data.type_).unwrap_or(SearchType::All);
196     let community_id = data.community_id;
197     let community_actor_id = if let Some(name) = &data.community_name {
198       webfinger_resolve::<ApubCommunity>(name, EndpointType::Community, context, &mut 0)
199         .await
200         .ok()
201     } else {
202       None
203     };
204     let creator_id = data.creator_id;
205     match search_type {
206       SearchType::Posts => {
207         posts = blocking(context.pool(), move |conn| {
208           PostQueryBuilder::create(conn)
209             .sort(sort)
210             .show_nsfw(show_nsfw)
211             .show_bot_accounts(show_bot_accounts)
212             .show_read_posts(show_read_posts)
213             .listing_type(listing_type)
214             .community_id(community_id)
215             .community_actor_id(community_actor_id)
216             .creator_id(creator_id)
217             .my_person_id(person_id)
218             .search_term(q)
219             .page(page)
220             .limit(limit)
221             .list()
222         })
223         .await??;
224       }
225       SearchType::Comments => {
226         comments = blocking(context.pool(), move |conn| {
227           CommentQueryBuilder::create(conn)
228             .sort(sort)
229             .listing_type(listing_type)
230             .search_term(q)
231             .show_bot_accounts(show_bot_accounts)
232             .community_id(community_id)
233             .community_actor_id(community_actor_id)
234             .creator_id(creator_id)
235             .my_person_id(person_id)
236             .page(page)
237             .limit(limit)
238             .list()
239         })
240         .await??;
241       }
242       SearchType::Communities => {
243         communities = blocking(context.pool(), move |conn| {
244           CommunityQueryBuilder::create(conn)
245             .sort(sort)
246             .listing_type(listing_type)
247             .search_term(q)
248             .my_person_id(person_id)
249             .page(page)
250             .limit(limit)
251             .list()
252         })
253         .await??;
254       }
255       SearchType::Users => {
256         users = blocking(context.pool(), move |conn| {
257           PersonQueryBuilder::create(conn)
258             .sort(sort)
259             .search_term(q)
260             .page(page)
261             .limit(limit)
262             .list()
263         })
264         .await??;
265       }
266       SearchType::All => {
267         // If the community or creator is included, dont search communities or users
268         let community_or_creator_included =
269           data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some();
270         let community_actor_id_2 = community_actor_id.to_owned();
271
272         posts = blocking(context.pool(), move |conn| {
273           PostQueryBuilder::create(conn)
274             .sort(sort)
275             .show_nsfw(show_nsfw)
276             .show_bot_accounts(show_bot_accounts)
277             .show_read_posts(show_read_posts)
278             .listing_type(listing_type)
279             .community_id(community_id)
280             .community_actor_id(community_actor_id_2)
281             .creator_id(creator_id)
282             .my_person_id(person_id)
283             .search_term(q)
284             .page(page)
285             .limit(limit)
286             .list()
287         })
288         .await??;
289
290         let q = data.q.to_owned();
291         let community_actor_id = community_actor_id.to_owned();
292
293         comments = blocking(context.pool(), move |conn| {
294           CommentQueryBuilder::create(conn)
295             .sort(sort)
296             .listing_type(listing_type)
297             .search_term(q)
298             .show_bot_accounts(show_bot_accounts)
299             .community_id(community_id)
300             .community_actor_id(community_actor_id)
301             .creator_id(creator_id)
302             .my_person_id(person_id)
303             .page(page)
304             .limit(limit)
305             .list()
306         })
307         .await??;
308
309         let q = data.q.to_owned();
310
311         communities = if community_or_creator_included {
312           vec![]
313         } else {
314           blocking(context.pool(), move |conn| {
315             CommunityQueryBuilder::create(conn)
316               .sort(sort)
317               .listing_type(listing_type)
318               .search_term(q)
319               .my_person_id(person_id)
320               .page(page)
321               .limit(limit)
322               .list()
323           })
324           .await??
325         };
326
327         let q = data.q.to_owned();
328
329         users = if community_or_creator_included {
330           vec![]
331         } else {
332           blocking(context.pool(), move |conn| {
333             PersonQueryBuilder::create(conn)
334               .sort(sort)
335               .search_term(q)
336               .page(page)
337               .limit(limit)
338               .list()
339           })
340           .await??
341         };
342       }
343       SearchType::Url => {
344         posts = blocking(context.pool(), move |conn| {
345           PostQueryBuilder::create(conn)
346             .sort(sort)
347             .show_nsfw(show_nsfw)
348             .show_bot_accounts(show_bot_accounts)
349             .show_read_posts(show_read_posts)
350             .listing_type(listing_type)
351             .my_person_id(person_id)
352             .community_id(community_id)
353             .community_actor_id(community_actor_id)
354             .creator_id(creator_id)
355             .url_search(q)
356             .page(page)
357             .limit(limit)
358             .list()
359         })
360         .await??;
361       }
362     };
363
364     // Blank out deleted or removed info for non logged in users
365     if person_id.is_none() {
366       for cv in communities
367         .iter_mut()
368         .filter(|cv| cv.community.deleted || cv.community.removed)
369       {
370         cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
371       }
372
373       for pv in posts
374         .iter_mut()
375         .filter(|p| p.post.deleted || p.post.removed)
376       {
377         pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
378       }
379
380       for cv in comments
381         .iter_mut()
382         .filter(|cv| cv.comment.deleted || cv.comment.removed)
383       {
384         cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
385       }
386     }
387
388     // Return the jwt
389     Ok(SearchResponse {
390       type_: search_type.to_string(),
391       comments,
392       posts,
393       communities,
394       users,
395     })
396   }
397 }
398
399 #[async_trait::async_trait(?Send)]
400 impl Perform for ResolveObject {
401   type Response = ResolveObjectResponse;
402
403   #[tracing::instrument(skip(context, _websocket_id))]
404   async fn perform(
405     &self,
406     context: &Data<LemmyContext>,
407     _websocket_id: Option<ConnectionId>,
408   ) -> Result<ResolveObjectResponse, LemmyError> {
409     let local_user_view =
410       get_local_user_view_from_jwt_opt(self.auth.as_ref(), context.pool(), context.secret())
411         .await?;
412     check_private_instance(&local_user_view, context.pool()).await?;
413
414     let res = search_by_apub_id(&self.q, context)
415       .await
416       .map_err(LemmyError::from)
417       .map_err(|e| e.with_message("couldnt_find_object"))?;
418     convert_response(res, local_user_view.map(|l| l.person.id), context.pool())
419       .await
420       .map_err(LemmyError::from)
421       .map_err(|e| e.with_message("couldnt_find_object"))
422   }
423 }
424
425 async fn convert_response(
426   object: SearchableObjects,
427   user_id: Option<PersonId>,
428   pool: &DbPool,
429 ) -> Result<ResolveObjectResponse, LemmyError> {
430   let removed_or_deleted;
431   let mut res = ResolveObjectResponse {
432     comment: None,
433     post: None,
434     community: None,
435     person: None,
436   };
437   use SearchableObjects::*;
438   match object {
439     Person(p) => {
440       removed_or_deleted = p.deleted;
441       res.person = Some(blocking(pool, move |conn| PersonViewSafe::read(conn, p.id)).await??)
442     }
443     Community(c) => {
444       removed_or_deleted = c.deleted || c.removed;
445       res.community =
446         Some(blocking(pool, move |conn| CommunityView::read(conn, c.id, user_id)).await??)
447     }
448     Post(p) => {
449       removed_or_deleted = p.deleted || p.removed;
450       res.post = Some(blocking(pool, move |conn| PostView::read(conn, p.id, user_id)).await??)
451     }
452     Comment(c) => {
453       removed_or_deleted = c.deleted || c.removed;
454       res.comment = Some(blocking(pool, move |conn| CommentView::read(conn, c.id, user_id)).await??)
455     }
456   };
457   // if the object was deleted from database, dont return it
458   if removed_or_deleted {
459     return Err(NotFound {}.into());
460   }
461   Ok(res)
462 }
463
464 #[async_trait::async_trait(?Send)]
465 impl Perform for LeaveAdmin {
466   type Response = GetSiteResponse;
467
468   #[tracing::instrument(skip(context, _websocket_id))]
469   async fn perform(
470     &self,
471     context: &Data<LemmyContext>,
472     _websocket_id: Option<ConnectionId>,
473   ) -> Result<GetSiteResponse, LemmyError> {
474     let data: &LeaveAdmin = self;
475     let local_user_view =
476       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
477
478     is_admin(&local_user_view)?;
479
480     // Make sure there isn't just one admin (so if one leaves, there will still be one left)
481     let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
482     if admins.len() == 1 {
483       return Err(LemmyError::from_message("cannot_leave_admin"));
484     }
485
486     let person_id = local_user_view.person.id;
487     blocking(context.pool(), move |conn| {
488       Person::leave_admin(conn, person_id)
489     })
490     .await??;
491
492     // Mod tables
493     let form = ModAddForm {
494       mod_person_id: person_id,
495       other_person_id: person_id,
496       removed: Some(true),
497     };
498
499     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
500
501     // Reread site and admins
502     let site_view = blocking(context.pool(), SiteView::read).await??;
503     let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
504
505     let federated_instances = build_federated_instances(
506       context.pool(),
507       &context.settings().federation,
508       &context.settings().hostname,
509     )
510     .await?;
511
512     Ok(GetSiteResponse {
513       site_view: Some(site_view),
514       admins,
515       online: 0,
516       version: version::VERSION.to_string(),
517       my_user: None,
518       federated_instances,
519     })
520   }
521 }
522
523 #[async_trait::async_trait(?Send)]
524 impl Perform for GetSiteConfig {
525   type Response = GetSiteConfigResponse;
526
527   #[tracing::instrument(skip(context, _websocket_id))]
528   async fn perform(
529     &self,
530     context: &Data<LemmyContext>,
531     _websocket_id: Option<ConnectionId>,
532   ) -> Result<GetSiteConfigResponse, LemmyError> {
533     let data: &GetSiteConfig = self;
534     let local_user_view =
535       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
536
537     // Only let admins read this
538     is_admin(&local_user_view)?;
539
540     let config_hjson = Settings::read_config_file()?;
541
542     Ok(GetSiteConfigResponse { config_hjson })
543   }
544 }
545
546 #[async_trait::async_trait(?Send)]
547 impl Perform for SaveSiteConfig {
548   type Response = GetSiteConfigResponse;
549
550   #[tracing::instrument(skip(context, _websocket_id))]
551   async fn perform(
552     &self,
553     context: &Data<LemmyContext>,
554     _websocket_id: Option<ConnectionId>,
555   ) -> Result<GetSiteConfigResponse, LemmyError> {
556     let data: &SaveSiteConfig = self;
557     let local_user_view =
558       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
559
560     // Only let admins read this
561     is_admin(&local_user_view)?;
562
563     // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
564     let config_hjson = Settings::save_config_file(&data.config_hjson)
565       .map_err(LemmyError::from)
566       .map_err(|e| e.with_message("couldnt_update_site"))?;
567
568     Ok(GetSiteConfigResponse { config_hjson })
569   }
570 }
571
572 /// Lists registration applications, filterable by undenied only.
573 #[async_trait::async_trait(?Send)]
574 impl Perform for ListRegistrationApplications {
575   type Response = ListRegistrationApplicationsResponse;
576
577   async fn perform(
578     &self,
579     context: &Data<LemmyContext>,
580     _websocket_id: Option<ConnectionId>,
581   ) -> Result<Self::Response, LemmyError> {
582     let data = self;
583     let local_user_view =
584       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
585
586     // Make sure user is an admin
587     is_admin(&local_user_view)?;
588
589     let unread_only = data.unread_only;
590     let verified_email_only = blocking(context.pool(), Site::read_simple)
591       .await??
592       .require_email_verification;
593
594     let page = data.page;
595     let limit = data.limit;
596     let registration_applications = blocking(context.pool(), move |conn| {
597       RegistrationApplicationQueryBuilder::create(conn)
598         .unread_only(unread_only)
599         .verified_email_only(verified_email_only)
600         .page(page)
601         .limit(limit)
602         .list()
603     })
604     .await??;
605
606     let res = Self::Response {
607       registration_applications,
608     };
609
610     Ok(res)
611   }
612 }
613
614 #[async_trait::async_trait(?Send)]
615 impl Perform for ApproveRegistrationApplication {
616   type Response = RegistrationApplicationResponse;
617
618   async fn perform(
619     &self,
620     context: &Data<LemmyContext>,
621     _websocket_id: Option<ConnectionId>,
622   ) -> Result<Self::Response, LemmyError> {
623     let data = self;
624     let local_user_view =
625       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
626
627     let app_id = data.id;
628
629     // Only let admins do this
630     is_admin(&local_user_view)?;
631
632     // Update the registration with reason, admin_id
633     let deny_reason = diesel_option_overwrite(&data.deny_reason);
634     let app_form = RegistrationApplicationForm {
635       admin_id: Some(local_user_view.person.id),
636       deny_reason,
637       ..RegistrationApplicationForm::default()
638     };
639
640     let registration_application = blocking(context.pool(), move |conn| {
641       RegistrationApplication::update(conn, app_id, &app_form)
642     })
643     .await??;
644
645     // Update the local_user row
646     let local_user_form = LocalUserForm {
647       accepted_application: Some(data.approve),
648       ..LocalUserForm::default()
649     };
650
651     let approved_user_id = registration_application.local_user_id;
652     blocking(context.pool(), move |conn| {
653       LocalUser::update(conn, approved_user_id, &local_user_form)
654     })
655     .await??;
656
657     if data.approve {
658       let approved_local_user_view = blocking(context.pool(), move |conn| {
659         LocalUserView::read(conn, approved_user_id)
660       })
661       .await??;
662
663       if approved_local_user_view.local_user.email.is_some() {
664         send_application_approved_email(&approved_local_user_view, &context.settings())?;
665       }
666     }
667
668     // Read the view
669     let registration_application = blocking(context.pool(), move |conn| {
670       RegistrationApplicationView::read(conn, app_id)
671     })
672     .await??;
673
674     Ok(Self::Response {
675       registration_application,
676     })
677   }
678 }
679
680 #[async_trait::async_trait(?Send)]
681 impl Perform for GetUnreadRegistrationApplicationCount {
682   type Response = GetUnreadRegistrationApplicationCountResponse;
683
684   async fn perform(
685     &self,
686     context: &Data<LemmyContext>,
687     _websocket_id: Option<ConnectionId>,
688   ) -> Result<Self::Response, LemmyError> {
689     let data = self;
690     let local_user_view =
691       get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
692
693     // Only let admins do this
694     is_admin(&local_user_view)?;
695
696     let verified_email_only = blocking(context.pool(), Site::read_simple)
697       .await??
698       .require_email_verification;
699
700     let registration_applications = blocking(context.pool(), move |conn| {
701       RegistrationApplicationView::get_unread_count(conn, verified_email_only)
702     })
703     .await??;
704
705     Ok(Self::Response {
706       registration_applications,
707     })
708   }
709 }