2 api::{get_user_from_jwt, get_user_from_jwt_opt, is_admin, Perform},
3 apub::fetcher::search_by_apub_id,
7 use actix_web::web::Data;
13 diesel_option_overwrite,
29 websocket::{GetUsersOnline, SendAllMessage, UserOperation},
34 utils::{check_slurs, check_slurs_opt},
39 use log::{debug, info};
40 use std::str::FromStr;
42 #[async_trait::async_trait(?Send)]
43 impl Perform for ListCategories {
44 type Response = ListCategoriesResponse;
48 context: &Data<LemmyContext>,
49 _websocket_id: Option<ConnectionId>,
50 ) -> Result<ListCategoriesResponse, LemmyError> {
51 let _data: &ListCategories = &self;
53 let categories = blocking(context.pool(), move |conn| Category::list_all(conn)).await??;
56 Ok(ListCategoriesResponse { categories })
60 #[async_trait::async_trait(?Send)]
61 impl Perform for GetModlog {
62 type Response = GetModlogResponse;
66 context: &Data<LemmyContext>,
67 _websocket_id: Option<ConnectionId>,
68 ) -> Result<GetModlogResponse, LemmyError> {
69 let data: &GetModlog = &self;
71 let community_id = data.community_id;
72 let mod_user_id = data.mod_user_id;
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)
80 let locked_posts = blocking(context.pool(), move |conn| {
81 ModLockPostView::list(conn, community_id, mod_user_id, page, limit)
85 let stickied_posts = blocking(context.pool(), move |conn| {
86 ModStickyPostView::list(conn, community_id, mod_user_id, page, limit)
90 let removed_comments = blocking(context.pool(), move |conn| {
91 ModRemoveCommentView::list(conn, community_id, mod_user_id, page, limit)
95 let banned_from_community = blocking(context.pool(), move |conn| {
96 ModBanFromCommunityView::list(conn, community_id, mod_user_id, page, limit)
100 let added_to_community = blocking(context.pool(), move |conn| {
101 ModAddCommunityView::list(conn, community_id, mod_user_id, page, limit)
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| {
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>
116 (Vec::new(), Vec::new(), Vec::new())
120 Ok(GetModlogResponse {
126 banned_from_community,
134 #[async_trait::async_trait(?Send)]
135 impl Perform for CreateSite {
136 type Response = SiteResponse;
140 context: &Data<LemmyContext>,
141 _websocket_id: Option<ConnectionId>,
142 ) -> Result<SiteResponse, LemmyError> {
143 let data: &CreateSite = &self;
145 let read_site = move |conn: &'_ _| Site::read(conn, 1);
146 if blocking(context.pool(), read_site).await?.is_ok() {
147 return Err(APIError::err("site_already_exists").into());
150 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
152 check_slurs(&data.name)?;
153 check_slurs_opt(&data.description)?;
155 // Make sure user is an admin
156 is_admin(context.pool(), user.id).await?;
158 let site_form = SiteForm {
159 name: data.name.to_owned(),
160 description: data.description.to_owned(),
161 icon: Some(data.icon.to_owned()),
162 banner: Some(data.banner.to_owned()),
164 enable_downvotes: data.enable_downvotes,
165 open_registration: data.open_registration,
166 enable_nsfw: data.enable_nsfw,
170 let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
171 if blocking(context.pool(), create_site).await?.is_err() {
172 return Err(APIError::err("site_already_exists").into());
175 let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
177 Ok(SiteResponse { site: site_view })
181 #[async_trait::async_trait(?Send)]
182 impl Perform for EditSite {
183 type Response = SiteResponse;
186 context: &Data<LemmyContext>,
187 websocket_id: Option<ConnectionId>,
188 ) -> Result<SiteResponse, LemmyError> {
189 let data: &EditSite = &self;
190 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
192 check_slurs(&data.name)?;
193 check_slurs_opt(&data.description)?;
195 // Make sure user is an admin
196 is_admin(context.pool(), user.id).await?;
198 let found_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
200 let icon = diesel_option_overwrite(&data.icon);
201 let banner = diesel_option_overwrite(&data.banner);
203 let site_form = SiteForm {
204 name: data.name.to_owned(),
205 description: data.description.to_owned(),
208 creator_id: found_site.creator_id,
209 updated: Some(naive_now()),
210 enable_downvotes: data.enable_downvotes,
211 open_registration: data.open_registration,
212 enable_nsfw: data.enable_nsfw,
215 let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
216 if blocking(context.pool(), update_site).await?.is_err() {
217 return Err(APIError::err("couldnt_update_site").into());
220 let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
222 let res = SiteResponse { site: site_view };
224 context.chat_server().do_send(SendAllMessage {
225 op: UserOperation::EditSite,
226 response: res.clone(),
234 #[async_trait::async_trait(?Send)]
235 impl Perform for GetSite {
236 type Response = GetSiteResponse;
240 context: &Data<LemmyContext>,
241 websocket_id: Option<ConnectionId>,
242 ) -> Result<GetSiteResponse, LemmyError> {
243 let data: &GetSite = &self;
245 // TODO refactor this a little
246 let res = blocking(context.pool(), move |conn| Site::read(conn, 1)).await?;
247 let site_view = if res.is_ok() {
248 Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
249 } else if let Some(setup) = Settings::get().setup.as_ref() {
250 let register = Register {
251 username: setup.admin_username.to_owned(),
252 email: setup.admin_email.to_owned(),
253 password: setup.admin_password.to_owned(),
254 password_verify: setup.admin_password.to_owned(),
258 captcha_answer: None,
260 let login_response = register.perform(context, websocket_id).await?;
261 info!("Admin {} created", setup.admin_username);
263 let create_site = CreateSite {
264 name: setup.site_name.to_owned(),
268 enable_downvotes: true,
269 open_registration: true,
271 auth: login_response.jwt,
273 create_site.perform(context, websocket_id).await?;
274 info!("Site {} created", setup.site_name);
275 Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
280 let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
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.id == site_creator_id) {
288 let creator_user = admins.remove(creator_index);
289 admins.insert(0, creator_user);
293 let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
297 .send(GetUsersOnline)
301 let my_user = get_user_from_jwt_opt(&data.auth, context.pool())
304 u.password_encrypted = "".to_string();
305 u.private_key = None;
315 version: version::VERSION.to_string(),
317 federated_instances: Settings::get().get_allowed_instances(),
322 #[async_trait::async_trait(?Send)]
323 impl Perform for Search {
324 type Response = SearchResponse;
328 context: &Data<LemmyContext>,
329 _websocket_id: Option<ConnectionId>,
330 ) -> Result<SearchResponse, LemmyError> {
331 let data: &Search = &self;
335 match search_by_apub_id(&data.q, context).await {
336 Ok(r) => return Ok(r),
337 Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
340 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
341 let user_id = user.map(|u| u.id);
343 let type_ = SearchType::from_str(&data.type_)?;
345 let mut posts = Vec::new();
346 let mut comments = Vec::new();
347 let mut communities = Vec::new();
348 let mut users = Vec::new();
350 // TODO no clean / non-nsfw searching rn
352 let q = data.q.to_owned();
353 let page = data.page;
354 let limit = data.limit;
355 let sort = SortType::from_str(&data.sort)?;
356 let community_id = data.community_id;
358 SearchType::Posts => {
359 posts = blocking(context.pool(), move |conn| {
360 PostQueryBuilder::create(conn)
363 .for_community_id(community_id)
372 SearchType::Comments => {
373 comments = blocking(context.pool(), move |conn| {
374 CommentQueryBuilder::create(&conn)
384 SearchType::Communities => {
385 communities = blocking(context.pool(), move |conn| {
386 CommunityQueryBuilder::create(conn)
395 SearchType::Users => {
396 users = blocking(context.pool(), move |conn| {
397 UserQueryBuilder::create(conn)
407 posts = blocking(context.pool(), move |conn| {
408 PostQueryBuilder::create(conn)
411 .for_community_id(community_id)
420 let q = data.q.to_owned();
421 let sort = SortType::from_str(&data.sort)?;
423 comments = blocking(context.pool(), move |conn| {
424 CommentQueryBuilder::create(conn)
434 let q = data.q.to_owned();
435 let sort = SortType::from_str(&data.sort)?;
437 communities = blocking(context.pool(), move |conn| {
438 CommunityQueryBuilder::create(conn)
447 let q = data.q.to_owned();
448 let sort = SortType::from_str(&data.sort)?;
450 users = blocking(context.pool(), move |conn| {
451 UserQueryBuilder::create(conn)
461 posts = blocking(context.pool(), move |conn| {
462 PostQueryBuilder::create(conn)
465 .for_community_id(community_id)
477 type_: data.type_.to_owned(),
486 #[async_trait::async_trait(?Send)]
487 impl Perform for TransferSite {
488 type Response = GetSiteResponse;
492 context: &Data<LemmyContext>,
493 _websocket_id: Option<ConnectionId>,
494 ) -> Result<GetSiteResponse, LemmyError> {
495 let data: &TransferSite = &self;
496 let mut user = get_user_from_jwt(&data.auth, context.pool()).await?;
498 is_admin(context.pool(), user.id).await?;
500 // TODO add a User_::read_safe() for this.
501 user.password_encrypted = "".to_string();
502 user.private_key = None;
503 user.public_key = None;
505 let read_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
507 // Make sure user is the creator
508 if read_site.creator_id != user.id {
509 return Err(APIError::err("not_an_admin").into());
512 let new_creator_id = data.user_id;
513 let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
514 if blocking(context.pool(), transfer_site).await?.is_err() {
515 return Err(APIError::err("couldnt_update_site").into());
519 let form = ModAddForm {
520 mod_user_id: user.id,
521 other_user_id: data.user_id,
522 removed: Some(false),
525 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
527 let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
529 let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
530 let creator_index = admins
532 .position(|r| r.id == site_view.creator_id)
533 .context(location_info!())?;
534 let creator_user = admins.remove(creator_index);
535 admins.insert(0, creator_user);
537 let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
540 site: Some(site_view),
544 version: version::VERSION.to_string(),
546 federated_instances: Settings::get().get_allowed_instances(),
551 #[async_trait::async_trait(?Send)]
552 impl Perform for GetSiteConfig {
553 type Response = GetSiteConfigResponse;
557 context: &Data<LemmyContext>,
558 _websocket_id: Option<ConnectionId>,
559 ) -> Result<GetSiteConfigResponse, LemmyError> {
560 let data: &GetSiteConfig = &self;
561 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
563 // Only let admins read this
564 is_admin(context.pool(), user.id).await?;
566 let config_hjson = Settings::read_config_file()?;
568 Ok(GetSiteConfigResponse { config_hjson })
572 #[async_trait::async_trait(?Send)]
573 impl Perform for SaveSiteConfig {
574 type Response = GetSiteConfigResponse;
578 context: &Data<LemmyContext>,
579 _websocket_id: Option<ConnectionId>,
580 ) -> Result<GetSiteConfigResponse, LemmyError> {
581 let data: &SaveSiteConfig = &self;
582 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
584 // Only let admins read this
585 let admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
586 let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
588 if !admin_ids.contains(&user.id) {
589 return Err(APIError::err("not_an_admin").into());
592 // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
593 let config_hjson = match Settings::save_config_file(&data.config_hjson) {
594 Ok(config_hjson) => config_hjson,
595 Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
598 Ok(GetSiteConfigResponse { config_hjson })