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