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