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