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