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 <tyhou13@gmx.com>
Co-authored-by: Felix Ableitner <me@nutomic.com>
This commit is contained in:
Freakazoid182 2024-09-19 11:15:04 +02:00 committed by GitHub
parent 026e23cf32
commit 43f20881cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 417 additions and 146 deletions

View File

@ -12,7 +12,7 @@ use lemmy_db_schema::{
}, },
traits::Crud, 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_db_views_actor::structs::PersonView;
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyErrorType, LemmyResult}, error::{LemmyErrorType, LemmyResult},
@ -61,11 +61,9 @@ pub async fn leave_admin(
let all_languages = Language::read_all(&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 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 oauth_providers = OAuthProvider::get_all_public(&mut context.pool()).await?;
let blocked_urls = LocalSiteUrlBlocklist::get_all(&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 { Ok(Json(GetSiteResponse {
site_view, site_view,
@ -74,10 +72,9 @@ pub async fn leave_admin(
my_user: None, my_user: None,
all_languages, all_languages,
discussion_languages, discussion_languages,
taglines,
custom_emojis,
oauth_providers: Some(oauth_providers), oauth_providers: Some(oauth_providers),
admin_oauth_providers: None, admin_oauth_providers: None,
blocked_urls, blocked_urls,
tagline,
})) }))
} }

View File

