From 43f20881cb2057d5784fac69a8944c00e57d5042 Mon Sep 17 00:00:00 2001 From: Freakazoid182 <5238563+Freakazoid182@users.noreply.github.com> Date: Thu, 19 Sep 2024 11:15:04 +0200 Subject: [PATCH] Feature/custom emoji and tagline views (#4580) * Add custom_emoji list route * Add tagline list route * Apply linting * Remove unecessary TaglineView * Add category filter for custom emoji * Add create tagline endpoint * Add update tagline endpoint * Add delete tagline endpoint * Format through lint.sh * Remove custom_emojis and taglines from site resource * Get random tagline on site requets * Impl Crud for Tagline Remove superfluous properties * Move tagline endpoints under /admin * Impl Crud for CustomEmoji * Remove delete from tagline and custom emoji impls * Check markdown for tagline * Validate markdown on tagline * Make content fields non optional Add error types for tagline validation * Use process_markdown instead of process_markdown_opt * Consolidate Tagline error types * Remove unecessary clone * Updat misleading comments * Remove local_site_id from tagline and custom_emoji * Update TaglineInserForm and TaglineUpdateForm * Add ignore_page_limits for custom emojis EmojiPicker needs to be able to retrieve all emojis in 1 call * Update custom_emoji_view Only keep get_all als helper function calling list with paging ignored Only order on category when filtering on category * Removing pointless get_all fn. * remove tagline length checks * make fields of TaglineInsertForm and TaglineUpdateForm mandatory * move emoji order statement * add comment for GetSiteResponse.tagline --------- Co-authored-by: Freakazoid182 <> Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com> Co-authored-by: Dessalines Co-authored-by: Felix Ableitner --- crates/api/src/site/leave_admin.rs | 9 +- crates/api_common/src/custom_emoji.rs | 21 ++++ crates/api_common/src/lib.rs | 1 + crates/api_common/src/site.rs | 11 +- crates/api_common/src/tagline.rs | 55 ++++++++++ crates/api_crud/src/custom_emoji/create.rs | 12 +-- crates/api_crud/src/custom_emoji/delete.rs | 2 +- crates/api_crud/src/custom_emoji/list.rs | 25 +++++ crates/api_crud/src/custom_emoji/mod.rs | 1 + crates/api_crud/src/custom_emoji/update.rs | 12 +-- crates/api_crud/src/lib.rs | 1 + crates/api_crud/src/site/create.rs | 9 +- crates/api_crud/src/site/read.rs | 9 +- crates/api_crud/src/site/update.rs | 9 +- crates/api_crud/src/tagline/create.rs | 38 +++++++ crates/api_crud/src/tagline/delete.rs | 25 +++++ crates/api_crud/src/tagline/list.rs | 19 ++++ crates/api_crud/src/tagline/mod.rs | 4 + crates/api_crud/src/tagline/update.rs | 42 ++++++++ crates/db_schema/src/impls/custom_emoji.rs | 25 ++--- crates/db_schema/src/impls/tagline.rs | 100 ++++++++++-------- crates/db_schema/src/newtypes.rs | 6 ++ crates/db_schema/src/schema.rs | 4 - crates/db_schema/src/source/custom_emoji.rs | 14 +-- crates/db_schema/src/source/tagline.rs | 23 ++-- crates/db_views/src/custom_emoji_view.rs | 30 ++++-- .../down.sql | 32 ++++++ .../up.sql | 6 ++ src/api_routes_http.rs | 18 +++- 29 files changed, 417 insertions(+), 146 deletions(-) create mode 100644 crates/api_common/src/tagline.rs create mode 100644 crates/api_crud/src/custom_emoji/list.rs create mode 100644 crates/api_crud/src/tagline/create.rs create mode 100644 crates/api_crud/src/tagline/delete.rs create mode 100644 crates/api_crud/src/tagline/list.rs create mode 100644 crates/api_crud/src/tagline/mod.rs create mode 100644 crates/api_crud/src/tagline/update.rs create mode 100644 migrations/2024-04-08-204327_custom_emoji_tagline_changes/down.sql create mode 100644 migrations/2024-04-08-204327_custom_emoji_tagline_changes/up.sql diff --git a/crates/api/src/site/leave_admin.rs b/crates/api/src/site/leave_admin.rs index d3581995a..5e1e69d49 100644 --- a/crates/api/src/site/leave_admin.rs +++ b/crates/api/src/site/leave_admin.rs @@ -12,7 +12,7 @@ use lemmy_db_schema::{ }, traits::Crud, }; -use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView}; +use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views_actor::structs::PersonView; use lemmy_utils::{ error::{LemmyErrorType, LemmyResult}, @@ -61,11 +61,9 @@ pub async fn leave_admin( let all_languages = Language::read_all(&mut context.pool()).await?; let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?; - let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?; - let custom_emojis = - CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).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?; Ok(Json(GetSiteResponse { site_view, @@ -74,10 +72,9 @@ pub async fn leave_admin( my_user: None, all_languages, discussion_languages, - taglines, - custom_emojis, oauth_providers: Some(oauth_providers), admin_oauth_providers: None, blocked_urls, + tagline, })) } diff --git a/crates/api_common/src/custom_emoji.rs b/crates/api_common/src/custom_emoji.rs index 468d2128d..3804b71af 100644 --- a/crates/api_common/src/custom_emoji.rs +++ b/crates/api_common/src/custom_emoji.rs @@ -1,6 +1,7 @@ use lemmy_db_schema::newtypes::CustomEmojiId; use lemmy_db_views::structs::CustomEmojiView; use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; #[cfg(feature = "full")] use ts_rs::TS; use url::Url; @@ -46,3 +47,23 @@ pub struct DeleteCustomEmoji { pub struct CustomEmojiResponse { pub custom_emoji: CustomEmojiView, } + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// A response for custom emojis. +pub struct ListCustomEmojisResponse { + pub custom_emojis: Vec, +} + +#[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 list of custom emojis. +pub struct ListCustomEmojis { + pub page: Option, + pub limit: Option, + pub category: Option, + pub ignore_page_limits: Option, +} diff --git a/crates/api_common/src/lib.rs b/crates/api_common/src/lib.rs index 48acaad21..68eeadecc 100644 --- a/crates/api_common/src/lib.rs +++ b/crates/api_common/src/lib.rs @@ -16,6 +16,7 @@ pub mod request; #[cfg(feature = "full")] pub mod send_activity; pub mod site; +pub mod tagline; #[cfg(feature = "full")] pub mod utils; diff --git a/crates/api_common/src/site.rs b/crates/api_common/src/site.rs index f25dcf94b..a4f4ea71e 100644 --- a/crates/api_common/src/site.rs +++ b/crates/api_common/src/site.rs @@ -30,7 +30,6 @@ use lemmy_db_schema::{ }; use lemmy_db_views::structs::{ CommentView, - CustomEmojiView, LocalUserView, PostView, RegistrationApplicationView, @@ -202,7 +201,6 @@ pub struct CreateSite { pub captcha_difficulty: Option, pub allowed_instances: Option>, pub blocked_instances: Option>, - pub taglines: Option>, pub registration_mode: Option, pub oauth_registration: Option, pub content_warning: Option, @@ -288,8 +286,6 @@ pub struct EditSite { pub blocked_instances: Option>, /// A list of blocked URLs pub blocked_urls: Option>, - /// A list of taglines shown at the top of the front page. - pub taglines: Option>, pub registration_mode: Option, /// Whether or not external auth methods can auto-register users. pub oauth_registration: Option, @@ -306,7 +302,6 @@ pub struct EditSite { /// The response for a site. pub struct SiteResponse { pub site_view: SiteView, - pub taglines: Vec, } #[skip_serializing_none] @@ -321,10 +316,8 @@ pub struct GetSiteResponse { pub my_user: Option, pub all_languages: Vec, pub discussion_languages: Vec, - /// A list of taglines shown at the top of the front page. - pub taglines: Vec, - /// A list of custom emojis your site supports. - pub custom_emojis: Vec, + /// If the site has any taglines, a random one is included here for displaying + pub tagline: Option, /// A list of external auth methods your site supports. pub oauth_providers: Option>, pub admin_oauth_providers: Option>, diff --git a/crates/api_common/src/tagline.rs b/crates/api_common/src/tagline.rs new file mode 100644 index 000000000..3090a2678 --- /dev/null +++ b/crates/api_common/src/tagline.rs @@ -0,0 +1,55 @@ +use lemmy_db_schema::{newtypes::TaglineId, source::tagline::Tagline}; +use serde::{Deserialize, Serialize}; +use serde_with::skip_serializing_none; +#[cfg(feature = "full")] +use ts_rs::TS; + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Create a tagline +pub struct CreateTagline { + pub content: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Update a tagline +pub struct UpdateTagline { + pub id: TaglineId, + pub content: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Delete a tagline +pub struct DeleteTagline { + pub id: TaglineId, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +pub struct TaglineResponse { + pub tagline: Tagline, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// A response for taglines. +pub struct ListTaglinesResponse { + pub taglines: Vec, +} + +#[skip_serializing_none] +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Default, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "full", derive(TS))] +#[cfg_attr(feature = "full", ts(export))] +/// Fetches a list of taglines. +pub struct ListTaglines { + pub page: Option, + pub limit: Option, +} diff --git a/crates/api_crud/src/custom_emoji/create.rs b/crates/api_crud/src/custom_emoji/create.rs index 3c5ce3296..654b75631 100644 --- a/crates/api_crud/src/custom_emoji/create.rs +++ b/crates/api_crud/src/custom_emoji/create.rs @@ -5,10 +5,12 @@ use lemmy_api_common::{ custom_emoji::{CreateCustomEmoji, CustomEmojiResponse}, utils::is_admin, }; -use lemmy_db_schema::source::{ - custom_emoji::{CustomEmoji, CustomEmojiInsertForm}, - custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, - local_site::LocalSite, +use lemmy_db_schema::{ + source::{ + custom_emoji::{CustomEmoji, CustomEmojiInsertForm}, + custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, + }, + traits::Crud, }; use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; use lemmy_utils::error::LemmyResult; @@ -19,12 +21,10 @@ pub async fn create_custom_emoji( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let local_site = LocalSite::read(&mut context.pool()).await?; // Make sure user is an admin is_admin(&local_user_view)?; let emoji_form = CustomEmojiInsertForm::builder() - .local_site_id(local_site.id) .shortcode(data.shortcode.to_lowercase().trim().to_string()) .alt_text(data.alt_text.to_string()) .category(data.category.to_string()) diff --git a/crates/api_crud/src/custom_emoji/delete.rs b/crates/api_crud/src/custom_emoji/delete.rs index 45ac8d0ba..818fd4d88 100644 --- a/crates/api_crud/src/custom_emoji/delete.rs +++ b/crates/api_crud/src/custom_emoji/delete.rs @@ -6,7 +6,7 @@ use lemmy_api_common::{ utils::is_admin, SuccessResponse, }; -use lemmy_db_schema::source::custom_emoji::CustomEmoji; +use lemmy_db_schema::{source::custom_emoji::CustomEmoji, traits::Crud}; use lemmy_db_views::structs::LocalUserView; use lemmy_utils::error::LemmyResult; diff --git a/crates/api_crud/src/custom_emoji/list.rs b/crates/api_crud/src/custom_emoji/list.rs new file mode 100644 index 000000000..6ee5a44b0 --- /dev/null +++ b/crates/api_crud/src/custom_emoji/list.rs @@ -0,0 +1,25 @@ +use actix_web::web::{Data, Json, Query}; +use lemmy_api_common::{ + context::LemmyContext, + custom_emoji::{ListCustomEmojis, ListCustomEmojisResponse}, +}; +use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; +use lemmy_utils::error::LemmyError; + +#[tracing::instrument(skip(context))] +pub async fn list_custom_emojis( + data: Query, + local_user_view: Option, + context: Data, +) -> Result, LemmyError> { + let custom_emojis = CustomEmojiView::list( + &mut context.pool(), + &data.category, + data.page, + data.limit, + data.ignore_page_limits.unwrap_or(false), + ) + .await?; + + Ok(Json(ListCustomEmojisResponse { custom_emojis })) +} diff --git a/crates/api_crud/src/custom_emoji/mod.rs b/crates/api_crud/src/custom_emoji/mod.rs index fdb2f5561..ffd48daf6 100644 --- a/crates/api_crud/src/custom_emoji/mod.rs +++ b/crates/api_crud/src/custom_emoji/mod.rs @@ -1,3 +1,4 @@ pub mod create; pub mod delete; +pub mod list; pub mod update; diff --git a/crates/api_crud/src/custom_emoji/update.rs b/crates/api_crud/src/custom_emoji/update.rs index 63246f85d..62d1d819d 100644 --- a/crates/api_crud/src/custom_emoji/update.rs +++ b/crates/api_crud/src/custom_emoji/update.rs @@ -5,10 +5,12 @@ use lemmy_api_common::{ custom_emoji::{CustomEmojiResponse, EditCustomEmoji}, utils::is_admin, }; -use lemmy_db_schema::source::{ - custom_emoji::{CustomEmoji, CustomEmojiUpdateForm}, - custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, - local_site::LocalSite, +use lemmy_db_schema::{ + source::{ + custom_emoji::{CustomEmoji, CustomEmojiUpdateForm}, + custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, + }, + traits::Crud, }; use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; use lemmy_utils::error::LemmyResult; @@ -19,12 +21,10 @@ pub async fn update_custom_emoji( context: Data, local_user_view: LocalUserView, ) -> LemmyResult> { - let local_site = LocalSite::read(&mut context.pool()).await?; // Make sure user is an admin is_admin(&local_user_view)?; let emoji_form = CustomEmojiUpdateForm::builder() - .local_site_id(local_site.id) .alt_text(data.alt_text.to_string()) .category(data.category.to_string()) .image_url(data.clone().image_url.into()) diff --git a/crates/api_crud/src/lib.rs b/crates/api_crud/src/lib.rs index b138fbd30..7d1b901b9 100644 --- a/crates/api_crud/src/lib.rs +++ b/crates/api_crud/src/lib.rs @@ -5,4 +5,5 @@ pub mod oauth_provider; pub mod post; pub mod private_message; pub mod site; +pub mod tagline; pub mod user; diff --git a/crates/api_crud/src/site/create.rs b/crates/api_crud/src/site/create.rs index 30321d8dd..162f5a355 100644 --- a/crates/api_crud/src/site/create.rs +++ b/crates/api_crud/src/site/create.rs @@ -20,7 +20,6 @@ use lemmy_db_schema::{ local_site::{LocalSite, LocalSiteUpdateForm}, local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitUpdateForm}, site::{Site, SiteUpdateForm}, - tagline::Tagline, }, traits::Crud, utils::{diesel_string_update, diesel_url_create, naive_now}, @@ -135,17 +134,11 @@ pub async fn create_site( let site_view = SiteView::read_local(&mut context.pool()).await?; - let new_taglines = data.taglines.clone(); - let taglines = Tagline::replace(&mut context.pool(), local_site.id, new_taglines).await?; - let rate_limit_config = local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); context.rate_limit_cell().set_config(rate_limit_config); - Ok(Json(SiteResponse { - site_view, - taglines, - })) + Ok(Json(SiteResponse { site_view })) } fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) -> LemmyResult<()> { diff --git a/crates/api_crud/src/site/read.rs b/crates/api_crud/src/site/read.rs index 6f524dd7d..0901b9186 100644 --- a/crates/api_crud/src/site/read.rs +++ b/crates/api_crud/src/site/read.rs @@ -13,7 +13,7 @@ use lemmy_db_schema::source::{ person_block::PersonBlock, tagline::Tagline, }; -use lemmy_db_views::structs::{CustomEmojiView, LocalUserView, SiteView}; +use lemmy_db_views::structs::{LocalUserView, SiteView}; use lemmy_db_views_actor::structs::{CommunityFollowerView, CommunityModeratorView, PersonView}; use lemmy_utils::{ error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, @@ -42,10 +42,8 @@ pub async fn get_site( let admins = PersonView::admins(&mut context.pool()).await?; let all_languages = Language::read_all(&mut context.pool()).await?; let discussion_languages = SiteLanguage::read_local_raw(&mut context.pool()).await?; - let taglines = Tagline::get_all(&mut context.pool(), site_view.local_site.id).await?; - let custom_emojis = - CustomEmojiView::get_all(&mut context.pool(), site_view.local_site.id).await?; let blocked_urls = LocalSiteUrlBlocklist::get_all(&mut context.pool()).await?; + let tagline = Tagline::get_random(&mut context.pool()).await?; let admin_oauth_providers = OAuthProvider::get_all(&mut context.pool()).await?; let oauth_providers = OAuthProvider::convert_providers_to_public(admin_oauth_providers.clone()); @@ -57,9 +55,8 @@ pub async fn get_site( my_user: None, all_languages, discussion_languages, - taglines, - custom_emojis, blocked_urls, + tagline, oauth_providers: Some(oauth_providers), admin_oauth_providers: Some(admin_oauth_providers), }) diff --git a/crates/api_crud/src/site/update.rs b/crates/api_crud/src/site/update.rs index a6046b423..1ae531bb7 100644 --- a/crates/api_crud/src/site/update.rs +++ b/crates/api_crud/src/site/update.rs @@ -24,7 +24,6 @@ use lemmy_db_schema::{ local_site_url_blocklist::LocalSiteUrlBlocklist, local_user::LocalUser, site::{Site, SiteUpdateForm}, - tagline::Tagline, }, traits::Crud, utils::{diesel_string_update, diesel_url_update, naive_now}, @@ -187,19 +186,13 @@ pub async fn update_site( .with_lemmy_type(LemmyErrorType::CouldntSetAllEmailVerified)?; } - let new_taglines = data.taglines.clone(); - let taglines = Tagline::replace(&mut context.pool(), local_site.id, new_taglines).await?; - let site_view = SiteView::read_local(&mut context.pool()).await?; let rate_limit_config = local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); context.rate_limit_cell().set_config(rate_limit_config); - Ok(Json(SiteResponse { - site_view, - taglines, - })) + Ok(Json(SiteResponse { site_view })) } fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> LemmyResult<()> { diff --git a/crates/api_crud/src/tagline/create.rs b/crates/api_crud/src/tagline/create.rs new file mode 100644 index 000000000..f67a26f68 --- /dev/null +++ b/crates/api_crud/src/tagline/create.rs @@ -0,0 +1,38 @@ +use activitypub_federation::config::Data; +use actix_web::web::Json; +use lemmy_api_common::{ + context::LemmyContext, + tagline::{CreateTagline, TaglineResponse}, + utils::{get_url_blocklist, is_admin, local_site_to_slur_regex, process_markdown}, +}; +use lemmy_db_schema::{ + source::{ + local_site::LocalSite, + tagline::{Tagline, TaglineInsertForm}, + }, + traits::Crud, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyError; + +#[tracing::instrument(skip(context))] +pub async fn create_tagline( + data: Json, + context: Data, + local_user_view: LocalUserView, +) -> Result, LemmyError> { + // Make sure user is an admin + is_admin(&local_user_view)?; + + let local_site = LocalSite::read(&mut context.pool()).await?; + + let slur_regex = local_site_to_slur_regex(&local_site); + let url_blocklist = get_url_blocklist(&context).await?; + let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?; + + let tagline_form = TaglineInsertForm { content }; + + let tagline = Tagline::create(&mut context.pool(), &tagline_form).await?; + + Ok(Json(TaglineResponse { tagline })) +} diff --git a/crates/api_crud/src/tagline/delete.rs b/crates/api_crud/src/tagline/delete.rs new file mode 100644 index 000000000..9add3cfe6 --- /dev/null +++ b/crates/api_crud/src/tagline/delete.rs @@ -0,0 +1,25 @@ +use activitypub_federation::config::Data; +use actix_web::web::Json; +use lemmy_api_common::{ + context::LemmyContext, + tagline::DeleteTagline, + utils::is_admin, + SuccessResponse, +}; +use lemmy_db_schema::{source::tagline::Tagline, traits::Crud}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyError; + +#[tracing::instrument(skip(context))] +pub async fn delete_tagline( + data: Json, + context: Data, + local_user_view: LocalUserView, +) -> Result, LemmyError> { + // Make sure user is an admin + is_admin(&local_user_view)?; + + Tagline::delete(&mut context.pool(), data.id).await?; + + Ok(Json(SuccessResponse::default())) +} diff --git a/crates/api_crud/src/tagline/list.rs b/crates/api_crud/src/tagline/list.rs new file mode 100644 index 000000000..21929f547 --- /dev/null +++ b/crates/api_crud/src/tagline/list.rs @@ -0,0 +1,19 @@ +use actix_web::web::{Data, Json, Query}; +use lemmy_api_common::{ + context::LemmyContext, + tagline::{ListTaglines, ListTaglinesResponse}, +}; +use lemmy_db_schema::source::tagline::Tagline; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyError; + +#[tracing::instrument(skip(context))] +pub async fn list_taglines( + data: Query, + local_user_view: Option, + context: Data, +) -> Result, LemmyError> { + let taglines = Tagline::list(&mut context.pool(), data.page, data.limit).await?; + + Ok(Json(ListTaglinesResponse { taglines })) +} diff --git a/crates/api_crud/src/tagline/mod.rs b/crates/api_crud/src/tagline/mod.rs new file mode 100644 index 000000000..ffd48daf6 --- /dev/null +++ b/crates/api_crud/src/tagline/mod.rs @@ -0,0 +1,4 @@ +pub mod create; +pub mod delete; +pub mod list; +pub mod update; diff --git a/crates/api_crud/src/tagline/update.rs b/crates/api_crud/src/tagline/update.rs new file mode 100644 index 000000000..043589d26 --- /dev/null +++ b/crates/api_crud/src/tagline/update.rs @@ -0,0 +1,42 @@ +use activitypub_federation::config::Data; +use actix_web::web::Json; +use lemmy_api_common::{ + context::LemmyContext, + tagline::{TaglineResponse, UpdateTagline}, + utils::{get_url_blocklist, is_admin, local_site_to_slur_regex, process_markdown}, +}; +use lemmy_db_schema::{ + source::{ + local_site::LocalSite, + tagline::{Tagline, TaglineUpdateForm}, + }, + traits::Crud, + utils::naive_now, +}; +use lemmy_db_views::structs::LocalUserView; +use lemmy_utils::error::LemmyError; + +#[tracing::instrument(skip(context))] +pub async fn update_tagline( + data: Json, + context: Data, + local_user_view: LocalUserView, +) -> Result, LemmyError> { + // Make sure user is an admin + is_admin(&local_user_view)?; + + let local_site = LocalSite::read(&mut context.pool()).await?; + + let slur_regex = local_site_to_slur_regex(&local_site); + let url_blocklist = get_url_blocklist(&context).await?; + let content = process_markdown(&data.content, &slur_regex, &url_blocklist, &context).await?; + + let tagline_form = TaglineUpdateForm { + content, + updated: naive_now(), + }; + + let tagline = Tagline::update(&mut context.pool(), data.id, &tagline_form).await?; + + Ok(Json(TaglineResponse { tagline })) +} diff --git a/crates/db_schema/src/impls/custom_emoji.rs b/crates/db_schema/src/impls/custom_emoji.rs index 050301659..9ba359071 100644 --- a/crates/db_schema/src/impls/custom_emoji.rs +++ b/crates/db_schema/src/impls/custom_emoji.rs @@ -8,36 +8,37 @@ use crate::{ custom_emoji::{CustomEmoji, CustomEmojiInsertForm, CustomEmojiUpdateForm}, custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, }, + traits::Crud, utils::{get_conn, DbPool}, }; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; -impl CustomEmoji { - pub async fn create(pool: &mut DbPool<'_>, form: &CustomEmojiInsertForm) -> Result { +#[async_trait] +impl Crud for CustomEmoji { + type InsertForm = CustomEmojiInsertForm; + type UpdateForm = CustomEmojiUpdateForm; + type IdType = CustomEmojiId; + + async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result { let conn = &mut get_conn(pool).await?; insert_into(custom_emoji) .values(form) .get_result::(conn) .await } - pub async fn update( + + async fn update( pool: &mut DbPool<'_>, - emoji_id: CustomEmojiId, - form: &CustomEmojiUpdateForm, + emoji_id: Self::IdType, + new_custom_emoji: &Self::UpdateForm, ) -> Result { let conn = &mut get_conn(pool).await?; diesel::update(custom_emoji.find(emoji_id)) - .set(form) + .set(new_custom_emoji) .get_result::(conn) .await } - pub async fn delete(pool: &mut DbPool<'_>, emoji_id: CustomEmojiId) -> Result { - let conn = &mut get_conn(pool).await?; - diesel::delete(custom_emoji.find(emoji_id)) - .execute(conn) - .await - } } impl CustomEmojiKeyword { diff --git a/crates/db_schema/src/impls/tagline.rs b/crates/db_schema/src/impls/tagline.rs index be4860e17..656d537d6 100644 --- a/crates/db_schema/src/impls/tagline.rs +++ b/crates/db_schema/src/impls/tagline.rs @@ -1,58 +1,64 @@ use crate::{ - newtypes::LocalSiteId, - schema::tagline::dsl::{local_site_id, tagline}, - source::tagline::{Tagline, TaglineForm}, - utils::{get_conn, DbPool}, + newtypes::TaglineId, + schema::tagline::dsl::{published, tagline}, + source::tagline::{Tagline, TaglineInsertForm, TaglineUpdateForm}, + traits::Crud, + utils::{get_conn, limit_and_offset, DbPool}, }; -use diesel::{insert_into, result::Error, ExpressionMethods, QueryDsl}; -use diesel_async::{AsyncPgConnection, RunQueryDsl}; +use diesel::{insert_into, result::Error, ExpressionMethods, OptionalExtension, QueryDsl}; +use diesel_async::RunQueryDsl; -impl Tagline { - pub async fn replace( - pool: &mut DbPool<'_>, - for_local_site_id: LocalSiteId, - list_content: Option>, - ) -> Result, Error> { +#[async_trait] +impl Crud for Tagline { + type InsertForm = TaglineInsertForm; + type UpdateForm = TaglineUpdateForm; + type IdType = TaglineId; + + async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result { let conn = &mut get_conn(pool).await?; - if let Some(list) = list_content { - conn - .build_transaction() - .run(|conn| { - Box::pin(async move { - Self::clear(conn).await?; - - for item in list { - let form = TaglineForm { - local_site_id: for_local_site_id, - content: item, - updated: None, - }; - insert_into(tagline) - .values(form) - .get_result::(conn) - .await?; - } - Self::get_all(&mut conn.into(), for_local_site_id).await - }) as _ - }) - .await - } else { - Self::get_all(&mut conn.into(), for_local_site_id).await - } + insert_into(tagline) + .values(form) + .get_result::(conn) + .await } - async fn clear(conn: &mut AsyncPgConnection) -> Result { - diesel::delete(tagline).execute(conn).await - } - - pub async fn get_all( + async fn update( pool: &mut DbPool<'_>, - for_local_site_id: LocalSiteId, - ) -> Result, Error> { + tagline_id: TaglineId, + new_tagline: &Self::UpdateForm, + ) -> Result { let conn = &mut get_conn(pool).await?; - tagline - .filter(local_site_id.eq(for_local_site_id)) - .get_results::(conn) + diesel::update(tagline.find(tagline_id)) + .set(new_tagline) + .get_result::(conn) .await } } + +impl Tagline { + pub async fn list( + pool: &mut DbPool<'_>, + page: Option, + limit: Option, + ) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + let (limit, offset) = limit_and_offset(page, limit)?; + tagline + .order(published.desc()) + .offset(offset) + .limit(limit) + .get_results::(conn) + .await + } + + pub async fn get_random(pool: &mut DbPool<'_>) -> Result, Error> { + let conn = &mut get_conn(pool).await?; + sql_function!(fn random() -> Text); + tagline + .order(random()) + .limit(1) + .first::(conn) + .await + .optional() + } +} diff --git a/crates/db_schema/src/newtypes.rs b/crates/db_schema/src/newtypes.rs index d90b1f3f6..58396c66a 100644 --- a/crates/db_schema/src/newtypes.rs +++ b/crates/db_schema/src/newtypes.rs @@ -148,6 +148,12 @@ pub struct LocalSiteId(i32); /// The custom emoji id. pub struct CustomEmojiId(i32); +#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)] +#[cfg_attr(feature = "full", derive(DieselNewType, TS))] +#[cfg_attr(feature = "full", ts(export))] +/// The tagline id. +pub struct TaglineId(i32); + #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)] #[cfg_attr(feature = "full", derive(DieselNewType, TS))] #[cfg_attr(feature = "full", ts(export))] diff --git a/crates/db_schema/src/schema.rs b/crates/db_schema/src/schema.rs index b5f68822c..289032e00 100644 --- a/crates/db_schema/src/schema.rs +++ b/crates/db_schema/src/schema.rs @@ -258,7 +258,6 @@ diesel::table! { diesel::table! { custom_emoji (id) { id -> Int4, - local_site_id -> Int4, #[max_length = 128] shortcode -> Varchar, image_url -> Text, @@ -974,7 +973,6 @@ diesel::table! { diesel::table! { tagline (id) { id -> Int4, - local_site_id -> Int4, content -> Text, published -> Timestamptz, updated -> Nullable, @@ -1011,7 +1009,6 @@ diesel::joinable!(community_moderator -> community (community_id)); diesel::joinable!(community_moderator -> person (person_id)); diesel::joinable!(community_person_ban -> community (community_id)); diesel::joinable!(community_person_ban -> person (person_id)); -diesel::joinable!(custom_emoji -> local_site (local_site_id)); diesel::joinable!(custom_emoji_keyword -> custom_emoji (custom_emoji_id)); diesel::joinable!(email_verification -> local_user (local_user_id)); diesel::joinable!(federation_allowlist -> instance (instance_id)); @@ -1075,7 +1072,6 @@ diesel::joinable!(site -> instance (instance_id)); diesel::joinable!(site_aggregates -> site (site_id)); diesel::joinable!(site_language -> language (language_id)); diesel::joinable!(site_language -> site (site_id)); -diesel::joinable!(tagline -> local_site (local_site_id)); diesel::allow_tables_to_appear_in_same_query!( admin_purge_comment, diff --git a/crates/db_schema/src/source/custom_emoji.rs b/crates/db_schema/src/source/custom_emoji.rs index 3217c9736..788885c97 100644 --- a/crates/db_schema/src/source/custom_emoji.rs +++ b/crates/db_schema/src/source/custom_emoji.rs @@ -1,4 +1,4 @@ -use crate::newtypes::{CustomEmojiId, DbUrl, LocalSiteId}; +use crate::newtypes::{CustomEmojiId, DbUrl}; #[cfg(feature = "full")] use crate::schema::custom_emoji; use chrono::{DateTime, Utc}; @@ -10,21 +10,13 @@ use typed_builder::TypedBuilder; #[skip_serializing_none] #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] -#[cfg_attr( - feature = "full", - derive(Queryable, Selectable, Associations, Identifiable, TS) -)] +#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))] #[cfg_attr(feature = "full", diesel(table_name = custom_emoji))] -#[cfg_attr( - feature = "full", - diesel(belongs_to(crate::source::local_site::LocalSite)) -)] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A custom emoji. pub struct CustomEmoji { pub id: CustomEmojiId, - pub local_site_id: LocalSiteId, pub shortcode: String, pub image_url: DbUrl, pub alt_text: String, @@ -37,7 +29,6 @@ pub struct CustomEmoji { #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = custom_emoji))] pub struct CustomEmojiInsertForm { - pub local_site_id: LocalSiteId, pub shortcode: String, pub image_url: DbUrl, pub alt_text: String, @@ -48,7 +39,6 @@ pub struct CustomEmojiInsertForm { #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = custom_emoji))] pub struct CustomEmojiUpdateForm { - pub local_site_id: LocalSiteId, pub image_url: DbUrl, pub alt_text: String, pub category: String, diff --git a/crates/db_schema/src/source/tagline.rs b/crates/db_schema/src/source/tagline.rs index dbc904a78..05f7e0520 100644 --- a/crates/db_schema/src/source/tagline.rs +++ b/crates/db_schema/src/source/tagline.rs @@ -1,4 +1,3 @@ -use crate::newtypes::LocalSiteId; #[cfg(feature = "full")] use crate::schema::tagline; use chrono::{DateTime, Utc}; @@ -9,21 +8,13 @@ use ts_rs::TS; #[skip_serializing_none] #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] -#[cfg_attr( - feature = "full", - derive(Queryable, Selectable, Associations, Identifiable, TS) -)] +#[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))] #[cfg_attr(feature = "full", diesel(table_name = tagline))] -#[cfg_attr( - feature = "full", - diesel(belongs_to(crate::source::local_site::LocalSite)) -)] #[cfg_attr(feature = "full", diesel(check_for_backend(diesel::pg::Pg)))] #[cfg_attr(feature = "full", ts(export))] /// A tagline, shown at the top of your site. pub struct Tagline { pub id: i32, - pub local_site_id: LocalSiteId, pub content: String, pub published: DateTime, pub updated: Option>, @@ -32,8 +23,14 @@ pub struct Tagline { #[derive(Clone, Default)] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", diesel(table_name = tagline))] -pub struct TaglineForm { - pub local_site_id: LocalSiteId, +pub struct TaglineInsertForm { pub content: String, - pub updated: Option>, +} + +#[derive(Clone, Default)] +#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] +#[cfg_attr(feature = "full", diesel(table_name = tagline))] +pub struct TaglineUpdateForm { + pub content: String, + pub updated: DateTime, } diff --git a/crates/db_views/src/custom_emoji_view.rs b/crates/db_views/src/custom_emoji_view.rs index 4d2f1fd85..a346c086d 100644 --- a/crates/db_views/src/custom_emoji_view.rs +++ b/crates/db_views/src/custom_emoji_view.rs @@ -2,10 +2,10 @@ use crate::structs::CustomEmojiView; use diesel::{result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl}; use diesel_async::RunQueryDsl; use lemmy_db_schema::{ - newtypes::{CustomEmojiId, LocalSiteId}, + newtypes::CustomEmojiId, schema::{custom_emoji, custom_emoji_keyword}, source::{custom_emoji::CustomEmoji, custom_emoji_keyword::CustomEmojiKeyword}, - utils::{get_conn, DbPool}, + utils::{get_conn, limit_and_offset, DbPool}, }; use std::collections::HashMap; @@ -35,18 +35,34 @@ impl CustomEmojiView { } } - pub async fn get_all( + pub async fn list( pool: &mut DbPool<'_>, - for_local_site_id: LocalSiteId, + category: &Option, + page: Option, + limit: Option, + ignore_page_limits: bool, ) -> Result, Error> { let conn = &mut get_conn(pool).await?; - let emojis = custom_emoji::table - .filter(custom_emoji::local_site_id.eq(for_local_site_id)) + + let mut query = custom_emoji::table .left_join( custom_emoji_keyword::table.on(custom_emoji_keyword::custom_emoji_id.eq(custom_emoji::id)), ) .order(custom_emoji::category) - .then_order_by(custom_emoji::id) + .into_boxed(); + + if !ignore_page_limits { + let (limit, offset) = limit_and_offset(page, limit)?; + query = query.limit(limit).offset(offset); + } + + if let Some(category) = category { + query = query.filter(custom_emoji::category.eq(category)) + } + + query = query.then_order_by(custom_emoji::id); + + let emojis = query .select(( custom_emoji::all_columns, custom_emoji_keyword::all_columns.nullable(), // (or all the columns if you want) diff --git a/migrations/2024-04-08-204327_custom_emoji_tagline_changes/down.sql b/migrations/2024-04-08-204327_custom_emoji_tagline_changes/down.sql new file mode 100644 index 000000000..a6b01a1d1 --- /dev/null +++ b/migrations/2024-04-08-204327_custom_emoji_tagline_changes/down.sql @@ -0,0 +1,32 @@ +ALTER TABLE custom_emoji + ADD COLUMN local_site_id int REFERENCES local_site (site_id) ON UPDATE CASCADE ON DELETE CASCADE; + +UPDATE + custom_emoji +SET + local_site_id = ( + SELECT + site_id + FROM + local_site + LIMIT 1); + +ALTER TABLE custom_emoji + ALTER COLUMN local_site_id SET NOT NULL; + +ALTER TABLE tagline + ADD COLUMN local_site_id int REFERENCES local_site (site_id) ON UPDATE CASCADE ON DELETE CASCADE; + +UPDATE + tagline +SET + local_site_id = ( + SELECT + site_id + FROM + local_site + LIMIT 1); + +ALTER TABLE tagline + ALTER COLUMN local_site_id SET NOT NULL; + diff --git a/migrations/2024-04-08-204327_custom_emoji_tagline_changes/up.sql b/migrations/2024-04-08-204327_custom_emoji_tagline_changes/up.sql new file mode 100644 index 000000000..5aa073f76 --- /dev/null +++ b/migrations/2024-04-08-204327_custom_emoji_tagline_changes/up.sql @@ -0,0 +1,6 @@ +ALTER TABLE custom_emoji + DROP COLUMN local_site_id; + +ALTER TABLE tagline + DROP COLUMN local_site_id; + diff --git a/src/api_routes_http.rs b/src/api_routes_http.rs index faa7d78f2..65931e810 100644 --- a/src/api_routes_http.rs +++ b/src/api_routes_http.rs @@ -107,6 +107,7 @@ use lemmy_api_crud::{ custom_emoji::{ create::create_custom_emoji, delete::delete_custom_emoji, + list::list_custom_emojis, update::update_custom_emoji, }, oauth_provider::{ @@ -128,6 +129,12 @@ use lemmy_api_crud::{ update::update_private_message, }, site::{create::create_site, read::get_site, update::update_site}, + tagline::{ + create::create_tagline, + delete::delete_tagline, + list::list_taglines, + update::update_tagline, + }, user::{ create::{authenticate_with_oauth, register}, delete::delete_account, @@ -381,6 +388,14 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .route("/community", web::post().to(purge_community)) .route("/post", web::post().to(purge_post)) .route("/comment", web::post().to(purge_comment)), + ) + .service( + web::scope("/tagline") + .wrap(rate_limit.message()) + .route("", web::post().to(create_tagline)) + .route("", web::put().to(update_tagline)) + .route("/delete", web::post().to(delete_tagline)) + .route("/list", web::get().to(list_taglines)), ), ) .service( @@ -388,7 +403,8 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) { .wrap(rate_limit.message()) .route("", web::post().to(create_custom_emoji)) .route("", web::put().to(update_custom_emoji)) - .route("/delete", web::post().to(delete_custom_emoji)), + .route("/delete", web::post().to(delete_custom_emoji)) + .route("/list", web::get().to(list_custom_emojis)), ) .service( web::scope("/oauth_provider")