mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-12-23 19:31:33 +00:00
Optimize stuff
This commit is contained in:
parent
29e46abf44
commit
dbfbc8660d
19 changed files with 83 additions and 87 deletions
21
Cargo.lock
generated
21
Cargo.lock
generated
|
@ -1662,6 +1662,26 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
|
checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-map"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "988f0d17a0fa38291e5f41f71ea8d46a5d5497b9054d5a759fae2cbb819f2356"
|
||||||
|
dependencies = [
|
||||||
|
"enum-map-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "enum-map-derive"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2a4da76b3b6116d758c7ba93f7ec6a35d2e2cf24feda76c6e38a375f4d5c59f2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.103",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "enum_delegate"
|
name = "enum_delegate"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -2764,6 +2784,7 @@ dependencies = [
|
||||||
"deser-hjson",
|
"deser-hjson",
|
||||||
"diesel",
|
"diesel",
|
||||||
"doku",
|
"doku",
|
||||||
|
"enum-map",
|
||||||
"futures",
|
"futures",
|
||||||
"html2text",
|
"html2text",
|
||||||
"http",
|
"http",
|
||||||
|
|
|
@ -42,7 +42,7 @@ impl Perform for BanFromCommunity {
|
||||||
|
|
||||||
// Verify that only mods or admins can ban
|
// Verify that only mods or admins can ban
|
||||||
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
|
is_mod_or_admin(context.pool(), local_user_view.person.id, community_id).await?;
|
||||||
is_valid_body_field(&data.reason)?;
|
is_valid_body_field(data.reason.as_deref())?;
|
||||||
|
|
||||||
let community_user_ban_form = CommunityPersonBanForm {
|
let community_user_ban_form = CommunityPersonBanForm {
|
||||||
community_id: data.community_id,
|
community_id: data.community_id,
|
||||||
|
|
|
@ -30,7 +30,7 @@ impl Perform for BanPerson {
|
||||||
// Make sure user is an admin
|
// Make sure user is an admin
|
||||||
is_admin(&local_user_view)?;
|
is_admin(&local_user_view)?;
|
||||||
|
|
||||||
is_valid_body_field(&data.reason)?;
|
is_valid_body_field(data.reason.as_deref())?;
|
||||||
|
|
||||||
let ban = data.ban;
|
let ban = data.ban;
|
||||||
let banned_person_id = data.person_id;
|
let banned_person_id = data.person_id;
|
||||||
|
|
|
@ -45,11 +45,10 @@ impl PerformCrud for CreateComment {
|
||||||
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
|
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
|
||||||
let local_site = LocalSite::read(context.pool()).await?;
|
let local_site = LocalSite::read(context.pool()).await?;
|
||||||
|
|
||||||
let content_slurs_removed = remove_slurs(
|
let content_slurs_removed = remove_slurs(&data.content, &local_site_to_slur_regex(&local_site));
|
||||||
&data.content.clone(),
|
is_valid_body_field(Some(&content_slurs_removed))?;
|
||||||
&local_site_to_slur_regex(&local_site),
|
|
||||||
);
|
let mentions = scrape_text_for_mentions(&content_slurs_removed);
|
||||||
is_valid_body_field(&Some(content_slurs_removed.clone()))?;
|
|
||||||
|
|
||||||
// Check for a community ban
|
// Check for a community ban
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
|
@ -96,7 +95,7 @@ impl PerformCrud for CreateComment {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let comment_form = CommentInsertForm::builder()
|
let comment_form = CommentInsertForm::builder()
|
||||||
.content(content_slurs_removed.clone())
|
.content(content_slurs_removed.into_owned())
|
||||||
.post_id(data.post_id)
|
.post_id(data.post_id)
|
||||||
.creator_id(local_user_view.person.id)
|
.creator_id(local_user_view.person.id)
|
||||||
.language_id(Some(language_id))
|
.language_id(Some(language_id))
|
||||||
|
@ -126,7 +125,6 @@ impl PerformCrud for CreateComment {
|
||||||
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_comment"))?;
|
.map_err(|e| LemmyError::from_error_message(e, "couldnt_create_comment"))?;
|
||||||
|
|
||||||
// Scan the comment for user mentions, add those rows
|
// Scan the comment for user mentions, add those rows
|
||||||
let mentions = scrape_text_for_mentions(&content_slurs_removed);
|
|
||||||
let recipient_ids = send_local_notifs(
|
let recipient_ids = send_local_notifs(
|
||||||
mentions,
|
mentions,
|
||||||
&updated_comment,
|
&updated_comment,
|
||||||
|
|
|
@ -64,11 +64,11 @@ impl PerformCrud for EditComment {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|c| remove_slurs(c, &local_site_to_slur_regex(&local_site)));
|
.map(|c| remove_slurs(c, &local_site_to_slur_regex(&local_site)));
|
||||||
|
|
||||||
is_valid_body_field(&content_slurs_removed)?;
|
is_valid_body_field(content_slurs_removed.as_deref())?;
|
||||||
|
|
||||||
let comment_id = data.comment_id;
|
let comment_id = data.comment_id;
|
||||||
let form = CommentUpdateForm::builder()
|
let form = CommentUpdateForm::builder()
|
||||||
.content(content_slurs_removed)
|
.content(content_slurs_removed.map(|s| s.into_owned()))
|
||||||
.language_id(data.language_id)
|
.language_id(data.language_id)
|
||||||
.updated(Some(Some(naive_now())))
|
.updated(Some(Some(naive_now())))
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -67,7 +67,7 @@ impl PerformCrud for CreateCommunity {
|
||||||
check_slurs_opt(&data.description, &slur_regex)?;
|
check_slurs_opt(&data.description, &slur_regex)?;
|
||||||
|
|
||||||
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
|
is_valid_actor_name(&data.name, local_site.actor_name_max_length as usize)?;
|
||||||
is_valid_body_field(&data.description)?;
|
is_valid_body_field(data.description.as_deref())?;
|
||||||
|
|
||||||
// Double check for duplicate community actor_ids
|
// Double check for duplicate community actor_ids
|
||||||
let community_actor_id = generate_local_apub_endpoint(
|
let community_actor_id = generate_local_apub_endpoint(
|
||||||
|
|
|
@ -38,7 +38,7 @@ impl PerformCrud for EditCommunity {
|
||||||
let slur_regex = local_site_to_slur_regex(&local_site);
|
let slur_regex = local_site_to_slur_regex(&local_site);
|
||||||
check_slurs_opt(&data.title, &slur_regex)?;
|
check_slurs_opt(&data.title, &slur_regex)?;
|
||||||
check_slurs_opt(&data.description, &slur_regex)?;
|
check_slurs_opt(&data.description, &slur_regex)?;
|
||||||
is_valid_body_field(&data.description)?;
|
is_valid_body_field(data.description.as_deref())?;
|
||||||
|
|
||||||
// Verify its a mod (only mods can edit it)
|
// Verify its a mod (only mods can edit it)
|
||||||
let community_id = data.community_id;
|
let community_id = data.community_id;
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl PerformCrud for CreatePost {
|
||||||
let url = data_url.map(clean_url_params).map(Into::into); // TODO no good way to handle a "clear"
|
let url = data_url.map(clean_url_params).map(Into::into); // TODO no good way to handle a "clear"
|
||||||
|
|
||||||
is_valid_post_title(&data.name)?;
|
is_valid_post_title(&data.name)?;
|
||||||
is_valid_body_field(&data.body)?;
|
is_valid_body_field(data.body.as_deref())?;
|
||||||
|
|
||||||
check_community_ban(local_user_view.person.id, data.community_id, context.pool()).await?;
|
check_community_ban(local_user_view.person.id, data.community_id, context.pool()).await?;
|
||||||
check_community_deleted_or_removed(data.community_id, context.pool()).await?;
|
check_community_deleted_or_removed(data.community_id, context.pool()).await?;
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl PerformCrud for EditPost {
|
||||||
is_valid_post_title(name)?;
|
is_valid_post_title(name)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_valid_body_field(&data.body)?;
|
is_valid_body_field(data.body.as_deref())?;
|
||||||
|
|
||||||
let post_id = data.post_id;
|
let post_id = data.post_id;
|
||||||
let orig_post = Post::read(context.pool(), post_id).await?;
|
let orig_post = Post::read(context.pool(), post_id).await?;
|
||||||
|
|
|
@ -39,16 +39,13 @@ impl PerformCrud for CreatePrivateMessage {
|
||||||
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
|
let local_user_view = local_user_view_from_jwt(&data.auth, context).await?;
|
||||||
let local_site = LocalSite::read(context.pool()).await?;
|
let local_site = LocalSite::read(context.pool()).await?;
|
||||||
|
|
||||||
let content_slurs_removed = remove_slurs(
|
let content_slurs_removed = remove_slurs(&data.content, &local_site_to_slur_regex(&local_site));
|
||||||
&data.content.clone(),
|
is_valid_body_field(Some(&content_slurs_removed))?;
|
||||||
&local_site_to_slur_regex(&local_site),
|
|
||||||
);
|
|
||||||
is_valid_body_field(&Some(content_slurs_removed.clone()))?;
|
|
||||||
|
|
||||||
check_person_block(local_user_view.person.id, data.recipient_id, context.pool()).await?;
|
check_person_block(local_user_view.person.id, data.recipient_id, context.pool()).await?;
|
||||||
|
|
||||||
let private_message_form = PrivateMessageInsertForm::builder()
|
let private_message_form = PrivateMessageInsertForm::builder()
|
||||||
.content(content_slurs_removed.clone())
|
.content(content_slurs_removed.clone().into_owned())
|
||||||
.creator_id(local_user_view.person.id)
|
.creator_id(local_user_view.person.id)
|
||||||
.recipient_id(data.recipient_id)
|
.recipient_id(data.recipient_id)
|
||||||
.build();
|
.build();
|
||||||
|
|
|
@ -41,14 +41,14 @@ impl PerformCrud for EditPrivateMessage {
|
||||||
|
|
||||||
// Doing the update
|
// Doing the update
|
||||||
let content_slurs_removed = remove_slurs(&data.content, &local_site_to_slur_regex(&local_site));
|
let content_slurs_removed = remove_slurs(&data.content, &local_site_to_slur_regex(&local_site));
|
||||||
is_valid_body_field(&Some(content_slurs_removed.clone()))?;
|
is_valid_body_field(Some(&content_slurs_removed))?;
|
||||||
|
|
||||||
let private_message_id = data.private_message_id;
|
let private_message_id = data.private_message_id;
|
||||||
PrivateMessage::update(
|
PrivateMessage::update(
|
||||||
context.pool(),
|
context.pool(),
|
||||||
private_message_id,
|
private_message_id,
|
||||||
&PrivateMessageUpdateForm::builder()
|
&PrivateMessageUpdateForm::builder()
|
||||||
.content(Some(content_slurs_removed))
|
.content(Some(content_slurs_removed.into_owned()))
|
||||||
.updated(Some(Some(naive_now())))
|
.updated(Some(Some(naive_now())))
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -66,7 +66,7 @@ impl PerformCrud for CreateSite {
|
||||||
site_description_length_check(desc)?;
|
site_description_length_check(desc)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_valid_body_field(&data.sidebar)?;
|
is_valid_body_field(data.sidebar.as_deref())?;
|
||||||
|
|
||||||
let application_question = diesel_option_overwrite(&data.application_question);
|
let application_question = diesel_option_overwrite(&data.application_question);
|
||||||
check_application_question(
|
check_application_question(
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl PerformCrud for EditSite {
|
||||||
site_description_length_check(desc)?;
|
site_description_length_check(desc)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_valid_body_field(&data.sidebar)?;
|
is_valid_body_field(data.sidebar.as_deref())?;
|
||||||
|
|
||||||
let application_question = diesel_option_overwrite(&data.application_question);
|
let application_question = diesel_option_overwrite(&data.application_question);
|
||||||
check_application_question(
|
check_application_question(
|
||||||
|
|
|
@ -167,7 +167,7 @@ impl Object for ApubComment {
|
||||||
let form = CommentInsertForm {
|
let form = CommentInsertForm {
|
||||||
creator_id: creator.id,
|
creator_id: creator.id,
|
||||||
post_id: post.id,
|
post_id: post.id,
|
||||||
content: content_slurs_removed,
|
content: content_slurs_removed.into_owned(),
|
||||||
removed: None,
|
removed: None,
|
||||||
published: note.published.map(|u| u.naive_local()),
|
published: note.published.map(|u| u.naive_local()),
|
||||||
updated: note.updated.map(|u| u.naive_local()),
|
updated: note.updated.map(|u| u.naive_local()),
|
||||||
|
|
|
@ -211,15 +211,14 @@ impl Object for ApubPost {
|
||||||
let local_site = LocalSite::read(context.pool()).await.ok();
|
let local_site = LocalSite::read(context.pool()).await.ok();
|
||||||
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
|
let slur_regex = &local_site_opt_to_slur_regex(&local_site);
|
||||||
|
|
||||||
let body_slurs_removed =
|
let body = read_from_string_or_source_opt(&page.content, &page.media_type, &page.source);
|
||||||
read_from_string_or_source_opt(&page.content, &page.media_type, &page.source)
|
let body_slurs_removed = body.as_deref().map(|s| remove_slurs(s, slur_regex));
|
||||||
.map(|s| remove_slurs(&s, slur_regex));
|
|
||||||
let language_id = LanguageTag::to_language_id_single(page.language, context.pool()).await?;
|
let language_id = LanguageTag::to_language_id_single(page.language, context.pool()).await?;
|
||||||
|
|
||||||
PostInsertForm {
|
PostInsertForm {
|
||||||
name,
|
name,
|
||||||
url: url.map(Into::into),
|
url: url.map(Into::into),
|
||||||
body: body_slurs_removed,
|
body: body_slurs_removed.map(|s| s.into_owned()),
|
||||||
creator_id: creator.id,
|
creator_id: creator.id,
|
||||||
community_id: community.id,
|
community_id: community.id,
|
||||||
removed: None,
|
removed: None,
|
||||||
|
|
|
@ -45,6 +45,7 @@ jsonwebtoken = "8.1.1"
|
||||||
lettre = "0.10.1"
|
lettre = "0.10.1"
|
||||||
comrak = { version = "0.14.0", default-features = false }
|
comrak = { version = "0.14.0", default-features = false }
|
||||||
totp-rs = { version = "4.2.0", features = ["gen_secret", "otpauth"] }
|
totp-rs = { version = "4.2.0", features = ["gen_secret", "otpauth"] }
|
||||||
|
enum-map = "2.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
reqwest = { workspace = true }
|
reqwest = { workspace = true }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::IpAddr;
|
use crate::IpAddr;
|
||||||
|
use enum_map::{enum_map, EnumMap};
|
||||||
use std::{collections::HashMap, time::Instant};
|
use std::{collections::HashMap, time::Instant};
|
||||||
use strum::IntoEnumIterator;
|
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -9,7 +9,7 @@ struct RateLimitBucket {
|
||||||
allowance: f64,
|
allowance: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Hash, Debug, EnumIter, Copy, Clone, AsRefStr)]
|
#[derive(Eq, PartialEq, Hash, Debug, enum_map::Enum, Copy, Clone, AsRefStr)]
|
||||||
pub(crate) enum RateLimitType {
|
pub(crate) enum RateLimitType {
|
||||||
Message,
|
Message,
|
||||||
Register,
|
Register,
|
||||||
|
@ -22,30 +22,10 @@ pub(crate) enum RateLimitType {
|
||||||
/// Rate limiting based on rate type and IP addr
|
/// Rate limiting based on rate type and IP addr
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct RateLimitStorage {
|
pub struct RateLimitStorage {
|
||||||
buckets: HashMap<RateLimitType, HashMap<IpAddr, RateLimitBucket>>,
|
buckets: HashMap<IpAddr, EnumMap<RateLimitType, RateLimitBucket>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RateLimitStorage {
|
impl RateLimitStorage {
|
||||||
fn insert_ip(&mut self, ip: &IpAddr) {
|
|
||||||
for rate_limit_type in RateLimitType::iter() {
|
|
||||||
if self.buckets.get(&rate_limit_type).is_none() {
|
|
||||||
self.buckets.insert(rate_limit_type, HashMap::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(bucket) = self.buckets.get_mut(&rate_limit_type) {
|
|
||||||
if bucket.get(ip).is_none() {
|
|
||||||
bucket.insert(
|
|
||||||
ip.clone(),
|
|
||||||
RateLimitBucket {
|
|
||||||
last_checked: Instant::now(),
|
|
||||||
allowance: -2f64,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rate limiting Algorithm described here: https://stackoverflow.com/a/668327/1655478
|
/// Rate limiting Algorithm described here: https://stackoverflow.com/a/668327/1655478
|
||||||
///
|
///
|
||||||
/// Returns true if the request passed the rate limit, false if it failed and should be rejected.
|
/// Returns true if the request passed the rate limit, false if it failed and should be rejected.
|
||||||
|
@ -57,40 +37,39 @@ impl RateLimitStorage {
|
||||||
rate: i32,
|
rate: i32,
|
||||||
per: i32,
|
per: i32,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
self.insert_ip(ip);
|
let current = Instant::now();
|
||||||
if let Some(bucket) = self.buckets.get_mut(&type_) {
|
let ip_buckets = self.buckets.entry(ip.clone()).or_insert(enum_map! {
|
||||||
if let Some(rate_limit) = bucket.get_mut(ip) {
|
_ => RateLimitBucket {
|
||||||
let current = Instant::now();
|
last_checked: current,
|
||||||
let time_passed = current.duration_since(rate_limit.last_checked).as_secs() as f64;
|
allowance: -2f64,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
#[allow(clippy::indexing_slicing)] // `EnumMap` has no `get` funciton
|
||||||
|
let rate_limit = &mut ip_buckets[type_];
|
||||||
|
let time_passed = current.duration_since(rate_limit.last_checked).as_secs() as f64;
|
||||||
|
|
||||||
// The initial value
|
// The initial value
|
||||||
if rate_limit.allowance == -2f64 {
|
if rate_limit.allowance == -2f64 {
|
||||||
rate_limit.allowance = f64::from(rate);
|
rate_limit.allowance = f64::from(rate);
|
||||||
};
|
};
|
||||||
|
|
||||||
rate_limit.last_checked = current;
|
rate_limit.last_checked = current;
|
||||||
rate_limit.allowance += time_passed * (f64::from(rate) / f64::from(per));
|
rate_limit.allowance += time_passed * (f64::from(rate) / f64::from(per));
|
||||||
if rate_limit.allowance > f64::from(rate) {
|
if rate_limit.allowance > f64::from(rate) {
|
||||||
rate_limit.allowance = f64::from(rate);
|
rate_limit.allowance = f64::from(rate);
|
||||||
}
|
}
|
||||||
|
|
||||||
if rate_limit.allowance < 1.0 {
|
if rate_limit.allowance < 1.0 {
|
||||||
debug!(
|
debug!(
|
||||||
"Rate limited type: {}, IP: {}, time_passed: {}, allowance: {}",
|
"Rate limited type: {}, IP: {}, time_passed: {}, allowance: {}",
|
||||||
type_.as_ref(),
|
type_.as_ref(),
|
||||||
ip,
|
ip,
|
||||||
time_passed,
|
time_passed,
|
||||||
rate_limit.allowance
|
rate_limit.allowance
|
||||||
);
|
);
|
||||||
false
|
false
|
||||||
} else {
|
|
||||||
rate_limit.allowance -= 1.0;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
rate_limit.allowance -= 1.0;
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
use crate::error::LemmyError;
|
use crate::error::LemmyError;
|
||||||
use regex::{Regex, RegexBuilder};
|
use regex::{Regex, RegexBuilder};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub fn remove_slurs(test: &str, slur_regex: &Option<Regex>) -> String {
|
pub fn remove_slurs<'a>(test: &'a str, slur_regex: &Option<Regex>) -> Cow<'a, str> {
|
||||||
if let Some(slur_regex) = slur_regex {
|
if let Some(slur_regex) = slur_regex {
|
||||||
slur_regex.replace_all(test, "*removed*").to_string()
|
slur_regex.replace_all(test, "*removed*")
|
||||||
} else {
|
} else {
|
||||||
test.to_string()
|
Cow::Borrowed(test)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,7 @@ pub fn is_valid_post_title(title: &str) -> LemmyResult<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This could be post bodies, comments, or any description field
|
/// This could be post bodies, comments, or any description field
|
||||||
pub fn is_valid_body_field(body: &Option<String>) -> LemmyResult<()> {
|
pub fn is_valid_body_field(body: Option<&str>) -> LemmyResult<()> {
|
||||||
if let Some(body) = body {
|
if let Some(body) = body {
|
||||||
let check = body.chars().count() <= BODY_MAX_LENGTH;
|
let check = body.chars().count() <= BODY_MAX_LENGTH;
|
||||||
if !check {
|
if !check {
|
||||||
|
|
Loading…
Reference in a new issue