@ -1,6 +1,7 @@
use lemmy_db_schema::newtypes::CustomEmojiId; use lemmy_db_schema::newtypes::CustomEmojiId;
use lemmy_db_views::structs::CustomEmojiView; use lemmy_db_views::structs::CustomEmojiView;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_with::skip_serializing_none;
#[cfg(feature = "full")] #[cfg(feature = "full")]
use ts_rs::TS; use ts_rs::TS;
use url::Url; use url::Url;
@ -46,3 +47,23 @@ pub struct DeleteCustomEmoji {
pub struct CustomEmojiResponse { pub struct CustomEmojiResponse {
pub custom_emoji: CustomEmojiView, 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<CustomEmojiView>,
}
#[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<i64>,
pub limit: Option<i64>,
pub category: Option<String>,
pub ignore_page_limits: Option<bool>,
}

View File

@ -16,6 +16,7 @@ pub mod request;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod send_activity; pub mod send_activity;
pub mod site; pub mod site;
pub mod tagline;
#[cfg(feature = "full")] #[cfg(feature = "full")]
pub mod utils; pub mod utils;

View File

@ -30,7 +30,6 @@ use lemmy_db_schema::{
}; };
use lemmy_db_views::structs::{ use lemmy_db_views::structs::{
CommentView, CommentView,
CustomEmojiView,
LocalUserView, LocalUserView,
PostView, PostView,
RegistrationApplicationView, RegistrationApplicationView,
@ -202,7 +201,6 @@ pub struct CreateSite {
pub captcha_difficulty: Option<String>, pub captcha_difficulty: Option<String>,
pub allowed_instances: Option<Vec<String>>, pub allowed_instances: Option<Vec<String>>,
pub blocked_instances: Option<Vec<String>>, pub blocked_instances: Option<Vec<String>>,
pub taglines: Option<Vec<String>>,
pub registration_mode: Option<RegistrationMode>, pub registration_mode: Option<RegistrationMode>,
pub oauth_registration: Option<bool>, pub oauth_registration: Option<bool>,
pub content_warning: Option<String>, pub content_warning: Option<String>,
@ -288,8 +286,6 @@ pub struct EditSite {
pub blocked_instances: Option<Vec<String>>, pub blocked_instances: Option<Vec<String>>,
/// A list of blocked URLs /// A list of blocked URLs
pub blocked_urls: Option<Vec<String>>, pub blocked_urls: Option<Vec<String>>,
/// A list of taglines shown at the top of the front page.
pub taglines: Option<Vec<String>>,
pub registration_mode: Option<RegistrationMode>, pub registration_mode: Option<RegistrationMode>,
/// Whether or not external auth methods can auto-register users. /// Whether or not external auth methods can auto-register users.
pub oauth_registration: Option<bool>, pub oauth_registration: Option<bool>,
@ -306,7 +302,6 @@ pub struct EditSite {
/// The response for a site. /// The response for a site.
pub struct SiteResponse { pub struct SiteResponse {
pub site_view: SiteView, pub site_view: SiteView,
pub taglines: Vec<Tagline>,
} }
#[skip_serializing_none] #[skip_serializing_none]
@ -321,10 +316,8 @@ pub struct GetSiteResponse {
pub my_user: Option<MyUserInfo>, pub my_user: Option<MyUserInfo>,
pub all_languages: Vec<Language>, pub all_languages: Vec<Language>,
pub discussion_languages: Vec<LanguageId>, pub discussion_languages: Vec<LanguageId>,
/// A list of taglines shown at the top of the front page. /// If the site has any taglines, a random one is included here for displaying
pub taglines: Vec<Tagline>, pub tagline: Option<Tagline>,
/// A list of custom emojis your site supports.
pub custom_emojis: Vec<CustomEmojiView>,
/// A list of external auth methods your site supports. /// A list of external auth methods your site supports.
pub oauth_providers: Option<Vec<PublicOAuthProvider>>, pub oauth_providers: Option<Vec<PublicOAuthProvider>>,
pub admin_oauth_providers: Option<Vec<OAuthProvider>>, pub admin_oauth_providers: Option<Vec<OAuthProvider>>,

View File

@ -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<Tagline>,
}
#[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<i64>,
pub limit: Option<i64>,
}

View File

@ -5,10 +5,12 @@ use lemmy_api_common::{
custom_emoji::{CreateCustomEmoji, CustomEmojiResponse}, custom_emoji::{CreateCustomEmoji, CustomEmojiResponse},
utils::is_admin, utils::is_admin,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::{
source::{
custom_emoji::{CustomEmoji, CustomEmojiInsertForm}, custom_emoji::{CustomEmoji, CustomEmojiInsertForm},
custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
local_site::LocalSite, },
traits::Crud,
}; };
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; use lemmy_db_views::structs::{CustomEmojiView, LocalUserView};
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
@ -19,12 +21,10 @@ pub async fn create_custom_emoji(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<CustomEmojiResponse>> { ) -> LemmyResult<Json<CustomEmojiResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin // Make sure user is an admin
is_admin(&local_user_view)?; is_admin(&local_user_view)?;
let emoji_form = CustomEmojiInsertForm::builder() let emoji_form = CustomEmojiInsertForm::builder()
.local_site_id(local_site.id)
.shortcode(data.shortcode.to_lowercase().trim().to_string()) .shortcode(data.shortcode.to_lowercase().trim().to_string())
.alt_text(data.alt_text.to_string()) .alt_text(data.alt_text.to_string())
.category(data.category.to_string()) .category(data.category.to_string())

View File

@ -6,7 +6,7 @@ use lemmy_api_common::{
utils::is_admin, utils::is_admin,
SuccessResponse, 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_db_views::structs::LocalUserView;
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;

View File

@ -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<ListCustomEmojis>,
local_user_view: Option<LocalUserView>,
context: Data<LemmyContext>,
) -> Result<Json<ListCustomEmojisResponse>, 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 }))
}

View File

@ -1,3 +1,4 @@
pub mod create; pub mod create;
pub mod delete; pub mod delete;
pub mod list;
pub mod update; pub mod update;

View File

@ -5,10 +5,12 @@ use lemmy_api_common::{
custom_emoji::{CustomEmojiResponse, EditCustomEmoji}, custom_emoji::{CustomEmojiResponse, EditCustomEmoji},
utils::is_admin, utils::is_admin,
}; };
use lemmy_db_schema::source::{ use lemmy_db_schema::{
source::{
custom_emoji::{CustomEmoji, CustomEmojiUpdateForm}, custom_emoji::{CustomEmoji, CustomEmojiUpdateForm},
custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
local_site::LocalSite, },
traits::Crud,
}; };
use lemmy_db_views::structs::{CustomEmojiView, LocalUserView}; use lemmy_db_views::structs::{CustomEmojiView, LocalUserView};
use lemmy_utils::error::LemmyResult; use lemmy_utils::error::LemmyResult;
@ -19,12 +21,10 @@ pub async fn update_custom_emoji(
context: Data<LemmyContext>, context: Data<LemmyContext>,
local_user_view: LocalUserView, local_user_view: LocalUserView,
) -> LemmyResult<Json<CustomEmojiResponse>> { ) -> LemmyResult<Json<CustomEmojiResponse>> {
let local_site = LocalSite::read(&mut context.pool()).await?;
// Make sure user is an admin // Make sure user is an admin
is_admin(&local_user_view)?; is_admin(&local_user_view)?;
let emoji_form = CustomEmojiUpdateForm::builder() let emoji_form = CustomEmojiUpdateForm::builder()
.local_site_id(local_site.id)
.alt_text(data.alt_text.to_string()) .alt_text(data.alt_text.to_string())
.category(data.category.to_string()) .category(data.category.to_string())
.image_url(data.clone().image_url.into()) .image_url(data.clone().image_url.into())

View File

@ -5,4 +5,5 @@ pub mod oauth_provider;
pub mod post; pub mod post;
pub mod private_message; pub mod private_message;
pub mod site; pub mod site;
pub mod tagline;
pub mod user; pub mod user;

View File

@ -20,7 +20,6 @@ use lemmy_db_schema::{
local_site::{LocalSite, LocalSiteUpdateForm}, local_site::{LocalSite, LocalSiteUpdateForm},
local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitUpdateForm}, local_site_rate_limit::{LocalSiteRateLimit, LocalSiteRateLimitUpdateForm},
site::{Site, SiteUpdateForm}, site::{Site, SiteUpdateForm},
tagline::Tagline,
}, },
traits::Crud, traits::Crud,
utils::{diesel_string_update, diesel_url_create, naive_now}, 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 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 = let rate_limit_config =
local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit);
context.rate_limit_cell().set_config(rate_limit_config); context.rate_limit_cell().set_config(rate_limit_config);
Ok(Json(SiteResponse { Ok(Json(SiteResponse { site_view }))
site_view,
taglines,
}))
} }
fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) -> LemmyResult<()> { fn validate_create_payload(local_site: &LocalSite, create_site: &CreateSite) -> LemmyResult<()> {

View File

@ -13,7 +13,7 @@ use lemmy_db_schema::source::{
person_block::PersonBlock, person_block::PersonBlock,
tagline::Tagline, 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_db_views_actor::structs::{CommunityFollowerView, CommunityModeratorView, PersonView};
use lemmy_utils::{ use lemmy_utils::{
error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult}, error::{LemmyError, LemmyErrorExt, LemmyErrorType, LemmyResult},
@ -42,10 +42,8 @@ pub async fn get_site(
let admins = PersonView::admins(&mut context.pool()).await?; let admins = PersonView::admins(&mut context.pool()).await?;
let all_languages = Language::read_all(&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 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 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 admin_oauth_providers = OAuthProvider::get_all(&mut context.pool()).await?;
let oauth_providers = let oauth_providers =
OAuthProvider::convert_providers_to_public(admin_oauth_providers.clone()); OAuthProvider::convert_providers_to_public(admin_oauth_providers.clone());
@ -57,9 +55,8 @@ pub async fn get_site(
my_user: None, my_user: None,
all_languages, all_languages,
discussion_languages, discussion_languages,
taglines,
custom_emojis,
blocked_urls, blocked_urls,
tagline,
oauth_providers: Some(oauth_providers), oauth_providers: Some(oauth_providers),
admin_oauth_providers: Some(admin_oauth_providers), admin_oauth_providers: Some(admin_oauth_providers),
}) })

View File

@ -24,7 +24,6 @@ use lemmy_db_schema::{
local_site_url_blocklist::LocalSiteUrlBlocklist, local_site_url_blocklist::LocalSiteUrlBlocklist,
local_user::LocalUser, local_user::LocalUser,
site::{Site, SiteUpdateForm}, site::{Site, SiteUpdateForm},
tagline::Tagline,
}, },
traits::Crud, traits::Crud,
utils::{diesel_string_update, diesel_url_update, naive_now}, utils::{diesel_string_update, diesel_url_update, naive_now},
@ -187,19 +186,13 @@ pub async fn update_site(
.with_lemmy_type(LemmyErrorType::CouldntSetAllEmailVerified)?; .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 site_view = SiteView::read_local(&mut context.pool()).await?;
let rate_limit_config = let rate_limit_config =
local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit); local_site_rate_limit_to_rate_limit_config(&site_view.local_site_rate_limit);
context.rate_limit_cell().set_config(rate_limit_config); context.rate_limit_cell().set_config(rate_limit_config);
Ok(Json(SiteResponse { Ok(Json(SiteResponse { site_view }))
site_view,
taglines,
}))
} }
fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> LemmyResult<()> { fn validate_update_payload(local_site: &LocalSite, edit_site: &EditSite) -> LemmyResult<()> {

View File

@ -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<CreateTagline>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<TaglineResponse>, 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 }))
}

