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 resolve_actor_identifier,
12 send_application_approved_email,
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,
22 local_user::{LocalUser, LocalUserForm},
25 registration_application::{RegistrationApplication, RegistrationApplicationForm},
28 traits::{Crud, DeleteableOrRemoveable},
35 comment_view::{CommentQueryBuilder, CommentView},
36 local_user_view::LocalUserView,
37 post_view::{PostQueryBuilder, PostView},
38 registration_application_view::{
39 RegistrationApplicationQueryBuilder,
40 RegistrationApplicationView,
44 use lemmy_db_views_actor::{
45 community_view::{CommunityQueryBuilder, CommunityView},
46 person_view::{PersonQueryBuilder, PersonViewSafe},
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_hide_community_view::ModHideCommunityView,
54 mod_lock_post_view::ModLockPostView,
55 mod_remove_comment_view::ModRemoveCommentView,
56 mod_remove_community_view::ModRemoveCommunityView,
57 mod_remove_post_view::ModRemovePostView,
58 mod_sticky_post_view::ModStickyPostView,
59 mod_transfer_community_view::ModTransferCommunityView,
61 use lemmy_utils::{settings::structs::Settings, version, ConnectionId, LemmyError};
62 use lemmy_websocket::LemmyContext;
64 #[async_trait::async_trait(?Send)]
65 impl Perform for GetModlog {
66 type Response = GetModlogResponse;
68 #[tracing::instrument(skip(context, _websocket_id))]
71 context: &Data<LemmyContext>,
72 _websocket_id: Option<ConnectionId>,
73 ) -> Result<GetModlogResponse, LemmyError> {
74 let data: &GetModlog = self;
77 get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
80 check_private_instance(&local_user_view, context.pool()).await?;
82 let community_id = data.community_id;
83 let mod_person_id = data.mod_person_id;
85 let limit = data.limit;
86 let removed_posts = blocking(context.pool(), move |conn| {
87 ModRemovePostView::list(conn, community_id, mod_person_id, page, limit)
91 let locked_posts = blocking(context.pool(), move |conn| {
92 ModLockPostView::list(conn, community_id, mod_person_id, page, limit)
96 let stickied_posts = blocking(context.pool(), move |conn| {
97 ModStickyPostView::list(conn, community_id, mod_person_id, page, limit)
101 let removed_comments = blocking(context.pool(), move |conn| {
102 ModRemoveCommentView::list(conn, community_id, mod_person_id, page, limit)
106 let banned_from_community = blocking(context.pool(), move |conn| {
107 ModBanFromCommunityView::list(conn, community_id, mod_person_id, page, limit)
111 let added_to_community = blocking(context.pool(), move |conn| {
112 ModAddCommunityView::list(conn, community_id, mod_person_id, page, limit)
116 let transferred_to_community = blocking(context.pool(), move |conn| {
117 ModTransferCommunityView::list(conn, community_id, mod_person_id, page, limit)
121 let hidden_communities = blocking(context.pool(), move |conn| {
122 ModHideCommunityView::list(conn, community_id, mod_person_id, page, limit)
126 // These arrays are only for the full modlog, when a community isn't given
127 let (removed_communities, banned, added) = if data.community_id.is_none() {
128 blocking(context.pool(), move |conn| {
130 ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?,
131 ModBanView::list(conn, mod_person_id, page, limit)?,
132 ModAddView::list(conn, mod_person_id, page, limit)?,
133 )) as Result<_, LemmyError>
137 (Vec::new(), Vec::new(), Vec::new())
141 Ok(GetModlogResponse {
147 banned_from_community,
151 transferred_to_community,
157 #[async_trait::async_trait(?Send)]
158 impl Perform for Search {
159 type Response = SearchResponse;
161 #[tracing::instrument(skip(context, _websocket_id))]
164 context: &Data<LemmyContext>,
165 _websocket_id: Option<ConnectionId>,
166 ) -> Result<SearchResponse, LemmyError> {
167 let data: &Search = self;
169 let local_user_view =
170 get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
173 check_private_instance(&local_user_view, context.pool()).await?;
175 let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
176 let show_bot_accounts = local_user_view
178 .map(|t| t.local_user.show_bot_accounts);
179 let show_read_posts = local_user_view
181 .map(|t| t.local_user.show_read_posts);
183 let person_id = local_user_view.map(|u| u.person.id);
185 let mut posts = Vec::new();
186 let mut comments = Vec::new();
187 let mut communities = Vec::new();
188 let mut users = Vec::new();
190 // TODO no clean / non-nsfw searching rn
192 let q = data.q.to_owned();
193 let page = data.page;
194 let limit = data.limit;
195 let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
196 let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.listing_type);
197 let search_type: SearchType = from_opt_str_to_opt_enum(&data.type_).unwrap_or(SearchType::All);
198 let community_id = data.community_id;
199 let community_actor_id = if let Some(name) = &data.community_name {
200 resolve_actor_identifier::<Community>(name, context.pool())
207 let creator_id = data.creator_id;
209 SearchType::Posts => {
210 posts = blocking(context.pool(), move |conn| {
211 PostQueryBuilder::create(conn)
213 .show_nsfw(show_nsfw)
214 .show_bot_accounts(show_bot_accounts)
215 .show_read_posts(show_read_posts)
216 .listing_type(listing_type)
217 .community_id(community_id)
218 .community_actor_id(community_actor_id)
219 .creator_id(creator_id)
220 .my_person_id(person_id)
228 SearchType::Comments => {
229 comments = blocking(context.pool(), move |conn| {
230 CommentQueryBuilder::create(conn)
232 .listing_type(listing_type)
234 .show_bot_accounts(show_bot_accounts)
235 .community_id(community_id)
236 .community_actor_id(community_actor_id)
237 .creator_id(creator_id)
238 .my_person_id(person_id)
245 SearchType::Communities => {
246 communities = blocking(context.pool(), move |conn| {
247 CommunityQueryBuilder::create(conn)
249 .listing_type(listing_type)
251 .my_person_id(person_id)
258 SearchType::Users => {
259 users = blocking(context.pool(), move |conn| {
260 PersonQueryBuilder::create(conn)
270 // If the community or creator is included, dont search communities or users
271 let community_or_creator_included =
272 data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some();
273 let community_actor_id_2 = community_actor_id.to_owned();
275 posts = blocking(context.pool(), move |conn| {
276 PostQueryBuilder::create(conn)
278 .show_nsfw(show_nsfw)
279 .show_bot_accounts(show_bot_accounts)
280 .show_read_posts(show_read_posts)
281 .listing_type(listing_type)
282 .community_id(community_id)
283 .community_actor_id(community_actor_id_2)
284 .creator_id(creator_id)
285 .my_person_id(person_id)
293 let q = data.q.to_owned();
294 let community_actor_id = community_actor_id.to_owned();
296 comments = blocking(context.pool(), move |conn| {
297 CommentQueryBuilder::create(conn)
299 .listing_type(listing_type)
301 .show_bot_accounts(show_bot_accounts)
302 .community_id(community_id)
303 .community_actor_id(community_actor_id)
304 .creator_id(creator_id)
305 .my_person_id(person_id)
312 let q = data.q.to_owned();
314 communities = if community_or_creator_included {
317 blocking(context.pool(), move |conn| {
318 CommunityQueryBuilder::create(conn)
320 .listing_type(listing_type)
322 .my_person_id(person_id)
330 let q = data.q.to_owned();
332 users = if community_or_creator_included {
335 blocking(context.pool(), move |conn| {
336 PersonQueryBuilder::create(conn)
347 posts = blocking(context.pool(), move |conn| {
348 PostQueryBuilder::create(conn)
350 .show_nsfw(show_nsfw)
351 .show_bot_accounts(show_bot_accounts)
352 .show_read_posts(show_read_posts)
353 .listing_type(listing_type)
354 .my_person_id(person_id)
355 .community_id(community_id)
356 .community_actor_id(community_actor_id)
357 .creator_id(creator_id)
367 // Blank out deleted or removed info for non logged in users
368 if person_id.is_none() {
369 for cv in communities
371 .filter(|cv| cv.community.deleted || cv.community.removed)
373 cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
378 .filter(|p| p.post.deleted || p.post.removed)
380 pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
385 .filter(|cv| cv.comment.deleted || cv.comment.removed)
387 cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
393 type_: search_type.to_string(),
402 #[async_trait::async_trait(?Send)]
403 impl Perform for ResolveObject {
404 type Response = ResolveObjectResponse;
406 #[tracing::instrument(skip(context, _websocket_id))]
409 context: &Data<LemmyContext>,
410 _websocket_id: Option<ConnectionId>,
411 ) -> Result<ResolveObjectResponse, LemmyError> {
412 let local_user_view =
413 get_local_user_view_from_jwt_opt(self.auth.as_ref(), context.pool(), context.secret())
415 check_private_instance(&local_user_view, context.pool()).await?;
417 let res = search_by_apub_id(&self.q, context)
419 .map_err(LemmyError::from)
420 .map_err(|e| e.with_message("couldnt_find_object"))?;
421 convert_response(res, local_user_view.map(|l| l.person.id), context.pool())
423 .map_err(LemmyError::from)
424 .map_err(|e| e.with_message("couldnt_find_object"))
428 async fn convert_response(
429 object: SearchableObjects,
430 user_id: Option<PersonId>,
432 ) -> Result<ResolveObjectResponse, LemmyError> {
433 let removed_or_deleted;
434 let mut res = ResolveObjectResponse {
440 use SearchableObjects::*;
443 removed_or_deleted = p.deleted;
444 res.person = Some(blocking(pool, move |conn| PersonViewSafe::read(conn, p.id)).await??)
447 removed_or_deleted = c.deleted || c.removed;
449 Some(blocking(pool, move |conn| CommunityView::read(conn, c.id, user_id)).await??)
452 removed_or_deleted = p.deleted || p.removed;
453 res.post = Some(blocking(pool, move |conn| PostView::read(conn, p.id, user_id)).await??)
456 removed_or_deleted = c.deleted || c.removed;
457 res.comment = Some(blocking(pool, move |conn| CommentView::read(conn, c.id, user_id)).await??)
460 // if the object was deleted from database, dont return it
461 if removed_or_deleted {
462 return Err(NotFound {}.into());
467 #[async_trait::async_trait(?Send)]
468 impl Perform for LeaveAdmin {
469 type Response = GetSiteResponse;
471 #[tracing::instrument(skip(context, _websocket_id))]
474 context: &Data<LemmyContext>,
475 _websocket_id: Option<ConnectionId>,
476 ) -> Result<GetSiteResponse, LemmyError> {
477 let data: &LeaveAdmin = self;
478 let local_user_view =
479 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
481 is_admin(&local_user_view)?;
483 // Make sure there isn't just one admin (so if one leaves, there will still be one left)
484 let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
485 if admins.len() == 1 {
486 return Err(LemmyError::from_message("cannot_leave_admin"));
489 let person_id = local_user_view.person.id;
490 blocking(context.pool(), move |conn| {
491 Person::leave_admin(conn, person_id)
496 let form = ModAddForm {
497 mod_person_id: person_id,
498 other_person_id: person_id,
502 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
504 // Reread site and admins
505 let site_view = blocking(context.pool(), SiteView::read_local).await??;
506 let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
508 let federated_instances = build_federated_instances(
510 &context.settings().federation,
511 &context.settings().hostname,
516 site_view: Some(site_view),
519 version: version::VERSION.to_string(),
526 #[async_trait::async_trait(?Send)]
527 impl Perform for GetSiteConfig {
528 type Response = GetSiteConfigResponse;
530 #[tracing::instrument(skip(context, _websocket_id))]
533 context: &Data<LemmyContext>,
534 _websocket_id: Option<ConnectionId>,
535 ) -> Result<GetSiteConfigResponse, LemmyError> {
536 let data: &GetSiteConfig = self;
537 let local_user_view =
538 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
540 // Only let admins read this
541 is_admin(&local_user_view)?;
543 let config_hjson = Settings::read_config_file()?;
545 Ok(GetSiteConfigResponse { config_hjson })
549 #[async_trait::async_trait(?Send)]
550 impl Perform for SaveSiteConfig {
551 type Response = GetSiteConfigResponse;
553 #[tracing::instrument(skip(context, _websocket_id))]
556 context: &Data<LemmyContext>,
557 _websocket_id: Option<ConnectionId>,
558 ) -> Result<GetSiteConfigResponse, LemmyError> {
559 let data: &SaveSiteConfig = self;
560 let local_user_view =
561 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
563 // Only let admins read this
564 is_admin(&local_user_view)?;
566 // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
567 let config_hjson = Settings::save_config_file(&data.config_hjson)
568 .map_err(LemmyError::from)
569 .map_err(|e| e.with_message("couldnt_update_site"))?;
571 Ok(GetSiteConfigResponse { config_hjson })
575 /// Lists registration applications, filterable by undenied only.
576 #[async_trait::async_trait(?Send)]
577 impl Perform for ListRegistrationApplications {
578 type Response = ListRegistrationApplicationsResponse;
582 context: &Data<LemmyContext>,
583 _websocket_id: Option<ConnectionId>,
584 ) -> Result<Self::Response, LemmyError> {
586 let local_user_view =
587 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
589 // Make sure user is an admin
590 is_admin(&local_user_view)?;
592 let unread_only = data.unread_only;
593 let verified_email_only = blocking(context.pool(), Site::read_local_site)
595 .require_email_verification;
597 let page = data.page;
598 let limit = data.limit;
599 let registration_applications = blocking(context.pool(), move |conn| {
600 RegistrationApplicationQueryBuilder::create(conn)
601 .unread_only(unread_only)
602 .verified_email_only(verified_email_only)
609 let res = Self::Response {
610 registration_applications,
617 #[async_trait::async_trait(?Send)]
618 impl Perform for ApproveRegistrationApplication {
619 type Response = RegistrationApplicationResponse;
623 context: &Data<LemmyContext>,
624 _websocket_id: Option<ConnectionId>,
625 ) -> Result<Self::Response, LemmyError> {
627 let local_user_view =
628 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
630 let app_id = data.id;
632 // Only let admins do this
633 is_admin(&local_user_view)?;
635 // Update the registration with reason, admin_id
636 let deny_reason = diesel_option_overwrite(&data.deny_reason);
637 let app_form = RegistrationApplicationForm {
638 admin_id: Some(local_user_view.person.id),
640 ..RegistrationApplicationForm::default()
643 let registration_application = blocking(context.pool(), move |conn| {
644 RegistrationApplication::update(conn, app_id, &app_form)
648 // Update the local_user row
649 let local_user_form = LocalUserForm {
650 accepted_application: Some(data.approve),
651 ..LocalUserForm::default()
654 let approved_user_id = registration_application.local_user_id;
655 blocking(context.pool(), move |conn| {
656 LocalUser::update(conn, approved_user_id, &local_user_form)
661 let approved_local_user_view = blocking(context.pool(), move |conn| {
662 LocalUserView::read(conn, approved_user_id)
666 if approved_local_user_view.local_user.email.is_some() {
667 send_application_approved_email(&approved_local_user_view, &context.settings())?;
672 let registration_application = blocking(context.pool(), move |conn| {
673 RegistrationApplicationView::read(conn, app_id)
678 registration_application,
683 #[async_trait::async_trait(?Send)]
684 impl Perform for GetUnreadRegistrationApplicationCount {
685 type Response = GetUnreadRegistrationApplicationCountResponse;
689 context: &Data<LemmyContext>,
690 _websocket_id: Option<ConnectionId>,
691 ) -> Result<Self::Response, LemmyError> {
693 let local_user_view =
694 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
696 // Only let admins do this
697 is_admin(&local_user_view)?;
699 let verified_email_only = blocking(context.pool(), Site::read_local_site)
701 .require_email_verification;
703 let registration_applications = blocking(context.pool(), move |conn| {
704 RegistrationApplicationView::get_unread_count(conn, verified_email_only)
709 registration_applications,