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