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