2 use actix_web::web::Data;
5 use lemmy_api_common::{
7 build_federated_instances,
8 check_private_instance,
9 get_local_user_view_from_jwt,
10 get_local_user_view_from_jwt_opt,
12 send_application_approved_email,
17 search::{search_by_apub_id, SearchableObjects},
18 webfinger::webfinger_resolve,
20 objects::community::ApubCommunity,
23 use lemmy_db_schema::{
24 diesel_option_overwrite,
25 from_opt_str_to_opt_enum,
28 local_user::{LocalUser, LocalUserForm},
30 registration_application::{RegistrationApplication, RegistrationApplicationForm},
33 traits::{Crud, DeleteableOrRemoveable},
40 comment_view::{CommentQueryBuilder, CommentView},
41 local_user_view::LocalUserView,
42 post_view::{PostQueryBuilder, PostView},
43 registration_application_view::{
44 RegistrationApplicationQueryBuilder,
45 RegistrationApplicationView,
49 use lemmy_db_views_actor::{
50 community_view::{CommunityQueryBuilder, CommunityView},
51 person_view::{PersonQueryBuilder, PersonViewSafe},
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,
65 use lemmy_utils::{location_info, settings::structs::Settings, version, ConnectionId, LemmyError};
66 use lemmy_websocket::LemmyContext;
68 #[async_trait::async_trait(?Send)]
69 impl Perform for GetModlog {
70 type Response = GetModlogResponse;
72 #[tracing::instrument(skip(context, _websocket_id))]
75 context: &Data<LemmyContext>,
76 _websocket_id: Option<ConnectionId>,
77 ) -> Result<GetModlogResponse, LemmyError> {
78 let data: &GetModlog = self;
81 get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
84 check_private_instance(&local_user_view, context.pool()).await?;
86 let community_id = data.community_id;
87 let mod_person_id = data.mod_person_id;
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)
95 let locked_posts = blocking(context.pool(), move |conn| {
96 ModLockPostView::list(conn, community_id, mod_person_id, page, limit)
100 let stickied_posts = blocking(context.pool(), move |conn| {
101 ModStickyPostView::list(conn, community_id, mod_person_id, page, limit)
105 let removed_comments = blocking(context.pool(), move |conn| {
106 ModRemoveCommentView::list(conn, community_id, mod_person_id, page, limit)
110 let banned_from_community = blocking(context.pool(), move |conn| {
111 ModBanFromCommunityView::list(conn, community_id, mod_person_id, page, limit)
115 let added_to_community = blocking(context.pool(), move |conn| {
116 ModAddCommunityView::list(conn, community_id, mod_person_id, page, limit)
120 let transferred_to_community = blocking(context.pool(), move |conn| {
121 ModTransferCommunityView::list(conn, community_id, mod_person_id, page, limit)
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| {
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>
136 (Vec::new(), Vec::new(), Vec::new())
140 Ok(GetModlogResponse {
146 banned_from_community,
150 transferred_to_community,
155 #[async_trait::async_trait(?Send)]
156 impl Perform for Search {
157 type Response = SearchResponse;
159 #[tracing::instrument(skip(context, _websocket_id))]
162 context: &Data<LemmyContext>,
163 _websocket_id: Option<ConnectionId>,
164 ) -> Result<SearchResponse, LemmyError> {
165 let data: &Search = self;
167 let local_user_view =
168 get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
171 check_private_instance(&local_user_view, context.pool()).await?;
173 let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
174 let show_bot_accounts = local_user_view
176 .map(|t| t.local_user.show_bot_accounts);
177 let show_read_posts = local_user_view
179 .map(|t| t.local_user.show_read_posts);
181 let person_id = local_user_view.map(|u| u.person.id);
183 let mut posts = Vec::new();
184 let mut comments = Vec::new();
185 let mut communities = Vec::new();
186 let mut users = Vec::new();
188 // TODO no clean / non-nsfw searching rn
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)
204 let creator_id = data.creator_id;
206 SearchType::Posts => {
207 posts = blocking(context.pool(), move |conn| {
208 PostQueryBuilder::create(conn)
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)
225 SearchType::Comments => {
226 comments = blocking(context.pool(), move |conn| {
227 CommentQueryBuilder::create(conn)
229 .listing_type(listing_type)
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)
242 SearchType::Communities => {
243 communities = blocking(context.pool(), move |conn| {
244 CommunityQueryBuilder::create(conn)
246 .listing_type(listing_type)
248 .my_person_id(person_id)
255 SearchType::Users => {
256 users = blocking(context.pool(), move |conn| {
257 PersonQueryBuilder::create(conn)
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();
272 posts = blocking(context.pool(), move |conn| {
273 PostQueryBuilder::create(conn)
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)
290 let q = data.q.to_owned();
291 let community_actor_id = community_actor_id.to_owned();
293 comments = blocking(context.pool(), move |conn| {
294 CommentQueryBuilder::create(conn)
296 .listing_type(listing_type)
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)
309 let q = data.q.to_owned();
311 communities = if community_or_creator_included {
314 blocking(context.pool(), move |conn| {
315 CommunityQueryBuilder::create(conn)
317 .listing_type(listing_type)
319 .my_person_id(person_id)
327 let q = data.q.to_owned();
329 users = if community_or_creator_included {
332 blocking(context.pool(), move |conn| {
333 PersonQueryBuilder::create(conn)
344 posts = blocking(context.pool(), move |conn| {
345 PostQueryBuilder::create(conn)
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)
364 // Blank out deleted or removed info for non logged in users
365 if person_id.is_none() {
366 for cv in communities
368 .filter(|cv| cv.community.deleted || cv.community.removed)
370 cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
375 .filter(|p| p.post.deleted || p.post.removed)
377 pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
382 .filter(|cv| cv.comment.deleted || cv.comment.removed)
384 cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
390 type_: search_type.to_string(),
399 #[async_trait::async_trait(?Send)]
400 impl Perform for ResolveObject {
401 type Response = ResolveObjectResponse;
403 #[tracing::instrument(skip(context, _websocket_id))]
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())
412 check_private_instance(&local_user_view, context.pool()).await?;
414 let res = search_by_apub_id(&self.q, context)
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())
420 .map_err(LemmyError::from)
421 .map_err(|e| e.with_message("couldnt_find_object"))
425 async fn convert_response(
426 object: SearchableObjects,
427 user_id: Option<PersonId>,
429 ) -> Result<ResolveObjectResponse, LemmyError> {
430 let removed_or_deleted;
431 let mut res = ResolveObjectResponse {
437 use SearchableObjects::*;
440 removed_or_deleted = p.deleted;
441 res.person = Some(blocking(pool, move |conn| PersonViewSafe::read(conn, p.id)).await??)
444 removed_or_deleted = c.deleted || c.removed;
446 Some(blocking(pool, move |conn| CommunityView::read(conn, c.id, user_id)).await??)
449 removed_or_deleted = p.deleted || p.removed;
450 res.post = Some(blocking(pool, move |conn| PostView::read(conn, p.id, user_id)).await??)
453 removed_or_deleted = c.deleted || c.removed;
454 res.comment = Some(blocking(pool, move |conn| CommentView::read(conn, c.id, user_id)).await??)
457 // if the object was deleted from database, dont return it
458 if removed_or_deleted {
459 return Err(NotFound {}.into());
464 #[async_trait::async_trait(?Send)]
465 impl Perform for TransferSite {
466 type Response = GetSiteResponse;
468 #[tracing::instrument(skip(context, _websocket_id))]
471 context: &Data<LemmyContext>,
472 _websocket_id: Option<ConnectionId>,
473 ) -> Result<GetSiteResponse, LemmyError> {
474 let data: &TransferSite = self;
475 let local_user_view =
476 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
478 is_admin(&local_user_view)?;
480 let read_site = blocking(context.pool(), Site::read_simple).await??;
482 // Make sure user is the creator
483 if read_site.creator_id != local_user_view.person.id {
484 return Err(LemmyError::from_message("not_an_admin"));
487 let new_creator_id = data.person_id;
488 let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
489 blocking(context.pool(), transfer_site)
491 .map_err(LemmyError::from)
492 .map_err(|e| e.with_message("couldnt_update_site"))?;
495 let form = ModAddForm {
496 mod_person_id: local_user_view.person.id,
497 other_person_id: data.person_id,
498 removed: Some(false),
501 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
503 let site_view = blocking(context.pool(), SiteView::read).await??;
505 let mut admins = blocking(context.pool(), PersonViewSafe::admins).await??;
506 let creator_index = admins
508 .position(|r| r.person.id == site_view.creator.id)
509 .context(location_info!())?;
510 let creator_person = admins.remove(creator_index);
511 admins.insert(0, creator_person);
513 let federated_instances = build_federated_instances(
515 &context.settings().federation,
516 &context.settings().hostname,
521 site_view: Some(site_view),
524 version: version::VERSION.to_string(),
531 #[async_trait::async_trait(?Send)]
532 impl Perform for GetSiteConfig {
533 type Response = GetSiteConfigResponse;
535 #[tracing::instrument(skip(context, _websocket_id))]
538 context: &Data<LemmyContext>,
539 _websocket_id: Option<ConnectionId>,
540 ) -> Result<GetSiteConfigResponse, LemmyError> {
541 let data: &GetSiteConfig = self;
542 let local_user_view =
543 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
545 // Only let admins read this
546 is_admin(&local_user_view)?;
548 let config_hjson = Settings::read_config_file()?;
550 Ok(GetSiteConfigResponse { config_hjson })
554 #[async_trait::async_trait(?Send)]
555 impl Perform for SaveSiteConfig {
556 type Response = GetSiteConfigResponse;
558 #[tracing::instrument(skip(context, _websocket_id))]
561 context: &Data<LemmyContext>,
562 _websocket_id: Option<ConnectionId>,
563 ) -> Result<GetSiteConfigResponse, LemmyError> {
564 let data: &SaveSiteConfig = self;
565 let local_user_view =
566 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
568 // Only let admins read this
569 is_admin(&local_user_view)?;
571 // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
572 let config_hjson = Settings::save_config_file(&data.config_hjson)
573 .map_err(LemmyError::from)
574 .map_err(|e| e.with_message("couldnt_update_site"))?;
576 Ok(GetSiteConfigResponse { config_hjson })
580 /// Lists registration applications, filterable by undenied only.
581 #[async_trait::async_trait(?Send)]
582 impl Perform for ListRegistrationApplications {
583 type Response = ListRegistrationApplicationsResponse;
587 context: &Data<LemmyContext>,
588 _websocket_id: Option<ConnectionId>,
589 ) -> Result<Self::Response, LemmyError> {
591 let local_user_view =
592 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
594 // Make sure user is an admin
595 is_admin(&local_user_view)?;
597 let unread_only = data.unread_only;
598 let verified_email_only = blocking(context.pool(), Site::read_simple)
600 .require_email_verification;
602 let page = data.page;
603 let limit = data.limit;
604 let registration_applications = blocking(context.pool(), move |conn| {
605 RegistrationApplicationQueryBuilder::create(conn)
606 .unread_only(unread_only)
607 .verified_email_only(verified_email_only)
614 let res = Self::Response {
615 registration_applications,
622 #[async_trait::async_trait(?Send)]
623 impl Perform for ApproveRegistrationApplication {
624 type Response = RegistrationApplicationResponse;
628 context: &Data<LemmyContext>,
629 _websocket_id: Option<ConnectionId>,
630 ) -> Result<Self::Response, LemmyError> {
632 let local_user_view =
633 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
635 let app_id = data.id;
637 // Only let admins do this
638 is_admin(&local_user_view)?;
640 // Update the registration with reason, admin_id
641 let deny_reason = diesel_option_overwrite(&data.deny_reason);
642 let app_form = RegistrationApplicationForm {
643 admin_id: Some(local_user_view.person.id),
645 ..RegistrationApplicationForm::default()
648 let registration_application = blocking(context.pool(), move |conn| {
649 RegistrationApplication::update(conn, app_id, &app_form)
653 // Update the local_user row
654 let local_user_form = LocalUserForm {
655 accepted_application: Some(data.approve),
656 ..LocalUserForm::default()
659 let approved_user_id = registration_application.local_user_id;
660 blocking(context.pool(), move |conn| {
661 LocalUser::update(conn, approved_user_id, &local_user_form)
666 let approved_local_user_view = blocking(context.pool(), move |conn| {
667 LocalUserView::read(conn, approved_user_id)
671 if approved_local_user_view.local_user.email.is_some() {
672 send_application_approved_email(&approved_local_user_view, &context.settings())?;
677 let registration_application = blocking(context.pool(), move |conn| {
678 RegistrationApplicationView::read(conn, app_id)
683 registration_application,
688 #[async_trait::async_trait(?Send)]
689 impl Perform for GetUnreadRegistrationApplicationCount {
690 type Response = GetUnreadRegistrationApplicationCountResponse;
694 context: &Data<LemmyContext>,
695 _websocket_id: Option<ConnectionId>,
696 ) -> Result<Self::Response, LemmyError> {
698 let local_user_view =
699 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
701 // Only let admins do this
702 is_admin(&local_user_view)?;
704 let verified_email_only = blocking(context.pool(), Site::read_simple)
706 .require_email_verification;
708 let registration_applications = blocking(context.pool(), move |conn| {
709 RegistrationApplicationView::get_unread_count(conn, verified_email_only)
714 registration_applications,