From 432d46c1aac19fcf968c0a0c466f5d1f75f01458 Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 2 Oct 2024 09:10:41 -0400 Subject: [PATCH] Adding a get_random_community endpoint. (#5042) * Adding a get_random_community endpoint. - Fixes #4698 * Fixing issue from main. * Adding ListingType to the query. * More concise query filter. --- crates/api/src/community/mod.rs | 1 + crates/api/src/community/random.rs | 55 +++++++++++++++++++++++++ crates/api/src/site/leave_admin.rs | 2 +- crates/api_common/src/community.rs | 9 ++++ crates/api_crud/src/site/read.rs | 2 +- crates/db_schema/src/impls/community.rs | 27 +++++++++++- crates/db_schema/src/impls/tagline.rs | 11 ++--- src/api_routes_http.rs | 2 + 8 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 crates/api/src/community/random.rs diff --git a/crates/api/src/community/mod.rs b/crates/api/src/community/mod.rs index 478192229..54bdbef28 100644 --- a/crates/api/src/community/mod.rs +++ b/crates/api/src/community/mod.rs @@ -3,4 +3,5 @@ pub mod ban; pub mod block; pub mod follow; pub mod hide; +pub mod random; pub mod transfer; diff --git a/crates/api/src/community/random.rs b/crates/api/src/community/random.rs new file mode 100644 index 000000000..3cc04e126 --- /dev/null +++ b/crates/api/src/community/random.rs @@ -0,0 +1,55 @@ +use activitypub_federation::config::Data; +use actix_web::web::{Json, Query}; +use lemmy_api_common::{ + community::{CommunityResponse, GetRandomCommunity}, + context::LemmyContext, + utils::{check_private_instance, is_mod_or_admin_opt}, +}; +use lemmy_db_schema::source::{ + actor_language::CommunityLanguage, + community::Community, + local_site::LocalSite, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_db_views_actor::structs::CommunityView; +use lemmy_utils::error::LemmyResult; + +#[tracing::instrument(skip(context))] +pub async fn get_random_community( + data: Query, + context: Data, + local_user_view: Option, +) -> LemmyResult> { + let local_site = LocalSite::read(&mut context.pool()).await?; + + check_private_instance(&local_user_view, &local_site)?; + + let local_user = local_user_view.as_ref().map(|u| &u.local_user); + + let random_community_id = + Community::get_random_community_id(&mut context.pool(), &data.type_).await?; + + let is_mod_or_admin = is_mod_or_admin_opt( + &mut context.pool(), + local_user_view.as_ref(), + Some(random_community_id), + ) + .await + .is_ok(); + + let community_view = CommunityView::read( + &mut context.pool(), + random_community_id, + local_user, + is_mod_or_admin, + ) + .await?; + + let discussion_languages = + CommunityLanguage::read(&mut context.pool(), random_community_id).await?; + + Ok(Json(CommunityResponse { + community_view, + discussion_languages, + })) +} diff --git a/crates/api/src/site/leave_admin.rs b/crates/api/src/site/leave_admin.rs index f5ddec35b..97ad7e2e5 100644 --- a/crates/api/src/site/leave_admin.rs +++ b/crates/api/src/site/leave_admin.rs @@ -63,7 +63,7 @@ pub async fn leave_admin( let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?; let oauth_providers = OAuthProvider::get_all_public(&mut context.pool()).await?; let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?; - let tagline = Tagline::get_random(&mut context.pool()).await?; + let tagline = Tagline::get_random(&mut context.pool()).await.ok(); Ok(Json(GetSiteResponse { site_view, diff --git a/crates/api_common/src/community.rs b/crates/api_common/src/community.rs index e1c1c5d76..25a2ca0d4 100644 --- a/crates/api_common/src/community.rs +++ b/crates/api_common/src/community.rs @@ -225,3 +225,12 @@ pub struct TransferCommunity { pub community_id: CommunityId, pub person_id: PersonId, } + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Fetches a random community +pub struct GetRandomCommunity { + pub type_: Option, +} diff --git a/crates/api_crud/src/site/read.rs b/crates/api_crud/src/site/read.rs index 20c10c415..47fd1f154 100644 --- a/crates/api_crud/src/site/read.rs +++ b/crates/api_crud/src/site/read.rs @@ -43,7 +43,7 @@ pub async fn get_site( let all_languages = Language::read_all(&mut context.pool()).await?; let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?; let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?; - let tagline = Tagline::get_random(&mut context.pool()).await?; + let tagline = Tagline::get_random(&mut context.pool()).await.ok(); let admin_oauth_providers = OAuthProvider::get_all(&mut context.pool()).await?; let oauth_providers = OAuthProvider::convert_providers_to_public(admin_oauth_providers.clone()); diff --git a/crates/db_schema/src/impls/community.rs b/crates/db_schema/src/impls/community.rs index 223a1dbf7..a054059a4 100644 --- a/crates/db_schema/src/impls/community.rs +++ b/crates/db_schema/src/impls/community.rs @@ -30,12 +30,13 @@ use crate::{ get_conn, DbPool, }, + ListingType, SubscribedType, }; use chrono::{DateTime, Utc}; use diesel::{ deserialize, - dsl::{self, exists, insert_into}, + dsl::{self, exists, insert_into, not}, pg::Pg, result::Error, select, @@ -193,6 +194,30 @@ impl Community { .await?; Ok(()) } + + pub async fn get_random_community_id( + pool: &mut DbPool<'_>, + type_: &Option, + ) -> Result { + let conn = &mut get_conn(pool).await?; + sql_function!(fn random() -> Text); + + let mut query = community::table + .filter(not(community::deleted)) + .filter(not(community::removed)) + .into_boxed(); + + if let Some(ListingType::Local) = type_ { + query = query.filter(community::local); + } + + query + .select(community::id) + .order(random()) + .limit(1) + .first::(conn) + .await + } } impl CommunityModerator { diff --git a/crates/db_schema/src/impls/tagline.rs b/crates/db_schema/src/impls/tagline.rs index 656d537d6..aa5841020 100644 --- a/crates/db_schema/src/impls/tagline.rs +++ b/crates/db_schema/src/impls/tagline.rs @@ -5,7 +5,7 @@ use crate::{ traits::Crud, utils::{get_conn, limit_and_offset, DbPool}, }; -use diesel::{insert_into, result::Error, ExpressionMethods, OptionalExtension, QueryDsl}; +use diesel::{insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; #[async_trait] @@ -51,14 +51,9 @@ impl Tagline { .await } - pub async fn get_random(pool: &mut DbPool<'_>) -> Result, Error> { + pub async fn get_random(pool: &mut DbPool<'_>) -> Result { let conn = &mut get_conn(pool).await?; sql_function!(fn random() -> Text); - tagline - .order(random()) - .limit(1) - .first::(conn) - .await - .optional() + tagline.order(random()).limit(1).first::(conn).await } } diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index 65931e810..df1aebf84 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -17,6 +17,7 @@ use lemmy_api::{ block::block_community, follow::follow_community, hide::hide_community, + random::get_random_community, transfer::transfer_community, }, local_user::{ @@ -193,6 +194,7 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .wrap(rate_limit.message()) .route("", web::get().to(get_community)) .route("", web::put().to(update_community)) + .route("/random", web::get().to(get_random_community)) .route("/hide", web::put().to(hide_community)) .route("/list", web::get().to(list_communities)) .route("/follow", web::post().to(follow_community))