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(|e| e.with_message("couldnt_find_object"))?;
420 convert_response(res, local_user_view.map(|l| l.person.id), context.pool())
422 .map_err(|e| e.with_message("couldnt_find_object"))
426 async fn convert_response(
427 object: SearchableObjects,
428 user_id: Option<PersonId>,
430 ) -> Result<ResolveObjectResponse, LemmyError> {
431 let removed_or_deleted;
432 let mut res = ResolveObjectResponse {
438 use SearchableObjects::*;
441 removed_or_deleted = p.deleted;
442 res.person = Some(blocking(pool, move |conn| PersonViewSafe::read(conn, p.id)).await??)
445 removed_or_deleted = c.deleted || c.removed;
447 Some(blocking(pool, move |conn| CommunityView::read(conn, c.id, user_id)).await??)
450 removed_or_deleted = p.deleted || p.removed;
451 res.post = Some(blocking(pool, move |conn| PostView::read(conn, p.id, user_id)).await??)
454 removed_or_deleted = c.deleted || c.removed;
455 res.comment = Some(blocking(pool, move |conn| CommentView::read(conn, c.id, user_id)).await??)
458 // if the object was deleted from database, dont return it
459 if removed_or_deleted {
460 return Err(NotFound {}.into());
465 #[async_trait::async_trait(?Send)]
466 impl Perform for LeaveAdmin {
467 type Response = GetSiteResponse;
469 #[tracing::instrument(skip(context, _websocket_id))]
472 context: &Data<LemmyContext>,
473 _websocket_id: Option<ConnectionId>,
474 ) -> Result<GetSiteResponse, LemmyError> {
475 let data: &LeaveAdmin = self;
476 let local_user_view =
477 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
479 is_admin(&local_user_view)?;
481 // Make sure there isn't just one admin (so if one leaves, there will still be one left)
482 let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
483 if admins.len() == 1 {
484 return Err(LemmyError::from_message("cannot_leave_admin"));
487 let person_id = local_user_view.person.id;
488 blocking(context.pool(), move |conn| {
489 Person::leave_admin(conn, person_id)
494 let form = ModAddForm {
495 mod_person_id: person_id,
496 other_person_id: person_id,
500 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
502 // Reread site and admins
503 let site_view = blocking(context.pool(), SiteView::read_local).await??;
504 let admins = blocking(context.pool(), PersonViewSafe::admins).await??;
506 let federated_instances = build_federated_instances(
508 &context.settings().federation,
509 &context.settings().hostname,
514 site_view: Some(site_view),
517 version: version::VERSION.to_string(),
524 #[async_trait::async_trait(?Send)]
525 impl Perform for GetSiteConfig {
526 type Response = GetSiteConfigResponse;
528 #[tracing::instrument(skip(context, _websocket_id))]
531 context: &Data<LemmyContext>,
532 _websocket_id: Option<ConnectionId>,
533 ) -> Result<GetSiteConfigResponse, LemmyError> {
534 let data: &GetSiteConfig = self;
535 let local_user_view =
536 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
538 // Only let admins read this
539 is_admin(&local_user_view)?;
541 let config_hjson = Settings::read_config_file()?;
543 Ok(GetSiteConfigResponse { config_hjson })
547 #[async_trait::async_trait(?Send)]
548 impl Perform for SaveSiteConfig {
549 type Response = GetSiteConfigResponse;
551 #[tracing::instrument(skip(context, _websocket_id))]
554 context: &Data<LemmyContext>,
555 _websocket_id: Option<ConnectionId>,
556 ) -> Result<GetSiteConfigResponse, LemmyError> {
557 let data: &SaveSiteConfig = self;
558 let local_user_view =
559 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
561 // Only let admins read this
562 is_admin(&local_user_view)?;
564 // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
565 let config_hjson = Settings::save_config_file(&data.config_hjson)
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_local_site)
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_local_site)
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,