2 use actix_web::web::Data;
5 use lemmy_api_common::{
7 build_federated_instances,
8 get_local_user_view_from_jwt,
9 get_local_user_view_from_jwt_opt,
15 search::{search_by_apub_id, SearchableObjects},
16 webfinger::webfinger_resolve,
18 objects::community::ApubCommunity,
21 use lemmy_db_schema::{
22 from_opt_str_to_opt_enum,
24 source::{moderator::*, site::Site},
25 traits::{Crud, DeleteableOrRemoveable},
32 comment_view::{CommentQueryBuilder, CommentView},
33 post_view::{PostQueryBuilder, PostView},
36 use lemmy_db_views_actor::{
37 community_view::{CommunityQueryBuilder, CommunityView},
38 person_view::{PersonQueryBuilder, PersonViewSafe},
40 use lemmy_db_views_moderator::{
41 mod_add_community_view::ModAddCommunityView,
42 mod_add_view::ModAddView,
43 mod_ban_from_community_view::ModBanFromCommunityView,
44 mod_ban_view::ModBanView,
45 mod_lock_post_view::ModLockPostView,
46 mod_remove_comment_view::ModRemoveCommentView,
47 mod_remove_community_view::ModRemoveCommunityView,
48 mod_remove_post_view::ModRemovePostView,
49 mod_sticky_post_view::ModStickyPostView,
50 mod_transfer_community_view::ModTransferCommunityView,
54 settings::structs::Settings,
60 use lemmy_websocket::LemmyContext;
62 #[async_trait::async_trait(?Send)]
63 impl Perform for GetModlog {
64 type Response = GetModlogResponse;
68 context: &Data<LemmyContext>,
69 _websocket_id: Option<ConnectionId>,
70 ) -> Result<GetModlogResponse, LemmyError> {
71 let data: &GetModlog = self;
73 let community_id = data.community_id;
74 let mod_person_id = data.mod_person_id;
76 let limit = data.limit;
77 let removed_posts = blocking(context.pool(), move |conn| {
78 ModRemovePostView::list(conn, community_id, mod_person_id, page, limit)
82 let locked_posts = blocking(context.pool(), move |conn| {
83 ModLockPostView::list(conn, community_id, mod_person_id, page, limit)
87 let stickied_posts = blocking(context.pool(), move |conn| {
88 ModStickyPostView::list(conn, community_id, mod_person_id, page, limit)
92 let removed_comments = blocking(context.pool(), move |conn| {
93 ModRemoveCommentView::list(conn, community_id, mod_person_id, page, limit)
97 let banned_from_community = blocking(context.pool(), move |conn| {
98 ModBanFromCommunityView::list(conn, community_id, mod_person_id, page, limit)
102 let added_to_community = blocking(context.pool(), move |conn| {
103 ModAddCommunityView::list(conn, community_id, mod_person_id, page, limit)
107 let transferred_to_community = blocking(context.pool(), move |conn| {
108 ModTransferCommunityView::list(conn, community_id, mod_person_id, page, limit)
112 // These arrays are only for the full modlog, when a community isn't given
113 let (removed_communities, banned, added) = if data.community_id.is_none() {
114 blocking(context.pool(), move |conn| {
116 ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?,
117 ModBanView::list(conn, mod_person_id, page, limit)?,
118 ModAddView::list(conn, mod_person_id, page, limit)?,
119 )) as Result<_, LemmyError>
123 (Vec::new(), Vec::new(), Vec::new())
127 Ok(GetModlogResponse {
133 banned_from_community,
137 transferred_to_community,
142 #[async_trait::async_trait(?Send)]
143 impl Perform for Search {
144 type Response = SearchResponse;
148 context: &Data<LemmyContext>,
149 _websocket_id: Option<ConnectionId>,
150 ) -> Result<SearchResponse, LemmyError> {
151 let data: &Search = self;
153 let local_user_view =
154 get_local_user_view_from_jwt_opt(&data.auth, context.pool(), context.secret()).await?;
156 let show_nsfw = local_user_view.as_ref().map(|t| t.local_user.show_nsfw);
157 let show_bot_accounts = local_user_view
159 .map(|t| t.local_user.show_bot_accounts);
160 let show_read_posts = local_user_view
162 .map(|t| t.local_user.show_read_posts);
164 let person_id = local_user_view.map(|u| u.person.id);
166 let mut posts = Vec::new();
167 let mut comments = Vec::new();
168 let mut communities = Vec::new();
169 let mut users = Vec::new();
171 // TODO no clean / non-nsfw searching rn
173 let q = data.q.to_owned();
174 let page = data.page;
175 let limit = data.limit;
176 let sort: Option<SortType> = from_opt_str_to_opt_enum(&data.sort);
177 let listing_type: Option<ListingType> = from_opt_str_to_opt_enum(&data.listing_type);
178 let search_type: SearchType = from_opt_str_to_opt_enum(&data.type_).unwrap_or(SearchType::All);
179 let community_id = data.community_id;
180 let community_actor_id = if let Some(name) = &data.community_name {
181 webfinger_resolve::<ApubCommunity>(name, EndpointType::Community, context, &mut 0)
187 let creator_id = data.creator_id;
189 SearchType::Posts => {
190 posts = blocking(context.pool(), move |conn| {
191 PostQueryBuilder::create(conn)
193 .show_nsfw(show_nsfw)
194 .show_bot_accounts(show_bot_accounts)
195 .show_read_posts(show_read_posts)
196 .listing_type(listing_type)
197 .community_id(community_id)
198 .community_actor_id(community_actor_id)
199 .creator_id(creator_id)
200 .my_person_id(person_id)
208 SearchType::Comments => {
209 comments = blocking(context.pool(), move |conn| {
210 CommentQueryBuilder::create(conn)
212 .listing_type(listing_type)
214 .show_bot_accounts(show_bot_accounts)
215 .community_id(community_id)
216 .community_actor_id(community_actor_id)
217 .creator_id(creator_id)
218 .my_person_id(person_id)
225 SearchType::Communities => {
226 communities = blocking(context.pool(), move |conn| {
227 CommunityQueryBuilder::create(conn)
229 .listing_type(listing_type)
231 .my_person_id(person_id)
238 SearchType::Users => {
239 users = blocking(context.pool(), move |conn| {
240 PersonQueryBuilder::create(conn)
250 // If the community or creator is included, dont search communities or users
251 let community_or_creator_included =
252 data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some();
253 let community_actor_id_2 = community_actor_id.to_owned();
255 posts = blocking(context.pool(), move |conn| {
256 PostQueryBuilder::create(conn)
258 .show_nsfw(show_nsfw)
259 .show_bot_accounts(show_bot_accounts)
260 .show_read_posts(show_read_posts)
261 .listing_type(listing_type)
262 .community_id(community_id)
263 .community_actor_id(community_actor_id_2)
264 .creator_id(creator_id)
265 .my_person_id(person_id)
273 let q = data.q.to_owned();
274 let community_actor_id = community_actor_id.to_owned();
276 comments = blocking(context.pool(), move |conn| {
277 CommentQueryBuilder::create(conn)
279 .listing_type(listing_type)
281 .show_bot_accounts(show_bot_accounts)
282 .community_id(community_id)
283 .community_actor_id(community_actor_id)
284 .creator_id(creator_id)
285 .my_person_id(person_id)
292 let q = data.q.to_owned();
294 communities = if community_or_creator_included {
297 blocking(context.pool(), move |conn| {
298 CommunityQueryBuilder::create(conn)
300 .listing_type(listing_type)
302 .my_person_id(person_id)
310 let q = data.q.to_owned();
312 users = if community_or_creator_included {
315 blocking(context.pool(), move |conn| {
316 PersonQueryBuilder::create(conn)
327 posts = blocking(context.pool(), move |conn| {
328 PostQueryBuilder::create(conn)
330 .show_nsfw(show_nsfw)
331 .show_bot_accounts(show_bot_accounts)
332 .show_read_posts(show_read_posts)
333 .listing_type(listing_type)
334 .my_person_id(person_id)
335 .community_id(community_id)
336 .community_actor_id(community_actor_id)
337 .creator_id(creator_id)
347 // Blank out deleted or removed info for non logged in users
348 if person_id.is_none() {
349 for cv in communities
351 .filter(|cv| cv.community.deleted || cv.community.removed)
353 cv.community = cv.to_owned().community.blank_out_deleted_or_removed_info();
358 .filter(|p| p.post.deleted || p.post.removed)
360 pv.post = pv.to_owned().post.blank_out_deleted_or_removed_info();
365 .filter(|cv| cv.comment.deleted || cv.comment.removed)
367 cv.comment = cv.to_owned().comment.blank_out_deleted_or_removed_info();
373 type_: search_type.to_string(),
382 #[async_trait::async_trait(?Send)]
383 impl Perform for ResolveObject {
384 type Response = ResolveObjectResponse;
388 context: &Data<LemmyContext>,
389 _websocket_id: Option<ConnectionId>,
390 ) -> Result<ResolveObjectResponse, LemmyError> {
391 let local_user_view =
392 get_local_user_view_from_jwt_opt(&self.auth, context.pool(), context.secret()).await?;
393 let res = search_by_apub_id(&self.q, context)
395 .map_err(|e| ApiError::err("couldnt_find_object", e))?;
396 convert_response(res, local_user_view.map(|l| l.person.id), context.pool())
398 .map_err(|e| ApiError::err("couldnt_find_object", e).into())
402 async fn convert_response(
403 object: SearchableObjects,
404 user_id: Option<PersonId>,
406 ) -> Result<ResolveObjectResponse, LemmyError> {
407 let removed_or_deleted;
408 let mut res = ResolveObjectResponse {
414 use SearchableObjects::*;
417 removed_or_deleted = p.deleted;
418 res.person = Some(blocking(pool, move |conn| PersonViewSafe::read(conn, p.id)).await??)
421 removed_or_deleted = c.deleted || c.removed;
423 Some(blocking(pool, move |conn| CommunityView::read(conn, c.id, user_id)).await??)
426 removed_or_deleted = p.deleted || p.removed;
427 res.post = Some(blocking(pool, move |conn| PostView::read(conn, p.id, user_id)).await??)
430 removed_or_deleted = c.deleted || c.removed;
431 res.comment = Some(blocking(pool, move |conn| CommentView::read(conn, c.id, user_id)).await??)
434 // if the object was deleted from database, dont return it
435 if removed_or_deleted {
436 return Err(NotFound {}.into());
441 #[async_trait::async_trait(?Send)]
442 impl Perform for TransferSite {
443 type Response = GetSiteResponse;
447 context: &Data<LemmyContext>,
448 _websocket_id: Option<ConnectionId>,
449 ) -> Result<GetSiteResponse, LemmyError> {
450 let data: &TransferSite = self;
451 let local_user_view =
452 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
454 is_admin(&local_user_view)?;
456 let read_site = blocking(context.pool(), Site::read_simple).await??;
458 // Make sure user is the creator
459 if read_site.creator_id != local_user_view.person.id {
460 return Err(ApiError::err_plain("not_an_admin").into());
463 let new_creator_id = data.person_id;
464 let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
465 blocking(context.pool(), transfer_site)
467 .map_err(|e| ApiError::err("couldnt_update_site", e))?;
470 let form = ModAddForm {
471 mod_person_id: local_user_view.person.id,
472 other_person_id: data.person_id,
473 removed: Some(false),
476 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
478 let site_view = blocking(context.pool(), SiteView::read).await??;
480 let mut admins = blocking(context.pool(), PersonViewSafe::admins).await??;
481 let creator_index = admins
483 .position(|r| r.person.id == site_view.creator.id)
484 .context(location_info!())?;
485 let creator_person = admins.remove(creator_index);
486 admins.insert(0, creator_person);
488 let banned = blocking(context.pool(), PersonViewSafe::banned).await??;
489 let federated_instances = build_federated_instances(
491 &context.settings().federation,
492 &context.settings().hostname,
497 site_view: Some(site_view),
501 version: version::VERSION.to_string(),
508 #[async_trait::async_trait(?Send)]
509 impl Perform for GetSiteConfig {
510 type Response = GetSiteConfigResponse;
514 context: &Data<LemmyContext>,
515 _websocket_id: Option<ConnectionId>,
516 ) -> Result<GetSiteConfigResponse, LemmyError> {
517 let data: &GetSiteConfig = self;
518 let local_user_view =
519 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
521 // Only let admins read this
522 is_admin(&local_user_view)?;
524 let config_hjson = Settings::read_config_file()?;
526 Ok(GetSiteConfigResponse { config_hjson })
530 #[async_trait::async_trait(?Send)]
531 impl Perform for SaveSiteConfig {
532 type Response = GetSiteConfigResponse;
536 context: &Data<LemmyContext>,
537 _websocket_id: Option<ConnectionId>,
538 ) -> Result<GetSiteConfigResponse, LemmyError> {
539 let data: &SaveSiteConfig = self;
540 let local_user_view =
541 get_local_user_view_from_jwt(&data.auth, context.pool(), context.secret()).await?;
543 // Only let admins read this
544 is_admin(&local_user_view)?;
546 // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
547 let config_hjson = Settings::save_config_file(&data.config_hjson)
548 .map_err(|e| ApiError::err("couldnt_update_site", e))?;
550 Ok(GetSiteConfigResponse { config_hjson })