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 websocket::{GetUsersOnline, SendAllMessage, UserOperation},
19 diesel_option_overwrite,
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 match blocking(context.pool(), move |conn| { Site::read(conn, 1)}).await?
147 Ok(_site) => return Err(APIError::err("site_already_exists").into()),
151 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
153 check_slurs(&data.name)?;
154 check_slurs_opt(&data.description)?;
156 // Make sure user is an admin
157 is_admin(context.pool(), user.id).await?;
159 let site_form = SiteForm {
160 name: data.name.to_owned(),
161 description: data.description.to_owned(),
162 icon: Some(data.icon.to_owned()),
163 banner: Some(data.banner.to_owned()),
165 enable_downvotes: data.enable_downvotes,
166 open_registration: data.open_registration,
167 enable_nsfw: data.enable_nsfw,
171 let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
172 if blocking(context.pool(), create_site).await?.is_err() {
173 return Err(APIError::err("site_already_exists").into());
176 let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
178 Ok(SiteResponse { site: site_view })
182 #[async_trait::async_trait(?Send)]
183 impl Perform for EditSite {
184 type Response = SiteResponse;
187 context: &Data<LemmyContext>,
188 websocket_id: Option<ConnectionId>,
189 ) -> Result<SiteResponse, LemmyError> {
190 let data: &EditSite = &self;
191 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
193 check_slurs(&data.name)?;
194 check_slurs_opt(&data.description)?;
196 // Make sure user is an admin
197 is_admin(context.pool(), user.id).await?;
199 let found_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
201 let icon = diesel_option_overwrite(&data.icon);
202 let banner = diesel_option_overwrite(&data.banner);
204 let site_form = SiteForm {
205 name: data.name.to_owned(),
206 description: data.description.to_owned(),
209 creator_id: found_site.creator_id,
210 updated: Some(naive_now()),
211 enable_downvotes: data.enable_downvotes,
212 open_registration: data.open_registration,
213 enable_nsfw: data.enable_nsfw,
216 let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
217 if blocking(context.pool(), update_site).await?.is_err() {
218 return Err(APIError::err("couldnt_update_site").into());
221 let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
223 let res = SiteResponse { site: site_view };
225 context.chat_server().do_send(SendAllMessage {
226 op: UserOperation::EditSite,
227 response: res.clone(),
235 #[async_trait::async_trait(?Send)]
236 impl Perform for GetSite {
237 type Response = GetSiteResponse;
241 context: &Data<LemmyContext>,
242 websocket_id: Option<ConnectionId>,
243 ) -> Result<GetSiteResponse, LemmyError> {
244 let data: &GetSite = &self;
246 // TODO refactor this a little
247 let res = blocking(context.pool(), move |conn| Site::read(conn, 1)).await?;
248 let site_view = if res.is_ok() {
249 Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
250 } else if let Some(setup) = Settings::get().setup.as_ref() {
251 let register = Register {
252 username: setup.admin_username.to_owned(),
253 email: setup.admin_email.to_owned(),
254 password: setup.admin_password.to_owned(),
255 password_verify: setup.admin_password.to_owned(),
259 captcha_answer: None,
261 let login_response = register.perform(context, websocket_id).await?;
262 info!("Admin {} created", setup.admin_username);
264 let create_site = CreateSite {
265 name: setup.site_name.to_owned(),
269 enable_downvotes: true,
270 open_registration: true,
272 auth: login_response.jwt,
274 create_site.perform(context, websocket_id).await?;
275 info!("Site {} created", setup.site_name);
276 Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
281 let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
283 // Make sure the site creator is the top admin
284 if let Some(site_view) = site_view.to_owned() {
285 let site_creator_id = site_view.creator_id;
286 // TODO investigate why this is sometimes coming back null
287 // Maybe user_.admin isn't being set to true?
288 if let Some(creator_index) = admins.iter().position(|r| r.id == site_creator_id) {
289 let creator_user = admins.remove(creator_index);
290 admins.insert(0, creator_user);
294 let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
298 .send(GetUsersOnline)
302 let my_user = get_user_from_jwt_opt(&data.auth, context.pool())
305 u.password_encrypted = "".to_string();
306 u.private_key = None;
316 version: version::VERSION.to_string(),
318 federated_instances: Settings::get().get_allowed_instances(),
323 #[async_trait::async_trait(?Send)]
324 impl Perform for Search {
325 type Response = SearchResponse;
329 context: &Data<LemmyContext>,
330 _websocket_id: Option<ConnectionId>,
331 ) -> Result<SearchResponse, LemmyError> {
332 let data: &Search = &self;
336 match search_by_apub_id(&data.q, context).await {
337 Ok(r) => return Ok(r),
338 Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
341 let user = get_user_from_jwt_opt(&data.auth, context.pool()).await?;
342 let user_id = user.map(|u| u.id);
344 let type_ = SearchType::from_str(&data.type_)?;
346 let mut posts = Vec::new();
347 let mut comments = Vec::new();
348 let mut communities = Vec::new();
349 let mut users = Vec::new();
351 // TODO no clean / non-nsfw searching rn
353 let q = data.q.to_owned();
354 let page = data.page;
355 let limit = data.limit;
356 let sort = SortType::from_str(&data.sort)?;
357 let community_id = data.community_id;
359 SearchType::Posts => {
360 posts = blocking(context.pool(), move |conn| {
361 PostQueryBuilder::create(conn)
364 .for_community_id(community_id)
373 SearchType::Comments => {
374 comments = blocking(context.pool(), move |conn| {
375 CommentQueryBuilder::create(&conn)
385 SearchType::Communities => {
386 communities = blocking(context.pool(), move |conn| {
387 CommunityQueryBuilder::create(conn)
396 SearchType::Users => {
397 users = blocking(context.pool(), move |conn| {
398 UserQueryBuilder::create(conn)
408 posts = blocking(context.pool(), move |conn| {
409 PostQueryBuilder::create(conn)
412 .for_community_id(community_id)
421 let q = data.q.to_owned();
422 let sort = SortType::from_str(&data.sort)?;
424 comments = blocking(context.pool(), move |conn| {
425 CommentQueryBuilder::create(conn)
435 let q = data.q.to_owned();
436 let sort = SortType::from_str(&data.sort)?;
438 communities = blocking(context.pool(), move |conn| {
439 CommunityQueryBuilder::create(conn)
448 let q = data.q.to_owned();
449 let sort = SortType::from_str(&data.sort)?;
451 users = blocking(context.pool(), move |conn| {
452 UserQueryBuilder::create(conn)
462 posts = blocking(context.pool(), move |conn| {
463 PostQueryBuilder::create(conn)
466 .for_community_id(community_id)
478 type_: data.type_.to_owned(),
487 #[async_trait::async_trait(?Send)]
488 impl Perform for TransferSite {
489 type Response = GetSiteResponse;
493 context: &Data<LemmyContext>,
494 _websocket_id: Option<ConnectionId>,
495 ) -> Result<GetSiteResponse, LemmyError> {
496 let data: &TransferSite = &self;
497 let mut user = get_user_from_jwt(&data.auth, context.pool()).await?;
499 is_admin(context.pool(), user.id).await?;
501 // TODO add a User_::read_safe() for this.
502 user.password_encrypted = "".to_string();
503 user.private_key = None;
504 user.public_key = None;
506 let read_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
508 // Make sure user is the creator
509 if read_site.creator_id != user.id {
510 return Err(APIError::err("not_an_admin").into());
513 let new_creator_id = data.user_id;
514 let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
515 if blocking(context.pool(), transfer_site).await?.is_err() {
516 return Err(APIError::err("couldnt_update_site").into());
520 let form = ModAddForm {
521 mod_user_id: user.id,
522 other_user_id: data.user_id,
523 removed: Some(false),
526 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
528 let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
530 let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
531 let creator_index = admins
533 .position(|r| r.id == site_view.creator_id)
534 .context(location_info!())?;
535 let creator_user = admins.remove(creator_index);
536 admins.insert(0, creator_user);
538 let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
541 site: Some(site_view),
545 version: version::VERSION.to_string(),
547 federated_instances: Settings::get().get_allowed_instances(),
552 #[async_trait::async_trait(?Send)]
553 impl Perform for GetSiteConfig {
554 type Response = GetSiteConfigResponse;
558 context: &Data<LemmyContext>,
559 _websocket_id: Option<ConnectionId>,
560 ) -> Result<GetSiteConfigResponse, LemmyError> {
561 let data: &GetSiteConfig = &self;
562 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
564 // Only let admins read this
565 is_admin(context.pool(), user.id).await?;
567 let config_hjson = Settings::read_config_file()?;
569 Ok(GetSiteConfigResponse { config_hjson })
573 #[async_trait::async_trait(?Send)]
574 impl Perform for SaveSiteConfig {
575 type Response = GetSiteConfigResponse;
579 context: &Data<LemmyContext>,
580 _websocket_id: Option<ConnectionId>,
581 ) -> Result<GetSiteConfigResponse, LemmyError> {
582 let data: &SaveSiteConfig = &self;
583 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
585 // Only let admins read this
586 let admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
587 let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
589 if !admin_ids.contains(&user.id) {
590 return Err(APIError::err("not_an_admin").into());
593 // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
594 let config_hjson = match Settings::save_config_file(&data.config_hjson) {
595 Ok(config_hjson) => config_hjson,
596 Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
599 Ok(GetSiteConfigResponse { config_hjson })