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