Freakazoid182 1 month ago committed by GitHub
commit ff21690fd8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -11,7 +11,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},
@ -62,10 +62,8 @@ 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 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,8 +72,7 @@ pub async fn leave_admin(
my_user: None, my_user: None,
all_languages, all_languages,
discussion_languages, discussion_languages,
taglines,
custom_emojis,
blocked_urls, blocked_urls,
tagline,
})) }))
} }

@ -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>,
}

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

@ -18,7 +18,6 @@ use lemmy_db_schema::{
}; };
use lemmy_db_views::structs::{ use lemmy_db_views::structs::{
CommentView, CommentView,
CustomEmojiView,
LocalUserView, LocalUserView,
PostView, PostView,
RegistrationApplicationView, RegistrationApplicationView,
@ -190,7 +189,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 content_warning: Option<String>, pub content_warning: Option<String>,
pub default_post_listing_mode: Option<PostListingMode>, pub default_post_listing_mode: Option<PostListingMode>,
@ -271,8 +269,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 to email admins for new reports. /// Whether to email admins for new reports.
pub reports_email_admins: Option<bool>, pub reports_email_admins: Option<bool>,
@ -289,7 +285,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]
@ -304,10 +299,7 @@ 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. pub tagline: Option<Tagline>,
pub taglines: Vec<Tagline>,
/// A list of custom emojis your site supports.
pub custom_emojis: Vec<CustomEmojiView>,
pub blocked_urls: Vec<LocalSiteUrlBlocklist>, pub blocked_urls: Vec<LocalSiteUrlBlocklist>,
} }

@ -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>,
}

@ -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::{
custom_emoji::{CustomEmoji, CustomEmojiInsertForm}, source::{
custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, custom_emoji::{CustomEmoji, CustomEmojiInsertForm},
local_site::LocalSite, custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
},
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())

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

@ -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 }))
}

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

@ -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::{
custom_emoji::{CustomEmoji, CustomEmojiUpdateForm}, source::{
custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm}, custom_emoji::{CustomEmoji, CustomEmojiUpdateForm},
local_site::LocalSite, custom_emoji_keyword::{CustomEmojiKeyword, CustomEmojiKeywordInsertForm},
},
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())

@ -4,4 +4,5 @@ pub mod custom_emoji;
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;

@ -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_option_overwrite, naive_now}, utils::{diesel_option_overwrite, naive_now},
@ -133,17 +132,11 @@ pub async fn create_site(
.await? .await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?; .ok_or(LemmyErrorType::LocalSiteNotSetup)?;
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<()> {
@ -586,7 +579,6 @@ mod tests {
captcha_difficulty: None, captcha_difficulty: None,
allowed_instances: None, allowed_instances: None,
blocked_instances: None, blocked_instances: None,
taglines: None,
registration_mode: site_registration_mode, registration_mode: site_registration_mode,
content_warning: None, content_warning: None,
default_post_listing_mode: None, default_post_listing_mode: None,

@ -9,7 +9,7 @@ use lemmy_db_schema::source::{
local_site_url_blocklist::LocalSiteUrlBlocklist, local_site_url_blocklist::LocalSiteUrlBlocklist,
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::{ use lemmy_db_views_actor::structs::{
CommunityBlockView, CommunityBlockView,
CommunityFollowerView, CommunityFollowerView,
@ -47,10 +47,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?;
Ok(GetSiteResponse { Ok(GetSiteResponse {
site_view, site_view,
admins, admins,
@ -58,9 +56,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,
}) })
}) })
.await .await

@ -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_option_overwrite, naive_now}, utils::{diesel_option_overwrite, naive_now},
@ -180,9 +179,6 @@ 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()) let site_view = SiteView::read_local(&mut context.pool())
.await? .await?
.ok_or(LemmyErrorType::LocalSiteNotSetup)?; .ok_or(LemmyErrorType::LocalSiteNotSetup)?;
@ -191,10 +187,7 @@ pub async fn update_site(
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<()> {
@ -597,7 +590,6 @@ mod tests {
allowed_instances: None, allowed_instances: None,
blocked_instances: None, blocked_instances: None,
blocked_urls: None, blocked_urls: None,
taglines: None,
registration_mode: site_registration_mode, registration_mode: site_registration_mode,
reports_email_admins: None, reports_email_admins: None,
content_warning: None, content_warning: None,

@ -0,0 +1,41 @@
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, utils::validation::is_valid_tagline_content};
#[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?;
is_valid_tagline_content(&content)?;
let tagline_form = TaglineInsertForm {
content: Some(content),
};
let tagline = Tagline::create(&mut context.pool(), &tagline_form).await?;
Ok(Json(TaglineResponse { tagline }))
}

@ -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()))
}

@ -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 }))
}

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

