]> Untitled Git - lemmy.git/blob - lemmy_api/src/site.rs
remove timing files added by accident
[lemmy.git] / lemmy_api / src / site.rs
1 use crate::{
2   get_user_from_jwt,
3   get_user_from_jwt_opt,
4   is_admin,
5   linked_instances,
6   version,
7   Perform,
8 };
9 use actix_web::web::Data;
10 use anyhow::Context;
11 use lemmy_apub::fetcher::search_by_apub_id;
12 use lemmy_db::{
13   aggregates::site_aggregates::SiteAggregates,
14   diesel_option_overwrite,
15   source::{category::Category_, site::Site_},
16   views::{
17     comment_view::CommentQueryBuilder,
18     community::community_view::CommunityQueryBuilder,
19     moderator::{
20       mod_add_community_view::ModAddCommunityView,
21       mod_add_view::ModAddView,
22       mod_ban_from_community_view::ModBanFromCommunityView,
23       mod_ban_view::ModBanView,
24       mod_lock_post_view::ModLockPostView,
25       mod_remove_comment_view::ModRemoveCommentView,
26       mod_remove_community_view::ModRemoveCommunityView,
27       mod_remove_post_view::ModRemovePostView,
28       mod_sticky_post_view::ModStickyPostView,
29     },
30     post_view::PostQueryBuilder,
31     site_view::SiteView,
32     user_view::{UserQueryBuilder, UserViewSafe},
33   },
34   Crud,
35   SearchType,
36   SortType,
37 };
38 use lemmy_db_schema::{
39   naive_now,
40   source::{
41     category::Category,
42     moderator::*,
43     site::{Site, *},
44   },
45 };
46 use lemmy_structs::{blocking, site::*, user::Register};
47 use lemmy_utils::{
48   location_info,
49   settings::Settings,
50   utils::{check_slurs, check_slurs_opt},
51   APIError,
52   ConnectionId,
53   LemmyError,
54 };
55 use lemmy_websocket::{
56   messages::{GetUsersOnline, SendAllMessage},
57   LemmyContext,
58   UserOperation,
59 };
60 use log::{debug, info};
61 use std::str::FromStr;
62
63 #[async_trait::async_trait(?Send)]
64 impl Perform for ListCategories {
65   type Response = ListCategoriesResponse;
66
67   async fn perform(
68     &self,
69     context: &Data<LemmyContext>,
70     _websocket_id: Option<ConnectionId>,
71   ) -> Result<ListCategoriesResponse, LemmyError> {
72     let _data: &ListCategories = &self;
73
74     let categories = blocking(context.pool(), move |conn| Category::list_all(conn)).await??;
75
76     // Return the jwt
77     Ok(ListCategoriesResponse { categories })
78   }
79 }
80
81 #[async_trait::async_trait(?Send)]
82 impl Perform for GetModlog {
83   type Response = GetModlogResponse;
84
85   async fn perform(
86     &self,
87     context: &Data<LemmyContext>,
88     _websocket_id: Option<ConnectionId>,
89   ) -> Result<GetModlogResponse, LemmyError> {
90     let data: &GetModlog = &self;
91
92     let community_id = data.community_id;
93     let mod_user_id = data.mod_user_id;
94     let page = data.page;
95     let limit = data.limit;
96     let removed_posts = blocking(context.pool(), move |conn| {
97       ModRemovePostView::list(conn, community_id, mod_user_id, page, limit)
98     })
99     .await??;
100
101     let locked_posts = blocking(context.pool(), move |conn| {
102       ModLockPostView::list(conn, community_id, mod_user_id, page, limit)
103     })
104     .await??;
105
106     let stickied_posts = blocking(context.pool(), move |conn| {
107       ModStickyPostView::list(conn, community_id, mod_user_id, page, limit)
108     })
109     .await??;
110
111     let removed_comments = blocking(context.pool(), move |conn| {
112       ModRemoveCommentView::list(conn, community_id, mod_user_id, page, limit)
113     })
114     .await??;
115
116     let banned_from_community = blocking(context.pool(), move |conn| {
117       ModBanFromCommunityView::list(conn, community_id, mod_user_id, page, limit)
118     })
119     .await??;
120
121     let added_to_community = blocking(context.pool(), move |conn| {
122       ModAddCommunityView::list(conn, community_id, mod_user_id, page, limit)
123     })
124     .await??;
125
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| {
129         Ok((
130           ModRemoveCommunityView::list(conn, mod_user_id, page, limit)?,
131           ModBanView::list(conn, mod_user_id, page, limit)?,
132           ModAddView::list(conn, mod_user_id, page, limit)?,
133         )) as Result<_, LemmyError>
134       })
135       .await??
136     } else {
137       (Vec::new(), Vec::new(), Vec::new())
138     };
139
140     // Return the jwt
141     Ok(GetModlogResponse {
142       removed_posts,
143       locked_posts,
144       stickied_posts,
145       removed_comments,
146       removed_communities,
147       banned_from_community,
148       banned,
149       added_to_community,
150       added,
151     })
152   }
153 }
154
155 #[async_trait::async_trait(?Send)]
156 impl Perform for CreateSite {
157   type Response = SiteResponse;
158
159   async fn perform(
160     &self,
161     context: &Data<LemmyContext>,
162     _websocket_id: Option<ConnectionId>,
163   ) -> Result<SiteResponse, LemmyError> {
164     let data: &CreateSite = &self;
165
166     let read_site = move |conn: &'_ _| Site::read(conn, 1);
167     if blocking(context.pool(), read_site).await?.is_ok() {
168       return Err(APIError::err("site_already_exists").into());
169     };
170
171     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
172
173     check_slurs(&data.name)?;
174     check_slurs_opt(&data.description)?;
175
176     // Make sure user is an admin
177     is_admin(context.pool(), user.id).await?;
178
179     let site_form = SiteForm {
180       name: data.name.to_owned(),
181       description: data.description.to_owned(),
182       icon: Some(data.icon.to_owned()),
183       banner: Some(data.banner.to_owned()),
184       creator_id: user.id,
185       enable_downvotes: data.enable_downvotes,
186       open_registration: data.open_registration,
187       enable_nsfw: data.enable_nsfw,
188       updated: None,
189     };
190
191     let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
192     if blocking(context.pool(), create_site).await?.is_err() {
193       return Err(APIError::err("site_already_exists").into());
194     }
195
196     let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
197
198     Ok(SiteResponse { site: site_view })
199   }
200 }
201
202 #[async_trait::async_trait(?Send)]
203 impl Perform for EditSite {
204   type Response = SiteResponse;
205   async fn perform(
206     &self,
207     context: &Data<LemmyContext>,
208     websocket_id: Option<ConnectionId>,
209   ) -> Result<SiteResponse, LemmyError> {
210     let data: &EditSite = &self;
211     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
212
213     check_slurs(&data.name)?;
214     check_slurs_opt(&data.description)?;
215
216     // Make sure user is an admin
217     is_admin(context.pool(), user.id).await?;
218
219     let found_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
220
221     let icon = diesel_option_overwrite(&data.icon);
222     let banner = diesel_option_overwrite(&data.banner);
223
224     let site_form = SiteForm {
225       name: data.name.to_owned(),
226       description: data.description.to_owned(),
227       icon,
228       banner,
229       creator_id: found_site.creator_id,
230       updated: Some(naive_now()),
231       enable_downvotes: data.enable_downvotes,
232       open_registration: data.open_registration,
233       enable_nsfw: data.enable_nsfw,
234     };
235
236     let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
237     if blocking(context.pool(), update_site).await?.is_err() {
238       return Err(APIError::err("couldnt_update_site").into());
239     }
240
241     let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
242
243     let res = SiteResponse { site: site_view };
244
245     context.chat_server().do_send(SendAllMessage {
246       op: UserOperation::EditSite,
247       response: res.clone(),
248       websocket_id,
249     });
250
251     Ok(res)
252   }
253 }
254
255 #[async_trait::async_trait(?Send)]
256 impl Perform for GetSite {
257   type Response = GetSiteResponse;
258
259   async fn perform(
260     &self,
261     context: &Data<LemmyContext>,
262     websocket_id: Option<ConnectionId>,
263   ) -> Result<GetSiteResponse, LemmyError> {
264     let data: &GetSite = &self;
265
266     // TODO refactor this a little
267     let res = blocking(context.pool(), move |conn| Site::read(conn, 1)).await?;
268     let site_view = if res.is_ok() {
269       Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
270     } else if let Some(setup) = Settings::get().setup.as_ref() {
271       let register = Register {
272         username: setup.admin_username.to_owned(),
273         email: setup.admin_email.to_owned(),
274         password: setup.admin_password.to_owned(),
275         password_verify: setup.admin_password.to_owned(),
276         admin: true,
277         show_nsfw: true,
278         captcha_uuid: None,
279         captcha_answer: None,
280       };
281       let login_response = register.perform(context, websocket_id).await?;
282       info!("Admin {} created", setup.admin_username);
283
284       let create_site = CreateSite {
285         name: setup.site_name.to_owned(),
286         description: None,
287         icon: None,
288         banner: None,
289         enable_downvotes: true,
290         open_registration: true,
291         enable_nsfw: true,
292         auth: login_response.jwt,
293       };
294       create_site.perform(context, websocket_id).await?;
295       info!("Site {} created", setup.site_name);
296       Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
297     } else {
298       None
299     };
300
301     let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
302
303     // Make sure the site creator is the top admin
304     if let Some(site_view) = site_view.to_owned() {
305       let site_creator_id = site_view.creator.id;
306       // TODO investigate why this is sometimes coming back null
307       // Maybe user_.admin isn't being set to true?
308       if let Some(creator_index) = admins.iter().position(|r| r.user.id == site_creator_id) {
309         let creator_user = admins.remove(creator_index);
310         admins.insert(0, creator_user);
311       }
312     }
313
314     let banned = blocking(context.pool(), move |conn| UserViewSafe::banned(conn)).await??;
315
316     let online = context
317       .chat_server()
318       .send(GetUsersOnline)
319       .await
320       .unwrap_or(1);
321
322     let my_user = get_user_from_jwt_opt(&data.auth, context.pool())
323       .await?
324       .map(|mut u| {
325         u.password_encrypted = "".to_string();
326         u.private_key = None;
327         u.public_key = None;
328         u
329       });
330
331     let counts = blocking(context.pool(), move |conn| SiteAggregates::read(conn)).await??;
332
333     Ok(GetSiteResponse {
334       site: site_view,
335       admins,
336       banned,
337       online,
338       version: version::VERSION.to_string(),
339       my_user,
340       federated_instances: linked_instances(context.pool()).await?,
341       counts,
342     })
343   }
344 }
345
346 #[async_trait::async_trait(?Send)]
347 impl Perform for Search {
348   type Response = SearchResponse;
349
350   async fn perform(
351     &self,
352     context: &Data<LemmyContext>,
353     _websocket_id: Option<ConnectionId>,
354   ) -> Result<SearchResponse, LemmyError> {
355     let data: &Search = &self;
356
357     match search_by_apub_id(&data.q, context).await {
358       Ok(r) => return Ok(r),
359       Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
360     }
361
362     let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
363     let user_id = user.map(|u| u.id);
364
365     let type_ = SearchType::from_str(&data.type_)?;
366
367     let mut posts = Vec::new();
368     let mut comments = Vec::new();
369     let mut communities = Vec::new();
370     let mut users = Vec::new();
371
372     // TODO no clean / non-nsfw searching rn
373
374     let q = data.q.to_owned();
375     let page = data.page;
376     let limit = data.limit;
377     let sort = SortType::from_str(&data.sort)?;
378     let community_id = data.community_id;
379     let community_name = data.community_name.to_owned();
380     match type_ {
381       SearchType::Posts => {
382         posts = blocking(context.pool(), move |conn| {
383           PostQueryBuilder::create(conn)
384             .sort(&sort)
385             .show_nsfw(true)
386             .community_id(community_id)
387             .community_name(community_name)
388             .my_user_id(user_id)
389             .search_term(q)
390             .page(page)
391             .limit(limit)
392             .list()
393         })
394         .await??;
395       }
396       SearchType::Comments => {
397         comments = blocking(context.pool(), move |conn| {
398           CommentQueryBuilder::create(&conn)
399             .sort(&sort)
400             .search_term(q)
401             .my_user_id(user_id)
402             .page(page)
403             .limit(limit)
404             .list()
405         })
406         .await??;
407       }
408       SearchType::Communities => {
409         communities = blocking(context.pool(), move |conn| {
410           CommunityQueryBuilder::create(conn)
411             .sort(&sort)
412             .search_term(q)
413             .page(page)
414             .limit(limit)
415             .list()
416         })
417         .await??;
418       }
419       SearchType::Users => {
420         users = blocking(context.pool(), move |conn| {
421           UserQueryBuilder::create(conn)
422             .sort(&sort)
423             .search_term(q)
424             .page(page)
425             .limit(limit)
426             .list()
427         })
428         .await??;
429       }
430       SearchType::All => {
431         posts = blocking(context.pool(), move |conn| {
432           PostQueryBuilder::create(conn)
433             .sort(&sort)
434             .show_nsfw(true)
435             .community_id(community_id)
436             .community_name(community_name)
437             .my_user_id(user_id)
438             .search_term(q)
439             .page(page)
440             .limit(limit)
441             .list()
442         })
443         .await??;
444
445         let q = data.q.to_owned();
446         let sort = SortType::from_str(&data.sort)?;
447
448         comments = blocking(context.pool(), move |conn| {
449           CommentQueryBuilder::create(conn)
450             .sort(&sort)
451             .search_term(q)
452             .my_user_id(user_id)
453             .page(page)
454             .limit(limit)
455             .list()
456         })
457         .await??;
458
459         let q = data.q.to_owned();
460         let sort = SortType::from_str(&data.sort)?;
461
462         communities = blocking(context.pool(), move |conn| {
463           CommunityQueryBuilder::create(conn)
464             .sort(&sort)
465             .search_term(q)
466             .page(page)
467             .limit(limit)
468             .list()
469         })
470         .await??;
471
472         let q = data.q.to_owned();
473         let sort = SortType::from_str(&data.sort)?;
474
475         users = blocking(context.pool(), move |conn| {
476           UserQueryBuilder::create(conn)
477             .sort(&sort)
478             .search_term(q)
479             .page(page)
480             .limit(limit)
481             .list()
482         })
483         .await??;
484       }
485       SearchType::Url => {
486         posts = blocking(context.pool(), move |conn| {
487           PostQueryBuilder::create(conn)
488             .sort(&sort)
489             .show_nsfw(true)
490             .community_id(community_id)
491             .community_name(community_name)
492             .url_search(q)
493             .page(page)
494             .limit(limit)
495             .list()
496         })
497         .await??;
498       }
499     };
500
501     // Return the jwt
502     Ok(SearchResponse {
503       type_: data.type_.to_owned(),
504       comments,
505       posts,
506       communities,
507       users,
508     })
509   }
510 }
511
512 #[async_trait::async_trait(?Send)]
513 impl Perform for TransferSite {
514   type Response = GetSiteResponse;
515
516   async fn perform(
517     &self,
518     context: &Data<LemmyContext>,
519     _websocket_id: Option<ConnectionId>,
520   ) -> Result<GetSiteResponse, LemmyError> {
521     let data: &TransferSite = &self;
522     let mut user = get_user_from_jwt(&data.auth, context.pool()).await?;
523
524     is_admin(context.pool(), user.id).await?;
525
526     // TODO add a User_::read_safe() for this.
527     user.password_encrypted = "".to_string();
528     user.private_key = None;
529     user.public_key = None;
530
531     let read_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
532
533     // Make sure user is the creator
534     if read_site.creator_id != user.id {
535       return Err(APIError::err("not_an_admin").into());
536     }
537
538     let new_creator_id = data.user_id;
539     let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
540     if blocking(context.pool(), transfer_site).await?.is_err() {
541       return Err(APIError::err("couldnt_update_site").into());
542     };
543
544     // Mod tables
545     let form = ModAddForm {
546       mod_user_id: user.id,
547       other_user_id: data.user_id,
548       removed: Some(false),
549     };
550
551     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
552
553     let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
554
555     let mut admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
556     let creator_index = admins
557       .iter()
558       .position(|r| r.user.id == site_view.creator.id)
559       .context(location_info!())?;
560     let creator_user = admins.remove(creator_index);
561     admins.insert(0, creator_user);
562
563     let banned = blocking(context.pool(), move |conn| UserViewSafe::banned(conn)).await??;
564
565     let counts = blocking(context.pool(), move |conn| SiteAggregates::read(conn)).await??;
566
567     Ok(GetSiteResponse {
568       site: Some(site_view),
569       admins,
570       banned,
571       online: 0,
572       version: version::VERSION.to_string(),
573       my_user: Some(user),
574       federated_instances: linked_instances(context.pool()).await?,
575       counts,
576     })
577   }
578 }
579
580 #[async_trait::async_trait(?Send)]
581 impl Perform for GetSiteConfig {
582   type Response = GetSiteConfigResponse;
583
584   async fn perform(
585     &self,
586     context: &Data<LemmyContext>,
587     _websocket_id: Option<ConnectionId>,
588   ) -> Result<GetSiteConfigResponse, LemmyError> {
589     let data: &GetSiteConfig = &self;
590     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
591
592     // Only let admins read this
593     is_admin(context.pool(), user.id).await?;
594
595     let config_hjson = Settings::read_config_file()?;
596
597     Ok(GetSiteConfigResponse { config_hjson })
598   }
599 }
600
601 #[async_trait::async_trait(?Send)]
602 impl Perform for SaveSiteConfig {
603   type Response = GetSiteConfigResponse;
604
605   async fn perform(
606     &self,
607     context: &Data<LemmyContext>,
608     _websocket_id: Option<ConnectionId>,
609   ) -> Result<GetSiteConfigResponse, LemmyError> {
610     let data: &SaveSiteConfig = &self;
611     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
612
613     // Only let admins read this
614     let admins = blocking(context.pool(), move |conn| UserViewSafe::admins(conn)).await??;
615     let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.user.id).collect();
616
617     if !admin_ids.contains(&user.id) {
618       return Err(APIError::err("not_an_admin").into());
619     }
620
621     // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
622     let config_hjson = match Settings::save_config_file(&data.config_hjson) {
623       Ok(config_hjson) => config_hjson,
624       Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
625     };
626
627     Ok(GetSiteConfigResponse { config_hjson })
628   }
629 }