]> Untitled Git - lemmy.git/blob - crates/api/src/site.rs
Add show_read_posts filter. Fixes #1561
[lemmy.git] / crates / api / src / site.rs
1 use crate::Perform;
2 use actix_web::web::Data;
3 use anyhow::Context;
4 use lemmy_api_common::{
5   blocking,
6   build_federated_instances,
7   get_local_user_settings_view_from_jwt,
8   get_local_user_view_from_jwt,
9   get_local_user_view_from_jwt_opt,
10   is_admin,
11   site::*,
12   user_show_bot_accounts,
13   user_show_nsfw,
14   user_show_read_posts,
15 };
16 use lemmy_apub::fetcher::search::search_by_apub_id;
17 use lemmy_db_queries::{source::site::Site_, Crud, SearchType, SortType};
18 use lemmy_db_schema::source::{moderator::*, site::Site};
19 use lemmy_db_views::{
20   comment_view::CommentQueryBuilder,
21   post_view::PostQueryBuilder,
22   site_view::SiteView,
23 };
24 use lemmy_db_views_actor::{
25   community_view::CommunityQueryBuilder,
26   person_view::{PersonQueryBuilder, PersonViewSafe},
27 };
28 use lemmy_db_views_moderator::{
29   mod_add_community_view::ModAddCommunityView,
30   mod_add_view::ModAddView,
31   mod_ban_from_community_view::ModBanFromCommunityView,
32   mod_ban_view::ModBanView,
33   mod_lock_post_view::ModLockPostView,
34   mod_remove_comment_view::ModRemoveCommentView,
35   mod_remove_community_view::ModRemoveCommunityView,
36   mod_remove_post_view::ModRemovePostView,
37   mod_sticky_post_view::ModStickyPostView,
38 };
39 use lemmy_utils::{
40   location_info,
41   settings::structs::Settings,
42   version,
43   ApiError,
44   ConnectionId,
45   LemmyError,
46 };
47 use lemmy_websocket::LemmyContext;
48 use log::debug;
49 use std::str::FromStr;
50
51 #[async_trait::async_trait(?Send)]
52 impl Perform for GetModlog {
53   type Response = GetModlogResponse;
54
55   async fn perform(
56     &self,
57     context: &Data<LemmyContext>,
58     _websocket_id: Option<ConnectionId>,
59   ) -> Result<GetModlogResponse, LemmyError> {
60     let data: &GetModlog = &self;
61
62     let community_id = data.community_id;
63     let mod_person_id = data.mod_person_id;
64     let page = data.page;
65     let limit = data.limit;
66     let removed_posts = blocking(context.pool(), move |conn| {
67       ModRemovePostView::list(conn, community_id, mod_person_id, page, limit)
68     })
69     .await??;
70
71     let locked_posts = blocking(context.pool(), move |conn| {
72       ModLockPostView::list(conn, community_id, mod_person_id, page, limit)
73     })
74     .await??;
75
76     let stickied_posts = blocking(context.pool(), move |conn| {
77       ModStickyPostView::list(conn, community_id, mod_person_id, page, limit)
78     })
79     .await??;
80
81     let removed_comments = blocking(context.pool(), move |conn| {
82       ModRemoveCommentView::list(conn, community_id, mod_person_id, page, limit)
83     })
84     .await??;
85
86     let banned_from_community = blocking(context.pool(), move |conn| {
87       ModBanFromCommunityView::list(conn, community_id, mod_person_id, page, limit)
88     })
89     .await??;
90
91     let added_to_community = blocking(context.pool(), move |conn| {
92       ModAddCommunityView::list(conn, community_id, mod_person_id, page, limit)
93     })
94     .await??;
95
96     // These arrays are only for the full modlog, when a community isn't given
97     let (removed_communities, banned, added) = if data.community_id.is_none() {
98       blocking(context.pool(), move |conn| {
99         Ok((
100           ModRemoveCommunityView::list(conn, mod_person_id, page, limit)?,
101           ModBanView::list(conn, mod_person_id, page, limit)?,
102           ModAddView::list(conn, mod_person_id, page, limit)?,
103         )) as Result<_, LemmyError>
104       })
105       .await??
106     } else {
107       (Vec::new(), Vec::new(), Vec::new())
108     };
109
110     // Return the jwt
111     Ok(GetModlogResponse {
112       removed_posts,
113       locked_posts,
114       stickied_posts,
115       removed_comments,
116       removed_communities,
117       banned_from_community,
118       banned,
119       added_to_community,
120       added,
121     })
122   }
123 }
124
125 #[async_trait::async_trait(?Send)]
126 impl Perform for Search {
127   type Response = SearchResponse;
128
129   async fn perform(
130     &self,
131     context: &Data<LemmyContext>,
132     _websocket_id: Option<ConnectionId>,
133   ) -> Result<SearchResponse, LemmyError> {
134     let data: &Search = &self;
135
136     match search_by_apub_id(&data.q, context).await {
137       Ok(r) => return Ok(r),
138       Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
139     }
140
141     let local_user_view = get_local_user_view_from_jwt_opt(&data.auth, context.pool()).await?;
142
143     let show_nsfw = user_show_nsfw(&local_user_view);
144     let show_bot_accounts = user_show_bot_accounts(&local_user_view);
145     let show_read_posts = user_show_read_posts(&local_user_view);
146
147     let person_id = local_user_view.map(|u| u.person.id);
148
149     let type_ = SearchType::from_str(&data.type_)?;
150
151     let mut posts = Vec::new();
152     let mut comments = Vec::new();
153     let mut communities = Vec::new();
154     let mut users = Vec::new();
155
156     // TODO no clean / non-nsfw searching rn
157
158     let q = data.q.to_owned();
159     let page = data.page;
160     let limit = data.limit;
161     let sort = SortType::from_str(&data.sort)?;
162     let community_id = data.community_id;
163     let community_name = data.community_name.to_owned();
164     match type_ {
165       SearchType::Posts => {
166         posts = blocking(context.pool(), move |conn| {
167           PostQueryBuilder::create(conn)
168             .sort(&sort)
169             .show_nsfw(show_nsfw)
170             .show_bot_accounts(show_bot_accounts)
171             .show_read_posts(show_read_posts)
172             .community_id(community_id)
173             .community_name(community_name)
174             .my_person_id(person_id)
175             .search_term(q)
176             .page(page)
177             .limit(limit)
178             .list()
179         })
180         .await??;
181       }
182       SearchType::Comments => {
183         comments = blocking(context.pool(), move |conn| {
184           CommentQueryBuilder::create(&conn)
185             .sort(&sort)
186             .search_term(q)
187             .show_bot_accounts(show_bot_accounts)
188             .my_person_id(person_id)
189             .page(page)
190             .limit(limit)
191             .list()
192         })
193         .await??;
194       }
195       SearchType::Communities => {
196         communities = blocking(context.pool(), move |conn| {
197           CommunityQueryBuilder::create(conn)
198             .sort(&sort)
199             .search_term(q)
200             .my_person_id(person_id)
201             .page(page)
202             .limit(limit)
203             .list()
204         })
205         .await??;
206       }
207       SearchType::Users => {
208         users = blocking(context.pool(), move |conn| {
209           PersonQueryBuilder::create(conn)
210             .sort(&sort)
211             .search_term(q)
212             .page(page)
213             .limit(limit)
214             .list()
215         })
216         .await??;
217       }
218       SearchType::All => {
219         posts = blocking(context.pool(), move |conn| {
220           PostQueryBuilder::create(conn)
221             .sort(&sort)
222             .show_nsfw(show_nsfw)
223             .show_bot_accounts(show_bot_accounts)
224             .show_read_posts(show_read_posts)
225             .community_id(community_id)
226             .community_name(community_name)
227             .my_person_id(person_id)
228             .search_term(q)
229             .page(page)
230             .limit(limit)
231             .list()
232         })
233         .await??;
234
235         let q = data.q.to_owned();
236         let sort = SortType::from_str(&data.sort)?;
237
238         comments = blocking(context.pool(), move |conn| {
239           CommentQueryBuilder::create(conn)
240             .sort(&sort)
241             .search_term(q)
242             .show_bot_accounts(show_bot_accounts)
243             .my_person_id(person_id)
244             .page(page)
245             .limit(limit)
246             .list()
247         })
248         .await??;
249
250         let q = data.q.to_owned();
251         let sort = SortType::from_str(&data.sort)?;
252
253         communities = blocking(context.pool(), move |conn| {
254           CommunityQueryBuilder::create(conn)
255             .sort(&sort)
256             .search_term(q)
257             .my_person_id(person_id)
258             .page(page)
259             .limit(limit)
260             .list()
261         })
262         .await??;
263
264         let q = data.q.to_owned();
265         let sort = SortType::from_str(&data.sort)?;
266
267         users = blocking(context.pool(), move |conn| {
268           PersonQueryBuilder::create(conn)
269             .sort(&sort)
270             .search_term(q)
271             .page(page)
272             .limit(limit)
273             .list()
274         })
275         .await??;
276       }
277       SearchType::Url => {
278         posts = blocking(context.pool(), move |conn| {
279           PostQueryBuilder::create(conn)
280             .sort(&sort)
281             .show_nsfw(show_nsfw)
282             .show_bot_accounts(show_bot_accounts)
283             .show_read_posts(show_read_posts)
284             .my_person_id(person_id)
285             .community_id(community_id)
286             .community_name(community_name)
287             .url_search(q)
288             .page(page)
289             .limit(limit)
290             .list()
291         })
292         .await??;
293       }
294     };
295
296     // Return the jwt
297     Ok(SearchResponse {
298       type_: data.type_.to_owned(),
299       comments,
300       posts,
301       communities,
302       users,
303     })
304   }
305 }
306
307 #[async_trait::async_trait(?Send)]
308 impl Perform for TransferSite {
309   type Response = GetSiteResponse;
310
311   async fn perform(
312     &self,
313     context: &Data<LemmyContext>,
314     _websocket_id: Option<ConnectionId>,
315   ) -> Result<GetSiteResponse, LemmyError> {
316     let data: &TransferSite = &self;
317     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
318
319     is_admin(&local_user_view)?;
320
321     let read_site = blocking(context.pool(), move |conn| Site::read_simple(conn)).await??;
322
323     // Make sure user is the creator
324     if read_site.creator_id != local_user_view.person.id {
325       return Err(ApiError::err("not_an_admin").into());
326     }
327
328     let new_creator_id = data.person_id;
329     let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
330     if blocking(context.pool(), transfer_site).await?.is_err() {
331       return Err(ApiError::err("couldnt_update_site").into());
332     };
333
334     // Mod tables
335     let form = ModAddForm {
336       mod_person_id: local_user_view.person.id,
337       other_person_id: data.person_id,
338       removed: Some(false),
339     };
340
341     blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
342
343     let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
344
345     let mut admins = blocking(context.pool(), move |conn| PersonViewSafe::admins(conn)).await??;
346     let creator_index = admins
347       .iter()
348       .position(|r| r.person.id == site_view.creator.id)
349       .context(location_info!())?;
350     let creator_person = admins.remove(creator_index);
351     admins.insert(0, creator_person);
352
353     let banned = blocking(context.pool(), move |conn| PersonViewSafe::banned(conn)).await??;
354     let federated_instances = build_federated_instances(context.pool()).await?;
355
356     let my_user = Some(get_local_user_settings_view_from_jwt(&data.auth, context.pool()).await?);
357
358     Ok(GetSiteResponse {
359       site_view: Some(site_view),
360       admins,
361       banned,
362       online: 0,
363       version: version::VERSION.to_string(),
364       my_user,
365       federated_instances,
366     })
367   }
368 }
369
370 #[async_trait::async_trait(?Send)]
371 impl Perform for GetSiteConfig {
372   type Response = GetSiteConfigResponse;
373
374   async fn perform(
375     &self,
376     context: &Data<LemmyContext>,
377     _websocket_id: Option<ConnectionId>,
378   ) -> Result<GetSiteConfigResponse, LemmyError> {
379     let data: &GetSiteConfig = &self;
380     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
381
382     // Only let admins read this
383     is_admin(&local_user_view)?;
384
385     let config_hjson = Settings::read_config_file()?;
386
387     Ok(GetSiteConfigResponse { config_hjson })
388   }
389 }
390
391 #[async_trait::async_trait(?Send)]
392 impl Perform for SaveSiteConfig {
393   type Response = GetSiteConfigResponse;
394
395   async fn perform(
396     &self,
397     context: &Data<LemmyContext>,
398     _websocket_id: Option<ConnectionId>,
399   ) -> Result<GetSiteConfigResponse, LemmyError> {
400     let data: &SaveSiteConfig = &self;
401     let local_user_view = get_local_user_view_from_jwt(&data.auth, context.pool()).await?;
402
403     // Only let admins read this
404     is_admin(&local_user_view)?;
405
406     // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
407     let config_hjson = Settings::save_config_file(&data.config_hjson)
408       .map_err(|_| ApiError::err("couldnt_update_site"))?;
409
410     Ok(GetSiteConfigResponse { config_hjson })
411   }
412 }