9 use actix_web::web::Data;
11 use lemmy_apub::fetcher::search_by_apub_id;
16 diesel_option_overwrite,
28 use lemmy_structs::{blocking, site::*, user::Register};
32 utils::{check_slurs, check_slurs_opt},
37 use lemmy_websocket::{
38 messages::{GetUsersOnline, SendAllMessage},
42 use log::{debug, info};
43 use std::str::FromStr;
45 #[async_trait::async_trait(?Send)]
46 impl Perform for ListCategories {
47 type Response = ListCategoriesResponse;
51 context: &Data<LemmyContext>,
52 _websocket_id: Option<ConnectionId>,
53 ) -> Result<ListCategoriesResponse, LemmyError> {
54 let _data: &ListCategories = &self;
56 let categories = blocking(context.pool(), move |conn| Category::list_all(conn)).await??;
59 Ok(ListCategoriesResponse { categories })
63 #[async_trait::async_trait(?Send)]
64 impl Perform for GetModlog {
65 type Response = GetModlogResponse;
69 context: &Data<LemmyContext>,
70 _websocket_id: Option<ConnectionId>,
71 ) -> Result<GetModlogResponse, LemmyError> {
72 let data: &GetModlog = &self;
74 let community_id = data.community_id;
75 let mod_user_id = data.mod_user_id;
77 let limit = data.limit;
78 let removed_posts = blocking(context.pool(), move |conn| {
79 ModRemovePostView::list(conn, community_id, mod_user_id, page, limit)
83 let locked_posts = blocking(context.pool(), move |conn| {
84 ModLockPostView::list(conn, community_id, mod_user_id, page, limit)
88 let stickied_posts = blocking(context.pool(), move |conn| {
89 ModStickyPostView::list(conn, community_id, mod_user_id, page, limit)
93 let removed_comments = blocking(context.pool(), move |conn| {
94 ModRemoveCommentView::list(conn, community_id, mod_user_id, page, limit)
98 let banned_from_community = blocking(context.pool(), move |conn| {
99 ModBanFromCommunityView::list(conn, community_id, mod_user_id, page, limit)
103 let added_to_community = blocking(context.pool(), move |conn| {
104 ModAddCommunityView::list(conn, community_id, mod_user_id, page, limit)
108 // These arrays are only for the full modlog, when a community isn't given
109 let (removed_communities, banned, added) = if data.community_id.is_none() {
110 blocking(context.pool(), move |conn| {
112 ModRemoveCommunityView::list(conn, mod_user_id, page, limit)?,
113 ModBanView::list(conn, mod_user_id, page, limit)?,
114 ModAddView::list(conn, mod_user_id, page, limit)?,
115 )) as Result<_, LemmyError>
119 (Vec::new(), Vec::new(), Vec::new())
123 Ok(GetModlogResponse {
129 banned_from_community,
137 #[async_trait::async_trait(?Send)]
138 impl Perform for CreateSite {
139 type Response = SiteResponse;
143 context: &Data<LemmyContext>,
144 _websocket_id: Option<ConnectionId>,
145 ) -> Result<SiteResponse, LemmyError> {
146 let data: &CreateSite = &self;
148 let read_site = move |conn: &'_ _| Site::read(conn, 1);
149 if blocking(context.pool(), read_site).await?.is_ok() {
150 return Err(APIError::err("site_already_exists").into());
153 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
155 check_slurs(&data.name)?;
156 check_slurs_opt(&data.description)?;
158 // Make sure user is an admin
159 is_admin(context.pool(), user.id).await?;
161 let site_form = SiteForm {
162 name: data.name.to_owned(),
163 description: data.description.to_owned(),
164 icon: Some(data.icon.to_owned()),
165 banner: Some(data.banner.to_owned()),
167 enable_downvotes: data.enable_downvotes,
168 open_registration: data.open_registration,
169 enable_nsfw: data.enable_nsfw,
173 let create_site = move |conn: &'_ _| Site::create(conn, &site_form);
174 if blocking(context.pool(), create_site).await?.is_err() {
175 return Err(APIError::err("site_already_exists").into());
178 let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
180 Ok(SiteResponse { site: site_view })
184 #[async_trait::async_trait(?Send)]
185 impl Perform for EditSite {
186 type Response = SiteResponse;
189 context: &Data<LemmyContext>,
190 websocket_id: Option<ConnectionId>,
191 ) -> Result<SiteResponse, LemmyError> {
192 let data: &EditSite = &self;
193 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
195 check_slurs(&data.name)?;
196 check_slurs_opt(&data.description)?;
198 // Make sure user is an admin
199 is_admin(context.pool(), user.id).await?;
201 let found_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
203 let icon = diesel_option_overwrite(&data.icon);
204 let banner = diesel_option_overwrite(&data.banner);
206 let site_form = SiteForm {
207 name: data.name.to_owned(),
208 description: data.description.to_owned(),
211 creator_id: found_site.creator_id,
212 updated: Some(naive_now()),
213 enable_downvotes: data.enable_downvotes,
214 open_registration: data.open_registration,
215 enable_nsfw: data.enable_nsfw,
218 let update_site = move |conn: &'_ _| Site::update(conn, 1, &site_form);
219 if blocking(context.pool(), update_site).await?.is_err() {
220 return Err(APIError::err("couldnt_update_site").into());
223 let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
225 let res = SiteResponse { site: site_view };
227 context.chat_server().do_send(SendAllMessage {
228 op: UserOperation::EditSite,
229 response: res.clone(),
237 #[async_trait::async_trait(?Send)]
238 impl Perform for GetSite {
239 type Response = GetSiteResponse;
243 context: &Data<LemmyContext>,
244 websocket_id: Option<ConnectionId>,
245 ) -> Result<GetSiteResponse, LemmyError> {
246 let data: &GetSite = &self;
248 // TODO refactor this a little
249 let res = blocking(context.pool(), move |conn| Site::read(conn, 1)).await?;
250 let site_view = if res.is_ok() {
251 Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
252 } else if let Some(setup) = Settings::get().setup.as_ref() {
253 let register = Register {
254 username: setup.admin_username.to_owned(),
255 email: setup.admin_email.to_owned(),
256 password: setup.admin_password.to_owned(),
257 password_verify: setup.admin_password.to_owned(),
261 captcha_answer: None,
263 let login_response = register.perform(context, websocket_id).await?;
264 info!("Admin {} created", setup.admin_username);
266 let create_site = CreateSite {
267 name: setup.site_name.to_owned(),
271 enable_downvotes: true,
272 open_registration: true,
274 auth: login_response.jwt,
276 create_site.perform(context, websocket_id).await?;
277 info!("Site {} created", setup.site_name);
278 Some(blocking(context.pool(), move |conn| SiteView::read(conn)).await??)
283 let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
285 // Make sure the site creator is the top admin
286 if let Some(site_view) = site_view.to_owned() {
287 let site_creator_id = site_view.creator_id;
288 // TODO investigate why this is sometimes coming back null
289 // Maybe user_.admin isn't being set to true?
290 if let Some(creator_index) = admins.iter().position(|r| r.id == site_creator_id) {
291 let creator_user = admins.remove(creator_index);
292 admins.insert(0, creator_user);
296 let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
300 .send(GetUsersOnline)
304 let my_user = get_user_from_jwt_opt(&data.auth, context.pool())
307 u.password_encrypted = "".to_string();
308 u.private_key = None;
318 version: version::VERSION.to_string(),
320 federated_instances: linked_instances(context.pool()).await?,
325 #[async_trait::async_trait(?Send)]
326 impl Perform for Search {
327 type Response = SearchResponse;
331 context: &Data<LemmyContext>,
332 _websocket_id: Option<ConnectionId>,
333 ) -> Result<SearchResponse, LemmyError> {
334 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;
358 let community_name = data.community_name.to_owned();
360 SearchType::Posts => {
361 posts = blocking(context.pool(), move |conn| {
362 PostQueryBuilder::create(conn)
365 .for_community_id(community_id)
366 .for_community_name(community_name)
375 SearchType::Comments => {
376 comments = blocking(context.pool(), move |conn| {
377 CommentQueryBuilder::create(&conn)
387 SearchType::Communities => {
388 communities = blocking(context.pool(), move |conn| {
389 CommunityQueryBuilder::create(conn)
398 SearchType::Users => {
399 users = blocking(context.pool(), move |conn| {
400 UserQueryBuilder::create(conn)
410 posts = blocking(context.pool(), move |conn| {
411 PostQueryBuilder::create(conn)
414 .for_community_id(community_id)
415 .for_community_name(community_name)
424 let q = data.q.to_owned();
425 let sort = SortType::from_str(&data.sort)?;
427 comments = blocking(context.pool(), move |conn| {
428 CommentQueryBuilder::create(conn)
438 let q = data.q.to_owned();
439 let sort = SortType::from_str(&data.sort)?;
441 communities = blocking(context.pool(), move |conn| {
442 CommunityQueryBuilder::create(conn)
451 let q = data.q.to_owned();
452 let sort = SortType::from_str(&data.sort)?;
454 users = blocking(context.pool(), move |conn| {
455 UserQueryBuilder::create(conn)
465 posts = blocking(context.pool(), move |conn| {
466 PostQueryBuilder::create(conn)
469 .for_community_id(community_id)
470 .for_community_name(community_name)
482 type_: data.type_.to_owned(),
491 #[async_trait::async_trait(?Send)]
492 impl Perform for TransferSite {
493 type Response = GetSiteResponse;
497 context: &Data<LemmyContext>,
498 _websocket_id: Option<ConnectionId>,
499 ) -> Result<GetSiteResponse, LemmyError> {
500 let data: &TransferSite = &self;
501 let mut user = get_user_from_jwt(&data.auth, context.pool()).await?;
503 is_admin(context.pool(), user.id).await?;
505 // TODO add a User_::read_safe() for this.
506 user.password_encrypted = "".to_string();
507 user.private_key = None;
508 user.public_key = None;
510 let read_site = blocking(context.pool(), move |conn| Site::read(conn, 1)).await??;
512 // Make sure user is the creator
513 if read_site.creator_id != user.id {
514 return Err(APIError::err("not_an_admin").into());
517 let new_creator_id = data.user_id;
518 let transfer_site = move |conn: &'_ _| Site::transfer(conn, new_creator_id);
519 if blocking(context.pool(), transfer_site).await?.is_err() {
520 return Err(APIError::err("couldnt_update_site").into());
524 let form = ModAddForm {
525 mod_user_id: user.id,
526 other_user_id: data.user_id,
527 removed: Some(false),
530 blocking(context.pool(), move |conn| ModAdd::create(conn, &form)).await??;
532 let site_view = blocking(context.pool(), move |conn| SiteView::read(conn)).await??;
534 let mut admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
535 let creator_index = admins
537 .position(|r| r.id == site_view.creator_id)
538 .context(location_info!())?;
539 let creator_user = admins.remove(creator_index);
540 admins.insert(0, creator_user);
542 let banned = blocking(context.pool(), move |conn| UserView::banned(conn)).await??;
545 site: Some(site_view),
549 version: version::VERSION.to_string(),
551 federated_instances: linked_instances(context.pool()).await?,
556 #[async_trait::async_trait(?Send)]
557 impl Perform for GetSiteConfig {
558 type Response = GetSiteConfigResponse;
562 context: &Data<LemmyContext>,
563 _websocket_id: Option<ConnectionId>,
564 ) -> Result<GetSiteConfigResponse, LemmyError> {
565 let data: &GetSiteConfig = &self;
566 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
568 // Only let admins read this
569 is_admin(context.pool(), user.id).await?;
571 let config_hjson = Settings::read_config_file()?;
573 Ok(GetSiteConfigResponse { config_hjson })
577 #[async_trait::async_trait(?Send)]
578 impl Perform for SaveSiteConfig {
579 type Response = GetSiteConfigResponse;
583 context: &Data<LemmyContext>,
584 _websocket_id: Option<ConnectionId>,
585 ) -> Result<GetSiteConfigResponse, LemmyError> {
586 let data: &SaveSiteConfig = &self;
587 let user = get_user_from_jwt(&data.auth, context.pool()).await?;
589 // Only let admins read this
590 let admins = blocking(context.pool(), move |conn| UserView::admins(conn)).await??;
591 let admin_ids: Vec<i32> = admins.into_iter().map(|m| m.id).collect();
593 if !admin_ids.contains(&user.id) {
594 return Err(APIError::err("not_an_admin").into());
597 // Make sure docker doesn't have :ro at the end of the volume, so its not a read-only filesystem
598 let config_hjson = match Settings::save_config_file(&data.config_hjson) {
599 Ok(config_hjson) => config_hjson,
600 Err(_e) => return Err(APIError::err("couldnt_update_site").into()),
603 Ok(GetSiteConfigResponse { config_hjson })