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