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