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_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,
60 use lemmy_utils::{settings::structs::Settings, version, ConnectionId, LemmyError};
61 use lemmy_websocket::LemmyContext;
63 #[async_trait::async_trait(?Send)]
64 impl Perform for GetModlog {
65 type Response = GetModlogResponse;
67 #[tracing::instrument(skip(context, _websocket_id))]
70 context: &Data<LemmyContext>,
71 _websocket_id: Option<ConnectionId>,
72 ) -> Result<GetModlogResponse, LemmyError> {
73 let data: &GetModlog = self;
76 get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
79 check_private_instance(&local_user_view, context.pool()).await?;
81 let community_id = data.community_id;
82 let mod_person_id = data.mod_person_id;
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)
90 let locked_posts = blocking(context.pool(), move |conn| {
91 ModLockPostView::list(conn, community_id, mod_person_id, page, limit)
95 let stickied_posts = blocking(context.pool(), move |conn| {
96 ModStickyPostView::list(conn, community_id, mod_person_id, page, limit)
100 let removed_comments = blocking(context.pool(), move |conn| {
101 ModRemoveCommentView::list(conn, community_id, mod_person_id, page, limit)
105 let banned_from_community = blocking(context.pool(), move |conn| {
106 ModBanFromCommunityView::list(conn, community_id, mod_person_id, page, limit)
110 let added_to_community = blocking(context.pool(), move |conn| {
111 ModAddCommunityView::list(conn, community_id, mod_person_id, page, limit)
115 let transferred_to_community = blocking(context.pool(), move |conn| {
116 ModTransferCommunityView::list(conn, community_id, mod_person_id, page, limit)
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| {
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>
131 (Vec::new(), Vec::new(), Vec::new())
135 Ok(GetModlogResponse {
141 banned_from_community,
145 transferred_to_community,
150 #[async_trait::async_trait(?Send)]
151 impl Perform for Search {
152 type Response = SearchResponse;
154 #[tracing::instrument(skip(context, _websocket_id))]
157 context: &Data<LemmyContext>,
158 _websocket_id: Option<ConnectionId>,
159 ) -> Result<SearchResponse, LemmyError> {
160 let data: &Search = self;
162 let local_user_view =
163 get_local_user_view_from_jwt_opt(data.auth.as_ref(), context.pool(), context.secret())
166 check_private_instance(&local_user_view, context.pool()).await?;
168 let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
169 let show_bot_accounts = local_user_view
171 .map(|t| t.local_user.show_bot_accounts);
172 let show_read_posts = local_user_view
174 .map(|t| t.local_user.show_read_posts);
176 let person_id = local_user_view.map(|u| u.person.id);
178 let mut posts = Vec::new();
179 let mut comments = Vec::new();
180 let mut communities = Vec::new();
181 let mut users = Vec::new();
183 // TODO no clean / non-nsfw searching rn
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())
200 let creator_id = data.creator_id;
202 SearchType::Posts => {
203 posts = blocking(context.pool(), move |conn| {
204 PostQueryBuilder::create(conn)
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)
221 SearchType::Comments => {
222 comments = blocking(context.pool(), move |conn| {
223 CommentQueryBuilder::create(conn)
225 .listing_type(listing_type)
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)
238 SearchType::Communities => {
239 communities = blocking(context.pool(), move |conn| {
240 CommunityQueryBuilder::create(conn)
242 .listing_type(listing_type)
244 .my_person_id(person_id)
251 SearchType::Users => {
252 users = blocking(context.pool(), move |conn| {
253 PersonQueryBuilder::create(conn)
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();
268 posts = blocking(context.pool(), move |conn| {
269 PostQueryBuilder::create(conn)
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)
286 let q = data.q.to_owned();
287 let community_actor_id = community_actor_id.to_owned();
289 comments = blocking(context.pool(), move |conn| {
290 CommentQueryBuilder::create(conn)
292 .listing_type(listing_type)
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)
305 let q = data.q.to_owned();
307 communities = if community_or_creator_included {
310 blocking(context.pool(), move |conn| {
311 CommunityQueryBuilder::create(conn)
313 .listing_type(listing_type)
315 .my_person_id(person_id)
323 let q = data.q.to_owned();
325 users = if community_or_creator_included {
328 blocking(context.pool(), move |conn| {
329 PersonQueryBuilder::create(conn)
340 posts = blocking(context.pool(), move |conn| {
341 PostQueryBuilder::create(conn)
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)
360 // Blank out deleted or removed info for non logged in users
361 if person_id.is_none() {
362 for cv in communities
364 .filter(|cv| cv.community.deleted || cv.community.removed)
366 cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
371 .filter(|p| p.post.deleted || p.post.removed)
373 pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
378 .filter(|cv| cv.comment.deleted || cv.comment.removed)
380 cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
386 type_: search_type.to_string(),
395 #[async_trait::async_trait(?Send)]
396 impl Perform for ResolveObject {
397 type Response = ResolveObjectResponse;
399 #[tracing::instrument(skip(context, _websocket_id))]
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())
408 check_private_instance(&local_user_view, context.pool()).await?;
410 let res = search_by_apub_id(&self.q, context)
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())
416 .map_err(LemmyError::from)
417 .map_err(|e| e.with_message("couldnt_find_object"))
421 async fn convert_response(
422 object: SearchableObjects,
423 user_id: Option<PersonId>,
425 ) -> Result<ResolveObjectResponse, LemmyError> {
426 let removed_or_deleted;
427 let mut res = ResolveObjectResponse {
433 use SearchableObjects::*;
436 removed_or_deleted = p.deleted;
437 res.person = Some(blocking(pool, move |conn| PersonViewSafe::read(conn, p.id)).await??)
440 removed_or_deleted = c.deleted || c.removed;
442 Some(blocking(pool, move |conn| CommunityView::read(conn, c.id, user_id)).await??)
445 removed_or_deleted = p.deleted || p.removed;
446 res.post = Some(blocking(pool, move |conn| PostView::read(conn, p.id, user_id)).await??)
449 removed_or_deleted = c.deleted || c.removed;
450 res.comment = Some(blocking(pool, move |conn| CommentView::read(conn, c.id, user_id)).await??)
453 // if the object was deleted from database, dont return it
454 if removed_or_deleted {
455 return Err(NotFound {}.into());
460 #[async_trait::async_trait(?Send)]
461 impl Perform for LeaveAdmin {
462 type Response = GetSiteResponse;
464 #[tracing::instrument(skip(context, _websocket_id))]
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?;
474 is_admin(&local_user_view)?;
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"));
482 let person_id = local_user_view.person.id;
483 blocking(context.pool(), move |conn| {
484 Person::leave_admin(conn, person_id)
489 let form = ModAddForm {
490 mod_person_id: person_id,
491 other_person_id: person_id,
495 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
497 // Reread site and admins
498 let site_view = blocking(context.pool(), SiteView::read).await??;
499 let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
501 let federated_instances = build_federated_instances(
503 &context.settings().federation,
504 &context.settings().hostname,
509 site_view: Some(site_view),
512 version: version::VERSION.to_string(),
519 #[async_trait::async_trait(?Send)]
520 impl Perform for GetSiteConfig {
521 type Response = GetSiteConfigResponse;
523 #[tracing::instrument(skip(context, _websocket_id))]
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?;
533 // Only let admins read this
534 is_admin(&local_user_view)?;
536 let config_hjson = Settings::read_config_file()?;
538 Ok(GetSiteConfigResponse { config_hjson })
542 #[async_trait::async_trait(?Send)]
543 impl Perform for SaveSiteConfig {
544 type Response = GetSiteConfigResponse;
546 #[tracing::instrument(skip(context, _websocket_id))]
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?;
556 // Only let admins read this
557 is_admin(&local_user_view)?;
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"))?;
564 Ok(GetSiteConfigResponse { config_hjson })
568 /// Lists registration applications, filterable by undenied only.
569 #[async_trait::async_trait(?Send)]
570 impl Perform for ListRegistrationApplications {
571 type Response = ListRegistrationApplicationsResponse;
575 context: &Data<LemmyContext>,
576 _websocket_id: Option<ConnectionId>,
577 ) -> Result<Self::Response, LemmyError> {
579 let local_user_view =
580 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
582 // Make sure user is an admin
583 is_admin(&local_user_view)?;
585 let unread_only = data.unread_only;
586 let verified_email_only = blocking(context.pool(), Site::read_simple)
588 .require_email_verification;
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)
602 let res = Self::Response {
603 registration_applications,
610 #[async_trait::async_trait(?Send)]
611 impl Perform for ApproveRegistrationApplication {
612 type Response = RegistrationApplicationResponse;
616 context: &Data<LemmyContext>,
617 _websocket_id: Option<ConnectionId>,
618 ) -> Result<Self::Response, LemmyError> {
620 let local_user_view =
621 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
623 let app_id = data.id;
625 // Only let admins do this
626 is_admin(&local_user_view)?;
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),
633 ..RegistrationApplicationForm::default()
636 let registration_application = blocking(context.pool(), move |conn| {
637 RegistrationApplication::update(conn, app_id, &app_form)
641 // Update the local_user row
642 let local_user_form = LocalUserForm {
643 accepted_application: Some(data.approve),
644 ..LocalUserForm::default()
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)
654 let approved_local_user_view = blocking(context.pool(), move |conn| {
655 LocalUserView::read(conn, approved_user_id)
659 if approved_local_user_view.local_user.email.is_some() {
660 send_application_approved_email(&approved_local_user_view, &context.settings())?;
665 let registration_application = blocking(context.pool(), move |conn| {
666 RegistrationApplicationView::read(conn, app_id)
671 registration_application,
676 #[async_trait::async_trait(?Send)]
677 impl Perform for GetUnreadRegistrationApplicationCount {
678 type Response = GetUnreadRegistrationApplicationCountResponse;
682 context: &Data<LemmyContext>,
683 _websocket_id: Option<ConnectionId>,
684 ) -> Result<Self::Response, LemmyError> {
686 let local_user_view =
687 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
689 // Only let admins do this
690 is_admin(&local_user_view)?;
692 let verified_email_only = blocking(context.pool(), Site::read_simple)
694 .require_email_verification;
696 let registration_applications = blocking(context.pool(), move |conn| {
697 RegistrationApplicationView::get_unread_count(conn, verified_email_only)
702 registration_applications,