refactor: extract slug utilities into api/slug.rs
This commit is contained in:
parent
4d7d7ec383
commit
4c88d5e2bb
81
src/api/slug.rs
Normal file
81
src/api/slug.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#![allow(clippy::unused_unit, deprecated, unused_imports)]
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub fn slugify(title: &str) -> String {
|
||||||
|
let slug: String = title
|
||||||
|
.to_lowercase()
|
||||||
|
.chars()
|
||||||
|
.map(|c| {
|
||||||
|
if c.is_ascii_alphanumeric() || c == '-' || c == '_' {
|
||||||
|
c
|
||||||
|
} else {
|
||||||
|
'-'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let parts: Vec<&str> = slug.split('-').filter(|s| !s.is_empty()).collect();
|
||||||
|
let slug = parts.join("-");
|
||||||
|
|
||||||
|
if slug.is_empty() {
|
||||||
|
return format!("{}", chrono::Utc::now().timestamp());
|
||||||
|
}
|
||||||
|
|
||||||
|
slug.chars().take(100).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub fn is_valid_slug(slug: &str) -> bool {
|
||||||
|
if slug.is_empty() || slug.len() > 200 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
slug.chars()
|
||||||
|
.all(|c| c.is_alphanumeric() || c == '-' || c == '_')
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub async fn ensure_unique_slug(
|
||||||
|
client: &tokio_postgres::Client,
|
||||||
|
base: &str,
|
||||||
|
exclude_id: Option<i32>,
|
||||||
|
) -> Result<String, ServerFnError> {
|
||||||
|
use crate::api::utils::query_error;
|
||||||
|
|
||||||
|
let mut candidate = base.to_string();
|
||||||
|
let mut suffix = 2;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let exists = if let Some(exclude) = exclude_id {
|
||||||
|
client
|
||||||
|
.query_opt(
|
||||||
|
"SELECT 1 FROM posts WHERE slug = $1 AND deleted_at IS NULL AND id != $2",
|
||||||
|
&[&candidate, &exclude],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(query_error)?
|
||||||
|
.is_some()
|
||||||
|
} else {
|
||||||
|
client
|
||||||
|
.query_opt(
|
||||||
|
"SELECT 1 FROM posts WHERE slug = $1 AND deleted_at IS NULL",
|
||||||
|
&[&candidate],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(query_error)?
|
||||||
|
.is_some()
|
||||||
|
};
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return Ok(candidate);
|
||||||
|
}
|
||||||
|
|
||||||
|
candidate = format!("{}-{}", base, suffix);
|
||||||
|
suffix += 1;
|
||||||
|
|
||||||
|
if candidate.len() > 200 {
|
||||||
|
return Err(ServerFnError::new("无法生成唯一 slug"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user