2 use actix_web::web::Data;
4 use lemmy_api_common::{
6 build_federated_instances,
7 check_private_instance,
8 get_local_user_view_from_jwt,
9 get_local_user_view_from_jwt_opt,
11 send_application_approved_email,
16 search::{search_by_apub_id, SearchableObjects},
17 webfinger::webfinger_resolve,
19 objects::community::ApubCommunity,
22 use lemmy_db_schema::{
23 diesel_option_overwrite,
24 from_opt_str_to_opt_enum,
27 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::{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 LeaveAdmin {
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: &LeaveAdmin = 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 // 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"));
486 let person_id = local_user_view.person.id;
487 blocking(context.pool(), move |conn| {
488 Person::leave_admin(conn, person_id)
493 let form = ModAddForm {
494 mod_person_id: person_id,
495 other_person_id: person_id,
499 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
501 // Reread site and admins
502 let site_view = blocking(context.pool(), SiteView::read).await??;
503 let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
505 let federated_instances = build_federated_instances(
507 &context.settings().federation,
508 &context.settings().hostname,
513 site_view: Some(site_view),
516 version: version::VERSION.to_string(),
523 #[async_trait::async_trait(?Send)]
524 impl Perform for GetSiteConfig {
525 type Response = GetSiteConfigResponse;
527 #[tracing::instrument(skip(context, _websocket_id))]
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?;
537 // Only let admins read this
538 is_admin(&local_user_view)?;
540 let config_hjson = Settings::read_config_file()?;
542 Ok(GetSiteConfigResponse { config_hjson })
546 #[async_trait::async_trait(?Send)]
547 impl Perform for SaveSiteConfig {
548 type Response = GetSiteConfigResponse;
550 #[tracing::instrument(skip(context, _websocket_id))]
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?;
560 // Only let admins read this
561 is_admin(&local_user_view)?;
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"))?;
568 Ok(GetSiteConfigResponse { config_hjson })
572 /// Lists registration applications, filterable by undenied only.
573 #[async_trait::async_trait(?Send)]
574 impl Perform for ListRegistrationApplications {
575 type Response = ListRegistrationApplicationsResponse;
579 context: &Data<LemmyContext>,
580 _websocket_id: Option<ConnectionId>,
581 ) -> Result<Self::Response, LemmyError> {
583 let local_user_view =
584 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
586 // Make sure user is an admin
587 is_admin(&local_user_view)?;
589 let unread_only = data.unread_only;
590 let verified_email_only = blocking(context.pool(), Site::read_simple)
592 .require_email_verification;
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)
606 let res = Self::Response {
607 registration_applications,
614 #[async_trait::async_trait(?Send)]
615 impl Perform for ApproveRegistrationApplication {
616 type Response = RegistrationApplicationResponse;
620 context: &Data<LemmyContext>,
621 _websocket_id: Option<ConnectionId>,
622 ) -> Result<Self::Response, LemmyError> {
624 let local_user_view =
625 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
627 let app_id = data.id;
629 // Only let admins do this
630 is_admin(&local_user_view)?;
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),
637 ..RegistrationApplicationForm::default()
640 let registration_application = blocking(context.pool(), move |conn| {
641 RegistrationApplication::update(conn, app_id, &app_form)
645 // Update the local_user row
646 let local_user_form = LocalUserForm {
647 accepted_application: Some(data.approve),
648 ..LocalUserForm::default()
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)
658 let approved_local_user_view = blocking(context.pool(), move |conn| {
659 LocalUserView::read(conn, approved_user_id)
663 if approved_local_user_view.local_user.email.is_some() {
664 send_application_approved_email(&approved_local_user_view, &context.settings())?;
669 let registration_application = blocking(context.pool(), move |conn| {
670 RegistrationApplicationView::read(conn, app_id)
675 registration_application,
680 #[async_trait::async_trait(?Send)]
681 impl Perform for GetUnreadRegistrationApplicationCount {
682 type Response = GetUnreadRegistrationApplicationCountResponse;
686 context: &Data<LemmyContext>,
687 _websocket_id: Option<ConnectionId>,
688 ) -> Result<Self::Response, LemmyError> {
690 let local_user_view =
691 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
693 // Only let admins do this
694 is_admin(&local_user_view)?;
696 let verified_email_only = blocking(context.pool(), Site::read_simple)
698 .require_email_verification;
700 let registration_applications = blocking(context.pool(), move |conn| {
701 RegistrationApplicationView::get_unread_count(conn, verified_email_only)
706 registration_applications,