From 682ca55e0c1385c548d8fb284038258fe81eefa8 Mon Sep 17 00:00:00 2001 From: drumlinish <138327213+drumlinish@users.noreply.github.com> Date: Mon, 3 Jul 2023 10:59:07 +0200 Subject: [PATCH 1/9] Fix quoting of max-file in docker-compose.yml (#3442) --- docker/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 985eae5e6..f9522e906 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -4,7 +4,7 @@ x-logging: &default-logging driver: "json-file" options: max-size: "50m" - max-file: 4 + max-file: "4" services: proxy: From 3578dab67f5d3b0f21c3a368280eb51521756a23 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Mon, 3 Jul 2023 11:01:41 +0200 Subject: [PATCH 2/9] Remove PerformApub trait (#3423) * Remove PerformApub trait This is completely useless now that websocket is gone. In the future I also plan to remove Perform and PerformCrud traits, but it will be difficult to do that while still compiling crates in parallel. * params need to use query --- crates/apub/src/activities/unfederated.rs | 35 --- crates/apub/src/api/list_comments.rs | 104 ++++--- crates/apub/src/api/list_posts.rs | 91 +++--- crates/apub/src/api/mod.rs | 21 +- crates/apub/src/api/read_community.rs | 119 ++++---- crates/apub/src/api/read_person.rs | 180 ++++++----- crates/apub/src/api/resolve_object.rs | 45 ++- crates/apub/src/api/search.rs | 355 +++++++++++----------- src/api_routes_http.rs | 47 ++- 9 files changed, 451 insertions(+), 546 deletions(-) diff --git a/crates/apub/src/activities/unfederated.rs b/crates/apub/src/activities/unfederated.rs index 4b9e6b5a9..24ff9b686 100644 --- a/crates/apub/src/activities/unfederated.rs +++ b/crates/apub/src/activities/unfederated.rs @@ -5,8 +5,6 @@ use lemmy_api_common::{ CommentResponse, DistinguishComment, GetComment, - GetComments, - GetCommentsResponse, ListCommentReports, ListCommentReportsResponse, ResolveCommentReport, @@ -15,7 +13,6 @@ use lemmy_api_common::{ community::{ CommunityResponse, CreateCommunity, - GetCommunity, GetCommunityResponse, ListCommunities, ListCommunitiesResponse, @@ -39,8 +36,6 @@ use lemmy_api_common::{ GetBannedPersons, GetCaptcha, GetCaptchaResponse, - GetPersonDetails, - GetPersonDetailsResponse, GetPersonMentions, GetPersonMentionsResponse, GetReplies, @@ -66,8 +61,6 @@ use lemmy_api_common::{ post::{ GetPost, GetPostResponse, - GetPosts, - GetPostsResponse, GetSiteMetadata, GetSiteMetadataResponse, ListPostReports, @@ -110,10 +103,6 @@ use lemmy_api_common::{ PurgePerson, PurgePost, RegistrationApplicationResponse, - ResolveObject, - ResolveObjectResponse, - Search, - SearchResponse, SiteResponse, }, }; @@ -122,10 +111,6 @@ impl SendActivity for Register { type Response = LoginResponse; } -impl SendActivity for GetPersonDetails { - type Response = GetPersonDetailsResponse; -} - impl SendActivity for GetPrivateMessages { type Response = PrivateMessagesResponse; } @@ -142,10 +127,6 @@ impl SendActivity for GetSite { type Response = GetSiteResponse; } -impl SendActivity for GetCommunity { - type Response = GetCommunityResponse; -} - impl SendActivity for ListCommunities { type Response = ListCommunitiesResponse; } @@ -158,18 +139,10 @@ impl SendActivity for GetPost { type Response = GetPostResponse; } -impl SendActivity for GetPosts { - type Response = GetPostsResponse; -} - impl SendActivity for GetComment { type Response = CommentResponse; } -impl SendActivity for GetComments { - type Response = GetCommentsResponse; -} - impl SendActivity for Login { type Response = LoginResponse; } @@ -286,14 +259,6 @@ impl SendActivity for PurgeComment { type Response = PurgeItemResponse; } -impl SendActivity for Search { - type Response = SearchResponse; -} - -impl SendActivity for ResolveObject { - type Response = ResolveObjectResponse; -} - impl SendActivity for TransferCommunity { type Response = GetCommunityResponse; } diff --git a/crates/apub/src/api/list_comments.rs b/crates/apub/src/api/list_comments.rs index b3a1b7cf2..487aefa9e 100644 --- a/crates/apub/src/api/list_comments.rs +++ b/crates/apub/src/api/list_comments.rs @@ -1,9 +1,10 @@ use crate::{ - api::{listing_type_with_default, PerformApub}, + api::listing_type_with_default, fetcher::resolve_actor_identifier, objects::community::ApubCommunity, }; use activitypub_federation::config::Data; +use actix_web::web::{Json, Query}; use lemmy_api_common::{ comment::{GetComments, GetCommentsResponse}, context::LemmyContext, @@ -16,61 +17,58 @@ use lemmy_db_schema::{ use lemmy_db_views::comment_view::CommentQuery; use lemmy_utils::error::LemmyError; -#[async_trait::async_trait] -impl PerformApub for GetComments { - type Response = GetCommentsResponse; +#[tracing::instrument(skip(context))] +pub async fn list_comments( + data: Query, + context: Data, +) -> Result, LemmyError> { + let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await; + let local_site = LocalSite::read(context.pool()).await?; + check_private_instance(&local_user_view, &local_site)?; - #[tracing::instrument(skip(context))] - async fn perform(&self, context: &Data) -> Result { - let data: &GetComments = self; - let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), context).await; - let local_site = LocalSite::read(context.pool()).await?; - check_private_instance(&local_user_view, &local_site)?; - - let community_id = if let Some(name) = &data.community_name { - resolve_actor_identifier::(name, context, &None, true) - .await - .ok() - .map(|c| c.id) - } else { - data.community_id - }; - let sort = data.sort; - let max_depth = data.max_depth; - let saved_only = data.saved_only; - let page = data.page; - let limit = data.limit; - let parent_id = data.parent_id; + let community_id = if let Some(name) = &data.community_name { + resolve_actor_identifier::(name, &context, &None, true) + .await + .ok() + .map(|c| c.id) + } else { + data.community_id + }; + let sort = data.sort; + let max_depth = data.max_depth; + let saved_only = data.saved_only; + let page = data.page; + let limit = data.limit; + let parent_id = data.parent_id; - let listing_type = listing_type_with_default(data.type_, &local_site, community_id)?; + let listing_type = listing_type_with_default(data.type_, &local_site, community_id)?; - // If a parent_id is given, fetch the comment to get the path - let parent_path = if let Some(parent_id) = parent_id { - Some(Comment::read(context.pool(), parent_id).await?.path) - } else { - None - }; + // If a parent_id is given, fetch the comment to get the path + let parent_path = if let Some(parent_id) = parent_id { + Some(Comment::read(context.pool(), parent_id).await?.path) + } else { + None + }; - let parent_path_cloned = parent_path.clone(); - let post_id = data.post_id; - let local_user = local_user_view.map(|l| l.local_user); - let comments = CommentQuery::builder() - .pool(context.pool()) - .listing_type(Some(listing_type)) - .sort(sort) - .max_depth(max_depth) - .saved_only(saved_only) - .community_id(community_id) - .parent_path(parent_path_cloned) - .post_id(post_id) - .local_user(local_user.as_ref()) - .page(page) - .limit(limit) - .build() - .list() - .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_get_comments"))?; + let parent_path_cloned = parent_path.clone(); + let post_id = data.post_id; + let local_user = local_user_view.map(|l| l.local_user); + let comments = CommentQuery::builder() + .pool(context.pool()) + .listing_type(Some(listing_type)) + .sort(sort) + .max_depth(max_depth) + .saved_only(saved_only) + .community_id(community_id) + .parent_path(parent_path_cloned) + .post_id(post_id) + .local_user(local_user.as_ref()) + .page(page) + .limit(limit) + .build() + .list() + .await + .map_err(|e| LemmyError::from_error_message(e, "couldnt_get_comments"))?; - Ok(GetCommentsResponse { comments }) - } + Ok(Json(GetCommentsResponse { comments })) } diff --git a/crates/apub/src/api/list_posts.rs b/crates/apub/src/api/list_posts.rs index 99c790c73..bde373f65 100644 --- a/crates/apub/src/api/list_posts.rs +++ b/crates/apub/src/api/list_posts.rs @@ -1,9 +1,10 @@ use crate::{ - api::{listing_type_with_default, PerformApub}, + api::listing_type_with_default, fetcher::resolve_actor_identifier, objects::community::ApubCommunity, }; use activitypub_federation::config::Data; +use actix_web::web::{Json, Query}; use lemmy_api_common::{ context::LemmyContext, post::{GetPosts, GetPostsResponse}, @@ -13,54 +14,50 @@ use lemmy_db_schema::source::{community::Community, local_site::LocalSite}; use lemmy_db_views::post_view::PostQuery; use lemmy_utils::error::LemmyError; -#[async_trait::async_trait] -impl PerformApub for GetPosts { - type Response = GetPostsResponse; +#[tracing::instrument(skip(context))] +pub async fn list_posts( + data: Query, + context: Data, +) -> Result, LemmyError> { + let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await; + let local_site = LocalSite::read(context.pool()).await?; - #[tracing::instrument(skip(context))] - async fn perform(&self, context: &Data) -> Result { - let data: &GetPosts = self; - let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), context).await; - let local_site = LocalSite::read(context.pool()).await?; + check_private_instance(&local_user_view, &local_site)?; - check_private_instance(&local_user_view, &local_site)?; + let sort = data.sort; - let sort = data.sort; - - let page = data.page; - let limit = data.limit; - let community_id = if let Some(name) = &data.community_name { - resolve_actor_identifier::(name, context, &None, true) - .await - .ok() - .map(|c| c.id) - } else { - data.community_id - }; - let saved_only = data.saved_only; - - let listing_type = listing_type_with_default(data.type_, &local_site, community_id)?; - - let is_mod_or_admin = - is_mod_or_admin_opt(context.pool(), local_user_view.as_ref(), community_id) - .await - .is_ok(); - - let posts = PostQuery::builder() - .pool(context.pool()) - .local_user(local_user_view.map(|l| l.local_user).as_ref()) - .listing_type(Some(listing_type)) - .sort(sort) - .community_id(community_id) - .saved_only(saved_only) - .page(page) - .limit(limit) - .is_mod_or_admin(Some(is_mod_or_admin)) - .build() - .list() + let page = data.page; + let limit = data.limit; + let community_id = if let Some(name) = &data.community_name { + resolve_actor_identifier::(name, &context, &None, true) .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_get_posts"))?; - - Ok(GetPostsResponse { posts }) - } + .ok() + .map(|c| c.id) + } else { + data.community_id + }; + let saved_only = data.saved_only; + + let listing_type = listing_type_with_default(data.type_, &local_site, community_id)?; + + let is_mod_or_admin = is_mod_or_admin_opt(context.pool(), local_user_view.as_ref(), community_id) + .await + .is_ok(); + + let posts = PostQuery::builder() + .pool(context.pool()) + .local_user(local_user_view.map(|l| l.local_user).as_ref()) + .listing_type(Some(listing_type)) + .sort(sort) + .community_id(community_id) + .saved_only(saved_only) + .page(page) + .limit(limit) + .is_mod_or_admin(Some(is_mod_or_admin)) + .build() + .list() + .await + .map_err(|e| LemmyError::from_error_message(e, "couldnt_get_posts"))?; + + Ok(Json(GetPostsResponse { posts })) } diff --git a/crates/apub/src/api/mod.rs b/crates/apub/src/api/mod.rs index d95c9b51b..705e81a30 100644 --- a/crates/apub/src/api/mod.rs +++ b/crates/apub/src/api/mod.rs @@ -1,21 +1,12 @@ -use activitypub_federation::config::Data; -use lemmy_api_common::context::LemmyContext; use lemmy_db_schema::{newtypes::CommunityId, source::local_site::LocalSite, ListingType}; use lemmy_utils::error::LemmyError; -mod list_comments; -mod list_posts; -mod read_community; -mod read_person; -mod resolve_object; -mod search; - -#[async_trait::async_trait] -pub trait PerformApub { - type Response: serde::ser::Serialize + Send; - - async fn perform(&self, context: &Data) -> Result; -} +pub mod list_comments; +pub mod list_posts; +pub mod read_community; +pub mod read_person; +pub mod resolve_object; +pub mod search; /// Returns default listing type, depending if the query is for frontpage or community. fn listing_type_with_default( diff --git a/crates/apub/src/api/read_community.rs b/crates/apub/src/api/read_community.rs index a19758990..e524694d3 100644 --- a/crates/apub/src/api/read_community.rs +++ b/crates/apub/src/api/read_community.rs @@ -1,9 +1,6 @@ -use crate::{ - api::PerformApub, - fetcher::resolve_actor_identifier, - objects::community::ApubCommunity, -}; +use crate::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity}; use activitypub_federation::config::Data; +use actix_web::web::{Json, Query}; use lemmy_api_common::{ community::{GetCommunity, GetCommunityResponse}, context::LemmyContext, @@ -18,78 +15,68 @@ use lemmy_db_schema::source::{ use lemmy_db_views_actor::structs::{CommunityModeratorView, CommunityView}; use lemmy_utils::error::LemmyError; -#[async_trait::async_trait] -impl PerformApub for GetCommunity { - type Response = GetCommunityResponse; +#[tracing::instrument(skip(context))] +pub async fn read_community( + data: Query, + context: Data, +) -> Result, LemmyError> { + let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await; + let local_site = LocalSite::read(context.pool()).await?; - #[tracing::instrument(skip(context))] - async fn perform( - &self, - context: &Data, - ) -> Result { - let data: &GetCommunity = self; - let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), context).await; - let local_site = LocalSite::read(context.pool()).await?; + if data.name.is_none() && data.id.is_none() { + return Err(LemmyError::from_message("no_id_given")); + } - if data.name.is_none() && data.id.is_none() { - return Err(LemmyError::from_message("no_id_given")); - } + check_private_instance(&local_user_view, &local_site)?; - check_private_instance(&local_user_view, &local_site)?; + let person_id = local_user_view.as_ref().map(|u| u.person.id); - let person_id = local_user_view.as_ref().map(|u| u.person.id); + let community_id = match data.id { + Some(id) => id, + None => { + let name = data.name.clone().unwrap_or_else(|| "main".to_string()); + resolve_actor_identifier::(&name, &context, &local_user_view, true) + .await + .map_err(|e| e.with_message("couldnt_find_community"))? + .id + } + }; - let community_id = match data.id { - Some(id) => id, - None => { - let name = data.name.clone().unwrap_or_else(|| "main".to_string()); - resolve_actor_identifier::(&name, context, &local_user_view, true) - .await - .map_err(|e| e.with_message("couldnt_find_community"))? - .id - } - }; + let is_mod_or_admin = + is_mod_or_admin_opt(context.pool(), local_user_view.as_ref(), Some(community_id)) + .await + .is_ok(); - let is_mod_or_admin = - is_mod_or_admin_opt(context.pool(), local_user_view.as_ref(), Some(community_id)) - .await - .is_ok(); + let community_view = CommunityView::read( + context.pool(), + community_id, + person_id, + Some(is_mod_or_admin), + ) + .await + .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; - let community_view = CommunityView::read( - context.pool(), - community_id, - person_id, - Some(is_mod_or_admin), - ) + let moderators = CommunityModeratorView::for_community(context.pool(), community_id) .await .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; - let moderators = CommunityModeratorView::for_community(context.pool(), community_id) - .await - .map_err(|e| LemmyError::from_error_message(e, "couldnt_find_community"))?; - - let site_id = - Site::instance_actor_id_from_url(community_view.community.actor_id.clone().into()); - let mut site = Site::read_from_apub_id(context.pool(), &site_id.into()).await?; - // no need to include metadata for local site (its already available through other endpoints). - // this also prevents us from leaking the federation private key. - if let Some(s) = &site { - if s.actor_id.domain() == Some(context.settings().hostname.as_ref()) { - site = None; - } + let site_id = Site::instance_actor_id_from_url(community_view.community.actor_id.clone().into()); + let mut site = Site::read_from_apub_id(context.pool(), &site_id.into()).await?; + // no need to include metadata for local site (its already available through other endpoints). + // this also prevents us from leaking the federation private key. + if let Some(s) = &site { + if s.actor_id.domain() == Some(context.settings().hostname.as_ref()) { + site = None; } + } - let community_id = community_view.community.id; - let discussion_languages = CommunityLanguage::read(context.pool(), community_id).await?; - - let res = GetCommunityResponse { - community_view, - site, - moderators, - discussion_languages, - }; + let community_id = community_view.community.id; + let discussion_languages = CommunityLanguage::read(context.pool(), community_id).await?; - // Return the jwt - Ok(res) - } + Ok(Json(GetCommunityResponse { + community_view, + site, + moderators, + discussion_languages, + })) } diff --git a/crates/apub/src/api/read_person.rs b/crates/apub/src/api/read_person.rs index 1e4797fa4..fb4755e38 100644 --- a/crates/apub/src/api/read_person.rs +++ b/crates/apub/src/api/read_person.rs @@ -1,5 +1,6 @@ -use crate::{api::PerformApub, fetcher::resolve_actor_identifier, objects::person::ApubPerson}; +use crate::{fetcher::resolve_actor_identifier, objects::person::ApubPerson}; use activitypub_federation::config::Data; +use actix_web::web::{Json, Query}; use lemmy_api_common::{ context::LemmyContext, person::{GetPersonDetails, GetPersonDetailsResponse}, @@ -13,108 +14,101 @@ use lemmy_db_views::{comment_view::CommentQuery, post_view::PostQuery}; use lemmy_db_views_actor::structs::{CommunityModeratorView, PersonView}; use lemmy_utils::error::LemmyError; -#[async_trait::async_trait] -impl PerformApub for GetPersonDetails { - type Response = GetPersonDetailsResponse; - - #[tracing::instrument(skip(self, context))] - async fn perform( - &self, - context: &Data, - ) -> Result { - let data: &GetPersonDetails = self; - - // Check to make sure a person name or an id is given - if data.username.is_none() && data.person_id.is_none() { - return Err(LemmyError::from_message("no_id_given")); - } +#[tracing::instrument(skip(context))] +pub async fn read_person( + data: Query, + context: Data, +) -> Result, LemmyError> { + // Check to make sure a person name or an id is given + if data.username.is_none() && data.person_id.is_none() { + return Err(LemmyError::from_message("no_id_given")); + } - let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), context).await; - let local_site = LocalSite::read(context.pool()).await?; - let is_admin = local_user_view.as_ref().map(|luv| is_admin(luv).is_ok()); + let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await; + let local_site = LocalSite::read(context.pool()).await?; + let is_admin = local_user_view.as_ref().map(|luv| is_admin(luv).is_ok()); - check_private_instance(&local_user_view, &local_site)?; + check_private_instance(&local_user_view, &local_site)?; - let person_details_id = match data.person_id { - Some(id) => id, - None => { - if let Some(username) = &data.username { - resolve_actor_identifier::(username, context, &local_user_view, true) - .await - .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))? - .id - } else { - return Err(LemmyError::from_message( - "couldnt_find_that_username_or_email", - )); - } + let person_details_id = match data.person_id { + Some(id) => id, + None => { + if let Some(username) = &data.username { + resolve_actor_identifier::(username, &context, &local_user_view, true) + .await + .map_err(|e| e.with_message("couldnt_find_that_username_or_email"))? + .id + } else { + return Err(LemmyError::from_message( + "couldnt_find_that_username_or_email", + )); } - }; + } + }; - // You don't need to return settings for the user, since this comes back with GetSite - // `my_user` - let person_view = PersonView::read(context.pool(), person_details_id).await?; + // You don't need to return settings for the user, since this comes back with GetSite + // `my_user` + let person_view = PersonView::read(context.pool(), person_details_id).await?; - let sort = data.sort; - let page = data.page; - let limit = data.limit; - let saved_only = data.saved_only; - let community_id = data.community_id; - let local_user = local_user_view.map(|l| l.local_user); - let local_user_clone = local_user.clone(); + let sort = data.sort; + let page = data.page; + let limit = data.limit; + let saved_only = data.saved_only; + let community_id = data.community_id; + let local_user = local_user_view.map(|l| l.local_user); + let local_user_clone = local_user.clone(); - let posts_query = PostQuery::builder() - .pool(context.pool()) - .sort(sort) - .saved_only(saved_only) - .local_user(local_user.as_ref()) - .community_id(community_id) - .is_mod_or_admin(is_admin) - .page(page) - .limit(limit); + let posts_query = PostQuery::builder() + .pool(context.pool()) + .sort(sort) + .saved_only(saved_only) + .local_user(local_user.as_ref()) + .community_id(community_id) + .is_mod_or_admin(is_admin) + .page(page) + .limit(limit); - // If its saved only, you don't care what creator it was - // Or, if its not saved, then you only want it for that specific creator - let posts = if !saved_only.unwrap_or(false) { - posts_query - .creator_id(Some(person_details_id)) - .build() - .list() - } else { - posts_query.build().list() - } - .await?; + // If its saved only, you don't care what creator it was + // Or, if its not saved, then you only want it for that specific creator + let posts = if !saved_only.unwrap_or(false) { + posts_query + .creator_id(Some(person_details_id)) + .build() + .list() + } else { + posts_query.build().list() + } + .await?; - let comments_query = CommentQuery::builder() - .pool(context.pool()) - .local_user(local_user_clone.as_ref()) - .sort(sort.map(post_to_comment_sort_type)) - .saved_only(saved_only) - .show_deleted_and_removed(Some(false)) - .community_id(community_id) - .page(page) - .limit(limit); + let comments_query = CommentQuery::builder() + .pool(context.pool()) + .local_user(local_user_clone.as_ref()) + .sort(sort.map(post_to_comment_sort_type)) + .saved_only(saved_only) + .show_deleted_and_removed(Some(false)) + .community_id(community_id) + .page(page) + .limit(limit); - // If its saved only, you don't care what creator it was - // Or, if its not saved, then you only want it for that specific creator - let comments = if !saved_only.unwrap_or(false) { - comments_query - .creator_id(Some(person_details_id)) - .build() - .list() - } else { - comments_query.build().list() - } - .await?; + // If its saved only, you don't care what creator it was + // Or, if its not saved, then you only want it for that specific creator + let comments = if !saved_only.unwrap_or(false) { + comments_query + .creator_id(Some(person_details_id)) + .build() + .list() + } else { + comments_query.build().list() + } + .await?; - let moderates = CommunityModeratorView::for_person(context.pool(), person_details_id).await?; + let moderates = CommunityModeratorView::for_person(context.pool(), person_details_id).await?; - // Return the jwt - Ok(GetPersonDetailsResponse { - person_view, - moderates, - comments, - posts, - }) - } + // Return the jwt + Ok(Json(GetPersonDetailsResponse { + person_view, + moderates, + comments, + posts, + })) } diff --git a/crates/apub/src/api/resolve_object.rs b/crates/apub/src/api/resolve_object.rs index 951cd4551..09689def1 100644 --- a/crates/apub/src/api/resolve_object.rs +++ b/crates/apub/src/api/resolve_object.rs @@ -1,8 +1,6 @@ -use crate::{ - api::PerformApub, - fetcher::search::{search_query_to_object_id, SearchableObjects}, -}; +use crate::fetcher::search::{search_query_to_object_id, SearchableObjects}; use activitypub_federation::config::Data; +use actix_web::web::{Json, Query}; use diesel::NotFound; use lemmy_api_common::{ context::LemmyContext, @@ -14,34 +12,29 @@ use lemmy_db_views::structs::{CommentView, PostView}; use lemmy_db_views_actor::structs::{CommunityView, PersonView}; use lemmy_utils::error::LemmyError; -#[async_trait::async_trait] -impl PerformApub for ResolveObject { - type Response = ResolveObjectResponse; - - #[tracing::instrument(skip(context))] - async fn perform( - &self, - context: &Data, - ) -> Result { - let local_user_view = local_user_view_from_jwt(&self.auth, context).await?; - let local_site = LocalSite::read(context.pool()).await?; - let person_id = local_user_view.person.id; - check_private_instance(&Some(local_user_view), &local_site)?; +#[tracing::instrument(skip(context))] +pub async fn resolve_object( + data: Query, + context: Data, +) -> Result, LemmyError> { + let local_user_view = local_user_view_from_jwt(&data.auth, &context).await?; + let local_site = LocalSite::read(context.pool()).await?; + let person_id = local_user_view.person.id; + check_private_instance(&Some(local_user_view), &local_site)?; - let res = search_query_to_object_id(&self.q, context) - .await - .map_err(|e| e.with_message("couldnt_find_object"))?; - convert_response(res, person_id, context.pool()) - .await - .map_err(|e| e.with_message("couldnt_find_object")) - } + let res = search_query_to_object_id(&data.q, &context) + .await + .map_err(|e| e.with_message("couldnt_find_object"))?; + convert_response(res, person_id, context.pool()) + .await + .map_err(|e| e.with_message("couldnt_find_object")) } async fn convert_response( object: SearchableObjects, user_id: PersonId, pool: &DbPool, -) -> Result { +) -> Result, LemmyError> { use SearchableObjects::*; let removed_or_deleted; let mut res = ResolveObjectResponse::default(); @@ -67,5 +60,5 @@ async fn convert_response( if removed_or_deleted { return Err(NotFound {}.into()); } - Ok(res) + Ok(Json(res)) } diff --git a/crates/apub/src/api/search.rs b/crates/apub/src/api/search.rs index 025d1408a..777a7013b 100644 --- a/crates/apub/src/api/search.rs +++ b/crates/apub/src/api/search.rs @@ -1,9 +1,6 @@ -use crate::{ - api::PerformApub, - fetcher::resolve_actor_identifier, - objects::community::ApubCommunity, -}; +use crate::{fetcher::resolve_actor_identifier, objects::community::ApubCommunity}; use activitypub_federation::config::Data; +use actix_web::web::{Json, Query}; use lemmy_api_common::{ context::LemmyContext, site::{Search, SearchResponse}, @@ -18,78 +15,142 @@ use lemmy_db_views::{comment_view::CommentQuery, post_view::PostQuery}; use lemmy_db_views_actor::{community_view::CommunityQuery, person_view::PersonQuery}; use lemmy_utils::error::LemmyError; -#[async_trait::async_trait] -impl PerformApub for Search { - type Response = SearchResponse; - - #[tracing::instrument(skip(context))] - async fn perform(&self, context: &Data) -> Result { - let data: &Search = self; - - let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), context).await; - let local_site = LocalSite::read(context.pool()).await?; - - check_private_instance(&local_user_view, &local_site)?; - - let is_admin = local_user_view.as_ref().map(|luv| is_admin(luv).is_ok()); - - let mut posts = Vec::new(); - let mut comments = Vec::new(); - let mut communities = Vec::new(); - let mut users = Vec::new(); - - // TODO no clean / non-nsfw searching rn - - let q = data.q.clone(); - let page = data.page; - let limit = data.limit; - let sort = data.sort; - let listing_type = data.listing_type; - let search_type = data.type_.unwrap_or(SearchType::All); - let community_id = if let Some(name) = &data.community_name { - resolve_actor_identifier::(name, context, &local_user_view, false) - .await - .ok() - .map(|c| c.id) - } else { - data.community_id - }; - let creator_id = data.creator_id; - let local_user = local_user_view.map(|l| l.local_user); - match search_type { - SearchType::Posts => { - posts = PostQuery::builder() - .pool(context.pool()) - .sort(sort) - .listing_type(listing_type) - .community_id(community_id) - .creator_id(creator_id) - .local_user(local_user.as_ref()) - .search_term(Some(q)) - .is_mod_or_admin(is_admin) - .page(page) - .limit(limit) - .build() - .list() - .await?; - } - SearchType::Comments => { - comments = CommentQuery::builder() - .pool(context.pool()) - .sort(sort.map(post_to_comment_sort_type)) - .listing_type(listing_type) - .search_term(Some(q)) - .community_id(community_id) - .creator_id(creator_id) - .local_user(local_user.as_ref()) - .page(page) - .limit(limit) - .build() - .list() - .await?; - } - SearchType::Communities => { - communities = CommunityQuery::builder() +#[tracing::instrument(skip(context))] +pub async fn search( + data: Query, + context: Data, +) -> Result, LemmyError> { + let local_user_view = local_user_view_from_jwt_opt(data.auth.as_ref(), &context).await; + let local_site = LocalSite::read(context.pool()).await?; + + check_private_instance(&local_user_view, &local_site)?; + + let is_admin = local_user_view.as_ref().map(|luv| is_admin(luv).is_ok()); + + let mut posts = Vec::new(); + let mut comments = Vec::new(); + let mut communities = Vec::new(); + let mut users = Vec::new(); + + // TODO no clean / non-nsfw searching rn + + let q = data.q.clone(); + let page = data.page; + let limit = data.limit; + let sort = data.sort; + let listing_type = data.listing_type; + let search_type = data.type_.unwrap_or(SearchType::All); + let community_id = if let Some(name) = &data.community_name { + resolve_actor_identifier::(name, &context, &local_user_view, false) + .await + .ok() + .map(|c| c.id) + } else { + data.community_id + }; + let creator_id = data.creator_id; + let local_user = local_user_view.map(|l| l.local_user); + match search_type { + SearchType::Posts => { + posts = PostQuery::builder() + .pool(context.pool()) + .sort(sort) + .listing_type(listing_type) + .community_id(community_id) + .creator_id(creator_id) + .local_user(local_user.as_ref()) + .search_term(Some(q)) + .is_mod_or_admin(is_admin) + .page(page) + .limit(limit) + .build() + .list() + .await?; + } + SearchType::Comments => { + comments = CommentQuery::builder() + .pool(context.pool()) + .sort(sort.map(post_to_comment_sort_type)) + .listing_type(listing_type) + .search_term(Some(q)) + .community_id(community_id) + .creator_id(creator_id) + .local_user(local_user.as_ref()) + .page(page) + .limit(limit) + .build() + .list() + .await?; + } + SearchType::Communities => { + communities = CommunityQuery::builder() + .pool(context.pool()) + .sort(sort) + .listing_type(listing_type) + .search_term(Some(q)) + .local_user(local_user.as_ref()) + .is_mod_or_admin(is_admin) + .page(page) + .limit(limit) + .build() + .list() + .await?; + } + SearchType::Users => { + users = PersonQuery::builder() + .pool(context.pool()) + .sort(sort) + .search_term(Some(q)) + .page(page) + .limit(limit) + .build() + .list() + .await?; + } + SearchType::All => { + // If the community or creator is included, dont search communities or users + let community_or_creator_included = + data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some(); + + let local_user_ = local_user.clone(); + posts = PostQuery::builder() + .pool(context.pool()) + .sort(sort) + .listing_type(listing_type) + .community_id(community_id) + .creator_id(creator_id) + .local_user(local_user_.as_ref()) + .search_term(Some(q)) + .is_mod_or_admin(is_admin) + .page(page) + .limit(limit) + .build() + .list() + .await?; + + let q = data.q.clone(); + + let local_user_ = local_user.clone(); + comments = CommentQuery::builder() + .pool(context.pool()) + .sort(sort.map(post_to_comment_sort_type)) + .listing_type(listing_type) + .search_term(Some(q)) + .community_id(community_id) + .creator_id(creator_id) + .local_user(local_user_.as_ref()) + .page(page) + .limit(limit) + .build() + .list() + .await?; + + let q = data.q.clone(); + + communities = if community_or_creator_included { + vec![] + } else { + CommunityQuery::builder() .pool(context.pool()) .sort(sort) .listing_type(listing_type) @@ -100,116 +161,48 @@ impl PerformApub for Search { .limit(limit) .build() .list() - .await?; - } - SearchType::Users => { - users = PersonQuery::builder() - .pool(context.pool()) - .sort(sort) - .search_term(Some(q)) - .page(page) - .limit(limit) - .build() - .list() - .await?; - } - SearchType::All => { - // If the community or creator is included, dont search communities or users - let community_or_creator_included = - data.community_id.is_some() || data.community_name.is_some() || data.creator_id.is_some(); - - let local_user_ = local_user.clone(); - posts = PostQuery::builder() - .pool(context.pool()) - .sort(sort) - .listing_type(listing_type) - .community_id(community_id) - .creator_id(creator_id) - .local_user(local_user_.as_ref()) - .search_term(Some(q)) - .is_mod_or_admin(is_admin) - .page(page) - .limit(limit) - .build() - .list() - .await?; + .await? + }; - let q = data.q.clone(); + let q = data.q.clone(); - let local_user_ = local_user.clone(); - comments = CommentQuery::builder() - .pool(context.pool()) - .sort(sort.map(post_to_comment_sort_type)) - .listing_type(listing_type) - .search_term(Some(q)) - .community_id(community_id) - .creator_id(creator_id) - .local_user(local_user_.as_ref()) - .page(page) - .limit(limit) - .build() - .list() - .await?; - - let q = data.q.clone(); - - communities = if community_or_creator_included { - vec![] - } else { - CommunityQuery::builder() - .pool(context.pool()) - .sort(sort) - .listing_type(listing_type) - .search_term(Some(q)) - .local_user(local_user.as_ref()) - .is_mod_or_admin(is_admin) - .page(page) - .limit(limit) - .build() - .list() - .await? - }; - - let q = data.q.clone(); - - users = if community_or_creator_included { - vec![] - } else { - PersonQuery::builder() - .pool(context.pool()) - .sort(sort) - .search_term(Some(q)) - .page(page) - .limit(limit) - .build() - .list() - .await? - }; - } - SearchType::Url => { - posts = PostQuery::builder() + users = if community_or_creator_included { + vec![] + } else { + PersonQuery::builder() .pool(context.pool()) .sort(sort) - .listing_type(listing_type) - .community_id(community_id) - .creator_id(creator_id) - .url_search(Some(q)) - .is_mod_or_admin(is_admin) + .search_term(Some(q)) .page(page) .limit(limit) .build() .list() - .await?; - } - }; - - // Return the jwt - Ok(SearchResponse { - type_: search_type, - comments, - posts, - communities, - users, - }) - } + .await? + }; + } + SearchType::Url => { + posts = PostQuery::builder() + .pool(context.pool()) + .sort(sort) + .listing_type(listing_type) + .community_id(community_id) + .creator_id(creator_id) + .url_search(Some(q)) + .is_mod_or_admin(is_admin) + .page(page) + .limit(limit) + .build() + .list() + .await?; + } + }; + + // Return the jwt + Ok(Json(SearchResponse { + type_: search_type, + comments, + posts, + communities, + users, + })) } diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index 375630a92..ca0fa4c22 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -9,7 +9,6 @@ use lemmy_api_common::{ DistinguishComment, EditComment, GetComment, - GetComments, ListCommentReports, RemoveComment, ResolveCommentReport, @@ -23,7 +22,6 @@ use lemmy_api_common::{ DeleteCommunity, EditCommunity, FollowCommunity, - GetCommunity, HideCommunity, ListCommunities, RemoveCommunity, @@ -39,7 +37,6 @@ use lemmy_api_common::{ DeleteAccount, GetBannedPersons, GetCaptcha, - GetPersonDetails, GetPersonMentions, GetReplies, GetReportCount, @@ -62,7 +59,6 @@ use lemmy_api_common::{ EditPost, FeaturePost, GetPost, - GetPosts, GetSiteMetadata, ListPostReports, LockPost, @@ -95,12 +91,20 @@ use lemmy_api_common::{ PurgeCommunity, PurgePerson, PurgePost, - ResolveObject, - Search, }, }; use lemmy_api_crud::PerformCrud; -use lemmy_apub::{api::PerformApub, SendActivity}; +use lemmy_apub::{ + api::{ + list_comments::list_comments, + list_posts::list_posts, + read_community::read_community, + read_person::read_person, + resolve_object::resolve_object, + search::search, + }, + SendActivity, +}; use lemmy_utils::rate_limit::RateLimitCell; use serde::Deserialize; @@ -124,12 +128,12 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .service( web::resource("/search") .wrap(rate_limit.search()) - .route(web::get().to(route_get_apub::)), + .route(web::get().to(search)), ) .service( web::resource("/resolve_object") .wrap(rate_limit.message()) - .route(web::get().to(route_get_apub::)), + .route(web::get().to(resolve_object)), ) // Community .service( @@ -141,7 +145,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .service( web::scope("/community") .wrap(rate_limit.message()) - .route("", web::get().to(route_get_apub::)) + .route("", web::get().to(read_community)) .route("", web::put().to(route_post_crud::)) .route("/hide", web::put().to(route_post::)) .route("/list", web::get().to(route_get_crud::)) @@ -186,7 +190,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { ) .route("/lock", web::post().to(route_post::)) .route("/feature", web::post().to(route_post::)) - .route("/list", web::get().to(route_get_apub::)) + .route("/list", web::get().to(list_posts)) .route("/like", web::post().to(route_post::)) .route("/save", web::put().to(route_post::)) .route("/report", web::post().to(route_post::)) @@ -225,7 +229,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { ) .route("/like", web::post().to(route_post::)) .route("/save", web::put().to(route_post::)) - .route("/list", web::get().to(route_get_apub::)) + .route("/list", web::get().to(list_comments)) .route("/report", web::post().to(route_post::)) .route( "/report/resolve", @@ -283,7 +287,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .service( web::scope("/user") .wrap(rate_limit.message()) - .route("", web::get().to(route_get_apub::)) + .route("", web::get().to(read_person)) .route("/mention", web::get().to(route_get::)) .route( "/mention/mark_as_read", @@ -398,23 +402,6 @@ where perform::(data.0, context, apub_data).await } -async fn route_get_apub<'a, Data>( - data: web::Query, - context: activitypub_federation::config::Data, -) -> Result -where - Data: PerformApub - + SendActivity::Response> - + Clone - + Deserialize<'a> - + Send - + 'static, -{ - let res = data.perform(&context).await?; - SendActivity::send_activity(&data.0, &res, &context).await?; - Ok(HttpResponse::Ok().json(res)) -} - async fn route_post<'a, Data>( data: web::Json, context: web::Data, From fc60b82f8264301202d6c118e8bf3c1de3f49968 Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Mon, 3 Jul 2023 11:45:53 +0200 Subject: [PATCH 3/9] Fix formatting for latest nightly (#ref 3467) --- crates/utils/src/rate_limit/rate_limiter.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/utils/src/rate_limit/rate_limiter.rs b/crates/utils/src/rate_limit/rate_limiter.rs index 12f264fae..cea05bc08 100644 --- a/crates/utils/src/rate_limit/rate_limiter.rs +++ b/crates/utils/src/rate_limit/rate_limiter.rs @@ -195,7 +195,9 @@ impl RateLimitStorage { /// Remove buckets older than the given duration pub(super) fn remove_older_than(&mut self, duration: Duration, now: InstantSecs) { // Only retain buckets that were last used after `instant` - let Some(instant) = now.to_instant().checked_sub(duration) else { return }; + let Some(instant) = now.to_instant().checked_sub(duration) else { + return; + }; let is_recently_used = |group: &RateLimitedGroup<_>| { group From 6405761891709cc98271452ac98cccf8219adb6e Mon Sep 17 00:00:00 2001 From: Nutomic Date: Mon, 3 Jul 2023 12:03:20 +0200 Subject: [PATCH 4/9] Mark follow as pending when subscribing to remote community (fixes #3384) (#3406) --- crates/api/src/community/follow.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/crates/api/src/community/follow.rs b/crates/api/src/community/follow.rs index ae1ed6a4b..39feeff25 100644 --- a/crates/api/src/community/follow.rs +++ b/crates/api/src/community/follow.rs @@ -26,19 +26,27 @@ impl Perform for FollowCommunity { let community_id = data.community_id; let community = Community::read(context.pool(), community_id).await?; - let community_follower_form = CommunityFollowerForm { + let mut community_follower_form = CommunityFollowerForm { community_id: data.community_id, person_id: local_user_view.person.id, pending: false, }; - if community.local && data.follow { - check_community_ban(local_user_view.person.id, community_id, context.pool()).await?; - check_community_deleted_or_removed(community_id, context.pool()).await?; + if data.follow { + if community.local { + check_community_ban(local_user_view.person.id, community_id, context.pool()).await?; + check_community_deleted_or_removed(community_id, context.pool()).await?; - CommunityFollower::follow(context.pool(), &community_follower_form) - .await - .map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?; + CommunityFollower::follow(context.pool(), &community_follower_form) + .await + .map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?; + } else { + // Mark as pending, the actual federation activity is sent via `SendActivity` handler + community_follower_form.pending = true; + CommunityFollower::follow(context.pool(), &community_follower_form) + .await + .map_err(|e| LemmyError::from_error_message(e, "community_follower_already_exists"))?; + } } if !data.follow { CommunityFollower::unfollow(context.pool(), &community_follower_form) From cb91eedd24a0a5b9c8275309461c886ee9a7b3ef Mon Sep 17 00:00:00 2001 From: Nutomic Date: Mon, 3 Jul 2023 15:14:01 +0200 Subject: [PATCH 5/9] Use serde(skip) instead of skip_serializing, add placeholder values (#3362) * Use serde(skip) instead of skip_serializing The latter breaks lemmy_crawler as the field is not included in the Lemmy API, but is required when attempting to parse API responses. Should only use serde(skip) to avoid this problem * use option * add placeholders * no unwrap --- crates/db_schema/src/source/community.rs | 9 ++++++--- crates/db_schema/src/source/mod.rs | 13 +++++++++++++ crates/db_schema/src/source/person.rs | 7 +++++-- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/crates/db_schema/src/source/community.rs b/crates/db_schema/src/source/community.rs index c24a64597..64b30b2a8 100644 --- a/crates/db_schema/src/source/community.rs +++ b/crates/db_schema/src/source/community.rs @@ -1,6 +1,9 @@ -use crate::newtypes::{CommunityId, DbUrl, InstanceId, PersonId}; #[cfg(feature = "full")] use crate::schema::{community, community_follower, community_moderator, community_person_ban}; +use crate::{ + newtypes::{CommunityId, DbUrl, InstanceId, PersonId}, + source::placeholder_apub_url, +}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -42,9 +45,9 @@ pub struct Community { pub icon: Option, /// A URL for a banner. pub banner: Option, - #[serde(skip_serializing)] + #[serde(skip, default = "placeholder_apub_url")] pub followers_url: DbUrl, - #[serde(skip_serializing)] + #[serde(skip, default = "placeholder_apub_url")] pub inbox_url: DbUrl, #[serde(skip)] pub shared_inbox_url: Option, diff --git a/crates/db_schema/src/source/mod.rs b/crates/db_schema/src/source/mod.rs index 926e23e73..a46f4fb40 100644 --- a/crates/db_schema/src/source/mod.rs +++ b/crates/db_schema/src/source/mod.rs @@ -1,3 +1,6 @@ +use crate::newtypes::DbUrl; +use url::Url; + #[cfg(feature = "full")] pub mod activity; pub mod actor_language; @@ -30,3 +33,13 @@ pub mod registration_application; pub mod secret; pub mod site; pub mod tagline; + +/// Default value for columns like [community::Community.inbox_url] which are marked as serde(skip). +/// +/// This is necessary so they can be successfully deserialized from API responses, even though the +/// value is not sent by Lemmy. Necessary for crates which rely on Rust API such as lemmy-stats-crawler. +fn placeholder_apub_url() -> DbUrl { + DbUrl(Box::new( + Url::parse("http://example.com").expect("parse placeholer url"), + )) +} diff --git a/crates/db_schema/src/source/person.rs b/crates/db_schema/src/source/person.rs index 1500e8983..82d6f61a2 100644 --- a/crates/db_schema/src/source/person.rs +++ b/crates/db_schema/src/source/person.rs @@ -1,6 +1,9 @@ -use crate::newtypes::{DbUrl, InstanceId, PersonId}; #[cfg(feature = "full")] use crate::schema::{person, person_follower}; +use crate::{ + newtypes::{DbUrl, InstanceId, PersonId}, + source::placeholder_apub_url, +}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; #[cfg(feature = "full")] @@ -40,7 +43,7 @@ pub struct Person { pub banner: Option, /// Whether the person is deleted. pub deleted: bool, - #[serde(skip_serializing)] + #[serde(skip, default = "placeholder_apub_url")] pub inbox_url: DbUrl, #[serde(skip)] pub shared_inbox_url: Option, From 050216eed97380c8c1682ba065cf5e62f0961934 Mon Sep 17 00:00:00 2001 From: David BELEY <6568955+dbeley@users.noreply.github.com> Date: Mon, 3 Jul 2023 15:15:45 +0200 Subject: [PATCH 6/9] Add awesome-lemmy to LemmyNet? (#3413) * Add LemmyNet/awesome-lemmy * Delete other apps/projects from README.md --- README.md | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/README.md b/README.md index 240bde516..e901ebcac 100644 --- a/README.md +++ b/README.md @@ -116,26 +116,7 @@ Each Lemmy server can set its own moderation policy; appointing site-wide admins ## Lemmy Projects -### Apps - -- [lemmy-ui - The official web app for lemmy](https://github.com/LemmyNet/lemmy-ui) -- [lemmyBB - A Lemmy forum UI based on phpBB](https://github.com/LemmyNet/lemmyBB) -- [Jerboa - A native Android app made by Lemmy's developers](https://github.com/dessalines/jerboa) -- [Mlem - A Lemmy client for iOS](https://github.com/buresdv/Mlem) -- [Lemoa - A Gtk client for Lemmy on Linux](https://github.com/lemmy-gtk/lemoa) -- [Liftoff - A Lemmy for Windows , Linux and Android ](https://github.com/liftoff-app/liftoff) - -### Libraries - -- [lemmy-js-client](https://github.com/LemmyNet/lemmy-js-client) -- [lemmy-rust-client](https://github.com/LemmyNet/lemmy/tree/main/crates/api_common) -- [go-lemmy](https://gitea.arsenm.dev/Arsen6331/go-lemmy) -- [Dart API client](https://github.com/LemmurOrg/lemmy_api_client) -- [Lemmy-Swift-Client](https://github.com/rrainn/Lemmy-Swift-Client) -- [Reddit -> Lemmy Importer](https://github.com/rileynull/RedditLemmyImporter) -- [lemmy-bot - Typescript library to make it easier to make bots for Lemmy](https://github.com/SleeplessOne1917/lemmy-bot) -- [Reddit API wrapper for Lemmy](https://github.com/derivator/tafkars) -- [Pythörhead - Python package for integrating with the Lemmy API](https://pypi.org/project/pythorhead/) +- [awesome-lemmy - A community driven list of apps and tools for lemmy](https://github.com/LemmyNet/awesome-lemmy) ## Support / Donate From efe98158c09de12905148146a6de41bf32660994 Mon Sep 17 00:00:00 2001 From: David BELEY <6568955+dbeley@users.noreply.github.com> Date: Mon, 3 Jul 2023 16:44:55 +0200 Subject: [PATCH 7/9] Fix awesome-lemmy owner (#3469) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e901ebcac..513ad8c87 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ Each Lemmy server can set its own moderation policy; appointing site-wide admins ## Lemmy Projects -- [awesome-lemmy - A community driven list of apps and tools for lemmy](https://github.com/LemmyNet/awesome-lemmy) +- [awesome-lemmy - A community driven list of apps and tools for lemmy](https://github.com/dbeley/awesome-lemmy) ## Support / Donate From b8ee9315bc95d647c609c89d5d38e1d19574fb4b Mon Sep 17 00:00:00 2001 From: Simon Bordeyne Date: Mon, 3 Jul 2023 17:10:25 +0200 Subject: [PATCH 8/9] Add Open links in new tab setting (#3318) * Add Open links in new tab setting * reorder because it fixes tests ? --- crates/api/src/local_user/save_settings.rs | 1 + crates/api_common/src/person.rs | 8 +++++--- crates/db_schema/src/schema.rs | 1 + crates/db_schema/src/source/local_user.rs | 4 ++++ crates/db_views/src/registration_application_view.rs | 1 + .../down.sql | 1 + .../up.sql | 1 + 7 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 migrations/2023-06-24-072904_add_open_links_in_new_tab_setting/down.sql create mode 100644 migrations/2023-06-24-072904_add_open_links_in_new_tab_setting/up.sql diff --git a/crates/api/src/local_user/save_settings.rs b/crates/api/src/local_user/save_settings.rs index e74266fc9..8f8d3194e 100644 --- a/crates/api/src/local_user/save_settings.rs +++ b/crates/api/src/local_user/save_settings.rs @@ -127,6 +127,7 @@ impl Perform for SaveUserSettings { .interface_language(data.interface_language.clone()) .totp_2fa_secret(totp_2fa_secret) .totp_2fa_url(totp_2fa_url) + .open_links_in_new_tab(data.open_links_in_new_tab) .build(); let local_user_res = LocalUser::update(context.pool(), local_user_id, &local_user_form).await; diff --git a/crates/api_common/src/person.rs b/crates/api_common/src/person.rs index 263fd584c..824d132a5 100644 --- a/crates/api_common/src/person.rs +++ b/crates/api_common/src/person.rs @@ -75,9 +75,9 @@ pub struct GetCaptchaResponse { #[cfg_attr(feature = "full", ts(export))] /// A captcha response. pub struct CaptchaResponse { - /// A Base64 encoded png + /// A Base64 encoded png pub png: String, - /// A Base64 encoded wav audio + /// A Base64 encoded wav audio pub wav: String, /// The UUID for the captcha item. pub uuid: String, @@ -109,7 +109,7 @@ pub struct SaveUserSettings { pub email: Option>, /// Your bio / info, in markdown. pub bio: Option, - /// Your matrix user id. Ex: @my_user:matrix.org + /// Your matrix user id. Ex: @my_user:matrix.org pub matrix_user_id: Option, /// Whether to show or hide avatars. pub show_avatars: Option, @@ -131,6 +131,8 @@ pub struct SaveUserSettings { /// None leaves it as is, true will generate or regenerate it, false clears it out. pub generate_totp_2fa: Option, pub auth: Sensitive, + /// Open links in a new tab + pub open_links_in_new_tab: Option, } #[derive(Debug, Serialize, Deserialize, Clone, Default)] diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index 42946d699..01aafa1d2 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -406,6 +406,7 @@ diesel::table! { accepted_application -> Bool, totp_2fa_secret -> Nullable, totp_2fa_url -> Nullable, + open_links_in_new_tab -> Bool, } } diff --git a/crates/db_schema/src/source/local_user.rs b/crates/db_schema/src/source/local_user.rs index 10849afe9..d6c999713 100644 --- a/crates/db_schema/src/source/local_user.rs +++ b/crates/db_schema/src/source/local_user.rs @@ -51,6 +51,8 @@ pub struct LocalUser { pub totp_2fa_secret: Option, /// A URL to add their 2-factor auth. pub totp_2fa_url: Option, + /// Open links in a new tab. + pub open_links_in_new_tab: bool, } #[derive(Clone, TypedBuilder)] @@ -78,6 +80,7 @@ pub struct LocalUserInsertForm { pub accepted_application: Option, pub totp_2fa_secret: Option>, pub totp_2fa_url: Option>, + pub open_links_in_new_tab: Option, } #[derive(Clone, TypedBuilder)] @@ -102,4 +105,5 @@ pub struct LocalUserUpdateForm { pub accepted_application: Option, pub totp_2fa_secret: Option>, pub totp_2fa_url: Option>, + pub open_links_in_new_tab: Option, } diff --git a/crates/db_views/src/registration_application_view.rs b/crates/db_views/src/registration_application_view.rs index 00cc92674..9963ed466 100644 --- a/crates/db_views/src/registration_application_view.rs +++ b/crates/db_views/src/registration_application_view.rs @@ -287,6 +287,7 @@ mod tests { totp_2fa_secret: inserted_sara_local_user.totp_2fa_secret, totp_2fa_url: inserted_sara_local_user.totp_2fa_url, password_encrypted: inserted_sara_local_user.password_encrypted, + open_links_in_new_tab: inserted_sara_local_user.open_links_in_new_tab, }, creator: Person { id: inserted_sara_person.id, diff --git a/migrations/2023-06-24-072904_add_open_links_in_new_tab_setting/down.sql b/migrations/2023-06-24-072904_add_open_links_in_new_tab_setting/down.sql new file mode 100644 index 000000000..a4dfd50b2 --- /dev/null +++ b/migrations/2023-06-24-072904_add_open_links_in_new_tab_setting/down.sql @@ -0,0 +1 @@ +alter table local_user drop column open_links_in_new_tab; diff --git a/migrations/2023-06-24-072904_add_open_links_in_new_tab_setting/up.sql b/migrations/2023-06-24-072904_add_open_links_in_new_tab_setting/up.sql new file mode 100644 index 000000000..39a4b44a1 --- /dev/null +++ b/migrations/2023-06-24-072904_add_open_links_in_new_tab_setting/up.sql @@ -0,0 +1 @@ +alter table local_user add column open_links_in_new_tab boolean default false not null; From e1494d468368c42e450cfeb71dbb194ad668f406 Mon Sep 17 00:00:00 2001 From: Nutomic Date: Mon, 3 Jul 2023 17:59:49 +0200 Subject: [PATCH 9/9] Dont compare db string errors (fixes #1393) (#3424) * Dont compare db string errors (fixes #1393) * cargo fmt --------- Co-authored-by: Dessalines --- crates/api_crud/src/post/create.rs | 15 +++------------ crates/api_crud/src/post/update.rs | 13 +++---------- crates/utils/src/utils/validation.rs | 2 +- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/crates/api_crud/src/post/create.rs b/crates/api_crud/src/post/create.rs index 0bbbabcb0..1dcc90241 100644 --- a/crates/api_crud/src/post/create.rs +++ b/crates/api_crud/src/post/create.rs @@ -107,18 +107,9 @@ impl PerformCrud for CreatePost { .thumbnail_url(thumbnail_url) .build(); - let inserted_post = match Post::create(context.pool(), &post_form).await { - Ok(post) => post, - Err(e) => { - let err_type = if e.to_string() == "value too long for type character varying(200)" { - "post_title_too_long" - } else { - "couldnt_create_post" - }; - - return Err(LemmyError::from_error_message(e, err_type)); - } - }; + let inserted_post = Post::create(context.pool(), &post_form) + .await + .map_err(|e| LemmyError::from_error_message(e, "couldnt_create_post"))?; let inserted_post_id = inserted_post.id; let protocol_and_hostname = context.settings().get_protocol_and_hostname(); diff --git a/crates/api_crud/src/post/update.rs b/crates/api_crud/src/post/update.rs index 253340834..632bc5ae7 100644 --- a/crates/api_crud/src/post/update.rs +++ b/crates/api_crud/src/post/update.rs @@ -96,16 +96,9 @@ impl PerformCrud for EditPost { .build(); let post_id = data.post_id; - let res = Post::update(context.pool(), post_id, &post_form).await; - if let Err(e) = res { - let err_type = if e.to_string() == "value too long for type character varying(200)" { - "post_title_too_long" - } else { - "couldnt_update_post" - }; - - return Err(LemmyError::from_error_message(e, err_type)); - } + Post::update(context.pool(), post_id, &post_form) + .await + .map_err(|e| LemmyError::from_error_message(e, "couldnt_create_post"))?; build_post_response( context, diff --git a/crates/utils/src/utils/validation.rs b/crates/utils/src/utils/validation.rs index 7e09c5af8..347d791a8 100644 --- a/crates/utils/src/utils/validation.rs +++ b/crates/utils/src/utils/validation.rs @@ -8,7 +8,7 @@ use url::Url; static VALID_ACTOR_NAME_REGEX: Lazy = Lazy::new(|| Regex::new(r"^[a-zA-Z0-9_]{3,}$").expect("compile regex")); static VALID_POST_TITLE_REGEX: Lazy = - Lazy::new(|| Regex::new(r".*\S{3,}.*").expect("compile regex")); + Lazy::new(|| Regex::new(r".*\S{3,200}.*").expect("compile regex")); static VALID_MATRIX_ID_REGEX: Lazy = Lazy::new(|| { Regex::new(r"^@[A-Za-z0-9._=-]+:[A-Za-z0-9.-]+\.[A-Za-z]{2,}$").expect("compile regex") });