View File

@ -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<DeleteTagline>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<SuccessResponse>, LemmyError> {
// Make sure user is an admin
is_admin(&local_user_view)?;
Tagline::delete(&mut context.pool(), data.id).await?;
Ok(Json(SuccessResponse::default()))
}

View File

@ -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<ListTaglines>,
local_user_view: Option<LocalUserView>,
context: Data<LemmyContext>,
) -> Result<Json<ListTaglinesResponse>, LemmyError> {
let taglines = Tagline::list(&mut context.pool(), data.page, data.limit).await?;
Ok(Json(ListTaglinesResponse { taglines }))
}

View File

@ -0,0 +1,4 @@
pub mod create;
pub mod delete;
pub mod list;
pub mod update;

View File

@ -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<UpdateTagline>,
context: Data<LemmyContext>,
local_user_view: LocalUserView,
) -> Result<Json<TaglineResponse>, 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 }))
}

View File

@ -8,36 +8,37 @@ use crate::{
custom_emoji::{CustomEmoji, CustomEmojiInsertForm, CustomEmojiUpdateForm}, custom_emoji::{CustomEmoji, CustomEmojiInsertForm, CustomEmojiUpdateForm},
custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
}, },
traits::Crud,
utils::{get_conn, DbPool}, utils::{get_conn, DbPool},
}; };
use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel::{dsl::insert_into, result::Error, ExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
impl CustomEmoji { #[async_trait]
pub async fn create(pool: &mut DbPool<'_>, form: &CustomEmojiInsertForm) -> Result<Self, Error> { impl Crud for CustomEmoji {
type InsertForm = CustomEmojiInsertForm;
type UpdateForm = CustomEmojiUpdateForm;
type IdType = CustomEmojiId;
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
insert_into(custom_emoji) insert_into(custom_emoji)
.values(form) .values(form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
pub async fn update(
async fn update(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
emoji_id: CustomEmojiId, emoji_id: Self::IdType,
form: &CustomEmojiUpdateForm, new_custom_emoji: &Self::UpdateForm,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
diesel::update(custom_emoji.find(emoji_id)) diesel::update(custom_emoji.find(emoji_id))
.set(form) .set(new_custom_emoji)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await .await
} }
pub async fn delete(pool: &mut DbPool<'_>, emoji_id: CustomEmojiId) -> Result<usize, Error> {
let conn = &mut get_conn(pool).await?;
diesel::delete(custom_emoji.find(emoji_id))
.execute(conn)
.await
}
} }
impl CustomEmojiKeyword { impl CustomEmojiKeyword {

View File

@ -1,58 +1,64 @@
use crate::{ use crate::{
newtypes::LocalSiteId, newtypes::TaglineId,
schema::tagline::dsl::{local_site_id, tagline}, schema::tagline::dsl::{published, tagline},
source::tagline::{Tagline, TaglineForm}, source::tagline::{Tagline, TaglineInsertForm, TaglineUpdateForm},
utils::{get_conn, DbPool}, traits::Crud,
utils::{get_conn, limit_and_offset, DbPool},
}; };
use diesel::{insert_into, result::Error, ExpressionMethods, QueryDsl}; use diesel::{insert_into, result::Error, ExpressionMethods, OptionalExtension, QueryDsl};
use diesel_async::{AsyncPgConnection, RunQueryDsl}; use diesel_async::RunQueryDsl;
impl Tagline { #[async_trait]
pub async fn replace( impl Crud for Tagline {
pool: &mut DbPool<'_>, type InsertForm = TaglineInsertForm;
for_local_site_id: LocalSiteId, type UpdateForm = TaglineUpdateForm;
list_content: Option<Vec<String>>, type IdType = TaglineId;
) -> Result<Vec<Self>, Error> {
async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?; 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) insert_into(tagline)
.values(form) .values(form)
.get_result::<Self>(conn) .get_result::<Self>(conn)
.await?;
}
Self::get_all(&mut conn.into(), for_local_site_id).await
}) as _
})
.await .await
} else {
Self::get_all(&mut conn.into(), for_local_site_id).await
}
} }
async fn clear(conn: &mut AsyncPgConnection) -> Result<usize, Error> { async fn update(
diesel::delete(tagline).execute(conn).await
}
pub async fn get_all(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
for_local_site_id: LocalSiteId, tagline_id: TaglineId,
new_tagline: &Self::UpdateForm,
) -> Result<Self, Error> {
let conn = &mut get_conn(pool).await?;
diesel::update(tagline.find(tagline_id))
.set(new_tagline)
.get_result::<Self>(conn)
.await
}
}
impl Tagline {
pub async fn list(
pool: &mut DbPool<'_>,
page: Option<i64>,
limit: Option<i64>,
) -> Result<Vec<Self>, Error> { ) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?; let conn = &mut get_conn(pool).await?;
let (limit, offset) = limit_and_offset(page, limit)?;
tagline tagline
.filter(local_site_id.eq(for_local_site_id)) .order(published.desc())
.offset(offset)
.limit(limit)
.get_results::<Self>(conn) .get_results::<Self>(conn)
.await .await
} }
pub async fn get_random(pool: &mut DbPool<'_>) -> Result<Option<Self>, Error> {
let conn = &mut get_conn(pool).await?;
sql_function!(fn random() -> Text);
tagline
.order(random())
.limit(1)
.first::<Self>(conn)
.await
.optional()
}
} }

