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