@ -0,0 +1,43 @@
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, utils::validation::is_valid_tagline_content};
#[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?;
is_valid_tagline_content(&content)?;
let tagline_form = TaglineUpdateForm {
content: Some(content),
updated: Some(Some(naive_now())),
};
let tagline = Tagline::update(&mut context.pool(), data.id, &tagline_form).await?;
Ok(Json(TaglineResponse { tagline }))
}

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

@ -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> {
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 { async fn create(pool: &mut DbPool<'_>, form: &Self::InsertForm) -> Result<Self, Error> {
let form = TaglineForm { let conn = &mut get_conn(pool).await?;
local_site_id: for_local_site_id, insert_into(tagline)
content: item, .values(form)
updated: None, .get_result::<Self>(conn)
}; .await
insert_into(tagline)
.values(form)
.get_result::<Self>(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
}
} }
async fn clear(conn: &mut AsyncPgConnection) -> Result<usize, Error> { async fn update(
diesel::delete(tagline).execute(conn).await pool: &mut DbPool<'_>,
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
} }
}
pub async fn get_all( impl Tagline {
pub async fn list(
pool: &mut DbPool<'_>, pool: &mut DbPool<'_>,
for_local_site_id: LocalSiteId, 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()
}
} }

@ -152,6 +152,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);
#[cfg(feature = "full")] #[cfg(feature = "full")]
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(remote = "Ltree")] #[serde(remote = "Ltree")]

@ -254,7 +254,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,
@ -929,7 +928,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>,
@ -966,7 +964,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));
@ -1028,7 +1025,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,

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

@ -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: Option<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: Option<String>,
pub updated: Option<Option<DateTime<Utc>>>,
} }

@ -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,39 @@ impl CustomEmojiView {
} }
} }
pub async fn get_all( pub async fn get_all(pool: &mut DbPool<'_>) -> Result<Vec<Self>, Error> {
Self::list(pool, &None, None, None, true).await
}
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) .into_boxed();
.then_order_by(custom_emoji::id)
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))
.order(custom_emoji::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)

@ -176,6 +176,7 @@ pub enum LemmyErrorType {
InvalidUnixTime, InvalidUnixTime,
InvalidBotAction, InvalidBotAction,
CantBlockLocalInstance, CantBlockLocalInstance,
TaglineInvalid,
Unknown(String), Unknown(String),
} }

@ -22,6 +22,8 @@ const BIO_MAX_LENGTH: usize = 300;
const ALT_TEXT_MAX_LENGTH: usize = 300; const ALT_TEXT_MAX_LENGTH: usize = 300;
const SITE_NAME_MAX_LENGTH: usize = 20; const SITE_NAME_MAX_LENGTH: usize = 20;
const SITE_NAME_MIN_LENGTH: usize = 1; const SITE_NAME_MIN_LENGTH: usize = 1;
const TAGLINE_CONTENT_MIN_LENGTH: usize = 1;
const TAGLINE_CONTENT_MAX_LENGTH: usize = 50000;
const SITE_DESCRIPTION_MAX_LENGTH: usize = 150; const SITE_DESCRIPTION_MAX_LENGTH: usize = 150;
//Invisible unicode characters, taken from https://invisible-characters.com/ //Invisible unicode characters, taken from https://invisible-characters.com/
const FORBIDDEN_DISPLAY_CHARS: [char; 53] = [ const FORBIDDEN_DISPLAY_CHARS: [char; 53] = [
@ -169,6 +171,20 @@ pub fn is_valid_body_field(body: &Option<String>, post: bool) -> LemmyResult<()>
Ok(()) Ok(())
} }
pub fn is_valid_tagline_content(content: &str) -> LemmyResult<()> {
min_length_check(
content,
TAGLINE_CONTENT_MIN_LENGTH,
LemmyErrorType::TaglineInvalid,
)?;
max_length_check(
content,
TAGLINE_CONTENT_MAX_LENGTH,
LemmyErrorType::TaglineInvalid,
)?;
Ok(())
}
pub fn is_valid_bio_field(bio: &str) -> LemmyResult<()> { pub fn is_valid_bio_field(bio: &str) -> LemmyResult<()> {
max_length_check(bio, BIO_MAX_LENGTH, LemmyErrorType::BioLengthOverflow) max_length_check(bio, BIO_MAX_LENGTH, LemmyErrorType::BioLengthOverflow)
} }

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

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

@ -106,6 +106,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,
}, },
post::{ post::{
@ -122,6 +123,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::{create::register, delete::delete_account}, user::{create::register, delete::delete_account},
}; };
use lemmy_apub::api::{ use lemmy_apub::api::{
@ -354,6 +361,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(
@ -361,7 +376,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)),
), ),
); );
cfg.service( cfg.service(

Loading…
Cancel
Save