View File

@ -148,6 +148,12 @@ pub struct LocalSiteId(i32);
/// The custom emoji id. /// The custom emoji id.
pub struct CustomEmojiId(i32); 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)] #[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "full", derive(DieselNewType, TS))] #[cfg_attr(feature = "full", derive(DieselNewType, TS))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]

View File

@ -258,7 +258,6 @@ diesel::table! {
diesel::table! { diesel::table! {
custom_emoji (id) { custom_emoji (id) {
id -> Int4, id -> Int4,
local_site_id -> Int4,
#[max_length = 128] #[max_length = 128]
shortcode -> Varchar, shortcode -> Varchar,
image_url -> Text, image_url -> Text,
@ -974,7 +973,6 @@ diesel::table! {
diesel::table! { diesel::table! {
tagline (id) { tagline (id) {
id -> Int4, id -> Int4,
local_site_id -> Int4,
content -> Text, content -> Text,
published -> Timestamptz, published -> Timestamptz,
updated -> Nullable<Timestamptz>, updated -> Nullable<Timestamptz>,
@ -1011,7 +1009,6 @@ diesel::joinable!(community_moderator -> community (community_id));
diesel::joinable!(community_moderator -> person (person_id)); diesel::joinable!(community_moderator -> person (person_id));
diesel::joinable!(community_person_ban -> community (community_id)); diesel::joinable!(community_person_ban -> community (community_id));
diesel::joinable!(community_person_ban -> person (person_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!(custom_emoji_keyword -> custom_emoji (custom_emoji_id));
diesel::joinable!(email_verification -> local_user (local_user_id)); diesel::joinable!(email_verification -> local_user (local_user_id));
diesel::joinable!(federation_allowlist -> instance (instance_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_aggregates -> site (site_id));
diesel::joinable!(site_language -> language (language_id)); diesel::joinable!(site_language -> language (language_id));
diesel::joinable!(site_language -> site (site_id)); diesel::joinable!(site_language -> site (site_id));
diesel::joinable!(tagline -> local_site (local_site_id));
diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!(
admin_purge_comment, admin_purge_comment,

View File

@ -1,4 +1,4 @@
use crate::newtypes::{CustomEmojiId, DbUrl, LocalSiteId}; use crate::newtypes::{CustomEmojiId, DbUrl};
#[cfg(feature = "full")] #[cfg(feature = "full")]
use crate::schema::custom_emoji; use crate::schema::custom_emoji;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@ -10,21 +10,13 @@ use typed_builder::TypedBuilder;
#[skip_serializing_none] #[skip_serializing_none]
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
#[cfg_attr( #[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable, TS)
)]
#[cfg_attr(feature = "full", diesel(table_name = custom_emoji))] #[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", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// A custom emoji. /// A custom emoji.
pub struct CustomEmoji { pub struct CustomEmoji {
pub id: CustomEmojiId, pub id: CustomEmojiId,
pub local_site_id: LocalSiteId,
pub shortcode: String, pub shortcode: String,
pub image_url: DbUrl, pub image_url: DbUrl,
pub alt_text: String, pub alt_text: String,
@ -37,7 +29,6 @@ pub struct CustomEmoji {
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = custom_emoji))] #[cfg_attr(feature = "full", diesel(table_name = custom_emoji))]
pub struct CustomEmojiInsertForm { pub struct CustomEmojiInsertForm {
pub local_site_id: LocalSiteId,
pub shortcode: String, pub shortcode: String,
pub image_url: DbUrl, pub image_url: DbUrl,
pub alt_text: String, pub alt_text: String,
@ -48,7 +39,6 @@ pub struct CustomEmojiInsertForm {
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = custom_emoji))] #[cfg_attr(feature = "full", diesel(table_name = custom_emoji))]
pub struct CustomEmojiUpdateForm { pub struct CustomEmojiUpdateForm {
pub local_site_id: LocalSiteId,
pub image_url: DbUrl, pub image_url: DbUrl,
pub alt_text: String, pub alt_text: String,
pub category: String, pub category: String,

View File

@ -1,4 +1,3 @@
use crate::newtypes::LocalSiteId;
#[cfg(feature = "full")] #[cfg(feature = "full")]
use crate::schema::tagline; use crate::schema::tagline;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
@ -9,21 +8,13 @@ use ts_rs::TS;
#[skip_serializing_none] #[skip_serializing_none]
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] #[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
#[cfg_attr( #[cfg_attr(feature = "full", derive(Queryable, Selectable, Identifiable, TS))]
feature = "full",
derive(Queryable, Selectable, Associations, Identifiable, TS)
)]
#[cfg_attr(feature = "full", diesel(table_name = tagline))] #[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", diesel(check_for_backend(diesel::pg::Pg)))]
#[cfg_attr(feature = "full", ts(export))] #[cfg_attr(feature = "full", ts(export))]
/// A tagline, shown at the top of your site. /// A tagline, shown at the top of your site.
pub struct Tagline { pub struct Tagline {
pub id: i32, pub id: i32,
pub local_site_id: LocalSiteId,
pub content: String, pub content: String,
pub published: DateTime<Utc>, pub published: DateTime<Utc>,
pub updated: Option<DateTime<Utc>>, pub updated: Option<DateTime<Utc>>,
@ -32,8 +23,14 @@ pub struct Tagline {
#[derive(Clone, Default)] #[derive(Clone, Default)]
#[cfg_attr(feature = "full", derive(Insertable, AsChangeset))] #[cfg_attr(feature = "full", derive(Insertable, AsChangeset))]
#[cfg_attr(feature = "full", diesel(table_name = tagline))] #[cfg_attr(feature = "full", diesel(table_name = tagline))]
pub struct TaglineForm { pub struct TaglineInsertForm {
pub local_site_id: LocalSiteId,
pub content: String, pub content: String,
pub updated: Option<DateTime<Utc>>, }
#[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<Utc>,
} }

View File

@ -2,10 +2,10 @@ use crate::structs::CustomEmojiView;
use diesel::{result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl}; use diesel::{result::Error, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, QueryDsl};
use diesel_async::RunQueryDsl; use diesel_async::RunQueryDsl;
use lemmy_db_schema::{ use lemmy_db_schema::{
newtypes::{CustomEmojiId, LocalSiteId}, newtypes::CustomEmojiId,
schema::{custom_emoji, custom_emoji_keyword}, schema::{custom_emoji, custom_emoji_keyword},
source::{custom_emoji::CustomEmoji, custom_emoji_keyword::CustomEmojiKeyword}, source::{custom_emoji::CustomEmoji, custom_emoji_keyword::CustomEmojiKeyword},
utils::{get_conn, DbPool}, utils::{get_conn, limit_and_offset, DbPool},
}; };
use std::collections::HashMap; use std::collections::HashMap;
@ -35,18 +35,34 @@ impl CustomEmojiView {
} }
} }
pub async fn get_all( pub async fn list(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
for_local_site_id: LocalSiteId, category: &Option<String>,
page: Option<i64>,
limit: Option<i64>,
ignore_page_limits: bool,
) -> Result<Vec<Self>, Error> { ) -> Result<Vec<Self>, Error> {
let conn = &mut get_conn(pool).await?; 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( .left_join(
custom_emoji_keyword::table.on(custom_emoji_keyword::custom_emoji_id.eq(custom_emoji::id)), custom_emoji_keyword::table.on(custom_emoji_keyword::custom_emoji_id.eq(custom_emoji::id)),
) )
.order(custom_emoji::category) .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(( .select((
custom_emoji::all_columns, custom_emoji::all_columns,
custom_emoji_keyword::all_columns.nullable(), // (or all the columns if you want) custom_emoji_keyword::all_columns.nullable(), // (or all the columns if you want)

View File

@ -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;

View File

@ -0,0 +1,6 @@
ALTER TABLE custom_emoji
DROP COLUMN local_site_id;
ALTER TABLE tagline
DROP COLUMN local_site_id;

View File

@ -107,6 +107,7 @@ use lemmy_api_crud::{
custom_emoji::{ custom_emoji::{
create::create_custom_emoji, create::create_custom_emoji,
delete::delete_custom_emoji, delete::delete_custom_emoji,
list::list_custom_emojis,
update::update_custom_emoji, update::update_custom_emoji,
}, },
oauth_provider::{ oauth_provider::{
@ -128,6 +129,12 @@ use lemmy_api_crud::{
update::update_private_message, update::update_private_message,
}, },
site::{create::create_site, read::get_site, update::update_site}, site::{create::create_site, read::get_site, update::update_site},
tagline::{
create::create_tagline,
delete::delete_tagline,
list::list_taglines,
update::update_tagline,
},
user::{ user::{
create::{authenticate_with_oauth, register}, create::{authenticate_with_oauth, register},
delete::delete_account, 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("/community", web::post().to(purge_community))
.route("/post", web::post().to(purge_post)) .route("/post", web::post().to(purge_post))
.route("/comment", web::post().to(purge_comment)), .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( .service(
@ -388,7 +403,8 @@ pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimitCell) {
.wrap(rate_limit.message()) .wrap(rate_limit.message())
.route("", web::post().to(create_custom_emoji)) .route("", web::post().to(create_custom_emoji))
.route("", web::put().to(update_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( .service(
web::scope("/oauth_provider") web::scope("/oauth_provider")