]> Untitled Git - lemmy.git/blob - lemmy_api/src/site.rs
Better account deletion (fixes #730) (#143)
[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     dbg!(&data);
337
338     match search_by_apub_id(&data.q, context).await {
339       Ok(r) => return Ok(r),
340       Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
341     }
342
343     let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
344     let user_id = user.map(|u| u.id);
345
346     let type_ = SearchType::from_str(&data.type_)?;
347
348     let mut posts = Vec::new();
349     let mut comments = Vec::new();
350     let mut communities = Vec::new();
351     let mut users = Vec::new();
352
353     // TODO no clean / non-nsfw searching rn
354
355     let q = data.q.to_owned();
356     let page = data.page;
357     let limit = data.limit;
358     let sort = SortType::from_str(&data.sort)?;
359     let community_id = data.community_id;
360     let community_name = data.community_name.to_owned();
361     match type_ {
362       SearchType::Posts => {
363         posts = blocking(context.pool(), move |conn| {
364           PostQueryBuilder::create(conn)
365             .sort(&sort)
366             .show_nsfw(true)
367             .for_community_id(community_id)
368             .for_community_name(community_name)
369             .search_term(q)
370             .my_user_id(user_id)
371             .page(page)
372             .limit(limit)
373             .list()
374         })
375         .await??;
376       }
377       SearchType::Comments => {
378         comments = blocking(context.pool(), move |conn| {
379           CommentQueryBuilder::create(&conn)
380             .sort(&sort)
381             .search_term(q)
382             .my_user_id(user_id)
383             .page(page)
384             .limit(limit)
385             .list()
386         })
387         .await??;
388       }
389       SearchType::Communities => {
390         communities = blocking(context.pool(), move |conn| {
391           CommunityQueryBuilder::create(conn)
392             .sort(&sort)
393             .search_term(q)
394             .page(page)
395             .limit(limit)
396             .list()
397         })
398         .await??;
399       }
400       SearchType::Users => {
401         users = blocking(context.pool(), move |conn| {
402           UserQueryBuilder::create(conn)
403             .sort(&sort)
404             .search_term(q)
405             .page(page)
406             .limit(limit)
407             .list()
408         })
409         .await??;
410       }
411       SearchType::All => {
412         posts = blocking(context.pool(), move |conn| {
413           PostQueryBuilder::create(conn)
414             .sort(&sort)
415             .show_nsfw(true)
416             .for_community_id(community_id)
417             .for_community_name(community_name)
418             .search_term(q)
419             .my_user_id(user_id)
420             .page(page)
421             .limit(limit)
422             .list()
423         })
424         .await??;
425
426         let q = data.q.to_owned();
427         let sort = SortType::from_str(&data.sort)?;
428
429         comments = blocking(context.pool(), move |conn| {
430           CommentQueryBuilder::create(conn)
431             .sort(&sort)
432             .search_term(q)
433             .my_user_id(user_id)
434             .page(page)
435             .limit(limit)
436             .list()
437         })
438         .await??;
439
440         let q = data.q.to_owned();
441         let sort = SortType::from_str(&data.sort)?;
442
443         communities = blocking(context.pool(), move |conn| {
444           CommunityQueryBuilder::create(conn)
445             .sort(&sort)
446             .search_term(q)
447             .page(page)
448             .limit(limit)
449             .list()
450         })
451         .await??;
452
453         let q = data.q.to_owned();
454         let sort = SortType::from_str(&data.sort)?;
455
456         users = blocking(context.pool(), move |conn| {
457           UserQueryBuilder::create(conn)
458             .sort(&sort)
459             .search_term(q)
460             .page(page)
461             .limit(limit)
462             .list()
463         })
464         .await??;
465       }
466       SearchType::Url => {
467         posts = blocking(context.pool(), move |conn| {
468           PostQueryBuilder::create(conn)
469             .sort(&sort)
470             .show_nsfw(true)
471             .for_community_id(community_id)
472             .for_community_name(community_name)
473             .url_search(q)
474             .page(page)
475             .limit(limit)
476             .list()
477         })
478         .await??;
479       }
480     };
481
482     // Return the jwt
483     Ok(SearchResponse {
484       type_: data.type_.to_owned(),
485       comments,
486       posts,
487       communities,
488       users,
489     })
490   }
491 }
492
493 #[async_trait::async_trait(?Send)]
494 impl Perform for TransferSite {
495   type Response = GetSiteResponse;
496
497   async fn perform(
498     &self,
499     context: &Data<LemmyContext>,
500     _websocket_id: Option<ConnectionId>,
501   ) -> Result<GetSiteResponse, LemmyError> {
502     let data: &TransferSite = &self;
503     let mut user = get_user_from_jwt(&data.auth, context.pool()).await?;
504
505     is_admin(context.pool(), user.id).await?;
506
507     // TODO add a User_::read_safe() for this.
508     user.password_encrypted = "".to_string();
509     user.private_key = None;
510     user.public_key = None;
511
512     let read_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
513
514     // Make sure user is the creator
515     if read_site.creator_id != user.id {
516       return Err(APIError::err("not_an_admin").into());
517     }
518
519     let new_creator_id = data.user_id;
520     let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
521     if blocking(context.pool(), transfer_site).await?.is_err() {
522       return Err(APIError::err("couldnt_update_site").into());
523     };
524
525     // Mod tables
526     let form = ModAddForm {
527       mod_user_id: user.id,
528       other_user_id: data.user_id,
529       removed: Some(false),
530     };
531
532     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
533
534     let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
535
536     let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
537     let creator_index = admins
538       .iter()
539       .position(|r| r.id == site_view.creator_id)
540       .context(location_info!())?;
541     let creator_user = admins.remove(creator_index);
542     admins.insert(0, creator_user);
543
544     let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
545
546     Ok(GetSiteResponse {
547       site: Some(site_view),
548       admins,
549       banned,
550       online: 0,
551       version: version::VERSION.to_string(),
552       my_user: Some(user),
553       federated_instances: linked_instances(context.pool()).await?,
554     })
555   }
556 }
557
558 #[async_trait::async_trait(?Send)]
559 impl Perform for GetSiteConfig {
560   type Response = GetSiteConfigResponse;
561
562   async fn perform(
563     &self,
564     context: &Data<LemmyContext>,
565     _websocket_id: Option<ConnectionId>,
566   ) -> Result<GetSiteConfigResponse, LemmyError> {
567     let data: &GetSiteConfig = &self;
568     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
569
570     // Only let admins read this
571     is_admin(context.pool(), user.id).await?;
572
573     let config_hjson = Settings::read_config_file()?;
574
575     Ok(GetSiteConfigResponse { config_hjson })
576   }
577 }
578
579 #[async_trait::async_trait(?Send)]
580 impl Perform for SaveSiteConfig {
581   type Response = GetSiteConfigResponse;
582
583   async fn perform(
584     &self,
585     context: &Data<LemmyContext>,
586     _websocket_id: Option<ConnectionId>,
587   ) -> Result<GetSiteConfigResponse, LemmyError> {
588     let data: &SaveSiteConfig = &self;
589     let user = get_user_from_jwt(&data.auth, context.pool()).await?;
590
591     // Only let admins read this
592     let admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
593     let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
594
595     if !admin_ids.contains(&user.id) {
596       return Err(APIError::err("not_an_admin").into());
597     }
598
599     // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
600     let config_hjson = match Settings::save_config_file(&data.config_hjson) {
601       Ok(config_hjson) => config_hjson,
602       Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
603     };
604
605     Ok(GetSiteConfigResponse { config_hjson })
606   }
607 }