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