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