various: inherit workspace lints in all crates; eliminate unwrap()
throughout Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Id8de9d65139ec4cf4cdeaee14c8c95b06a6a6964
This commit is contained in:
parent
1fe2c7998d
commit
b8ff35acea
12 changed files with 514 additions and 239 deletions
|
|
@ -20,6 +20,15 @@ pub struct PostgresBackend {
|
|||
pool: Pool,
|
||||
}
|
||||
|
||||
/// Escape special LIKE pattern characters (`%`, `_`, `\`) in user input
|
||||
/// to prevent wildcard injection.
|
||||
fn escape_like_pattern(input: &str) -> String {
|
||||
input
|
||||
.replace('\\', "\\\\")
|
||||
.replace('%', "\\%")
|
||||
.replace('_', "\\_")
|
||||
}
|
||||
|
||||
impl PostgresBackend {
|
||||
pub async fn new(config: &PostgresConfig) -> Result<Self> {
|
||||
let mut pool_config = PoolConfig::new();
|
||||
|
|
@ -335,7 +344,7 @@ fn build_search_inner(
|
|||
|
||||
params.push(Box::new(text.clone()));
|
||||
params.push(Box::new(prefix_query));
|
||||
params.push(Box::new(format!("%{}%", text)));
|
||||
params.push(Box::new(format!("%{}%", escape_like_pattern(&text))));
|
||||
params.push(Box::new(text.clone()));
|
||||
params.push(Box::new(text.clone()));
|
||||
params.push(Box::new(text.clone()));
|
||||
|
|
@ -377,7 +386,7 @@ fn build_search_inner(
|
|||
params.push(Box::new(term.clone()));
|
||||
params.push(Box::new(term.clone()));
|
||||
params.push(Box::new(term.clone()));
|
||||
params.push(Box::new(format!("%{}%", term)));
|
||||
params.push(Box::new(format!("%{}%", escape_like_pattern(&term))));
|
||||
Ok(format!(
|
||||
"(similarity(COALESCE(title, ''), ${idx_title}) > 0.3 OR \
|
||||
similarity(COALESCE(artist, ''), ${idx_artist}) > 0.3 OR \
|
||||
|
|
@ -1086,6 +1095,88 @@ impl StorageBackend for PostgresBackend {
|
|||
Ok(rows)
|
||||
}
|
||||
|
||||
async fn batch_update_media(
|
||||
&self,
|
||||
ids: &[MediaId],
|
||||
title: Option<&str>,
|
||||
artist: Option<&str>,
|
||||
album: Option<&str>,
|
||||
genre: Option<&str>,
|
||||
year: Option<i32>,
|
||||
description: Option<&str>,
|
||||
) -> Result<u64> {
|
||||
if ids.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Build SET clause dynamically from provided fields
|
||||
let mut set_parts = Vec::new();
|
||||
let mut params: Vec<Box<dyn tokio_postgres::types::ToSql + Sync + Send>> =
|
||||
Vec::new();
|
||||
let mut idx = 1;
|
||||
|
||||
if let Some(v) = title {
|
||||
set_parts.push(format!("title = ${idx}"));
|
||||
params.push(Box::new(v.to_string()));
|
||||
idx += 1;
|
||||
}
|
||||
if let Some(v) = artist {
|
||||
set_parts.push(format!("artist = ${idx}"));
|
||||
params.push(Box::new(v.to_string()));
|
||||
idx += 1;
|
||||
}
|
||||
if let Some(v) = album {
|
||||
set_parts.push(format!("album = ${idx}"));
|
||||
params.push(Box::new(v.to_string()));
|
||||
idx += 1;
|
||||
}
|
||||
if let Some(v) = genre {
|
||||
set_parts.push(format!("genre = ${idx}"));
|
||||
params.push(Box::new(v.to_string()));
|
||||
idx += 1;
|
||||
}
|
||||
if let Some(v) = year {
|
||||
set_parts.push(format!("year = ${idx}"));
|
||||
params.push(Box::new(v));
|
||||
idx += 1;
|
||||
}
|
||||
if let Some(v) = description {
|
||||
set_parts.push(format!("description = ${idx}"));
|
||||
params.push(Box::new(v.to_string()));
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
// Always update updated_at
|
||||
let now = chrono::Utc::now();
|
||||
set_parts.push(format!("updated_at = ${idx}"));
|
||||
params.push(Box::new(now));
|
||||
idx += 1;
|
||||
|
||||
if set_parts.len() == 1 {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let uuids: Vec<Uuid> = ids.iter().map(|id| id.0).collect();
|
||||
let sql = format!(
|
||||
"UPDATE media_items SET {} WHERE id = ANY(${idx})",
|
||||
set_parts.join(", ")
|
||||
);
|
||||
params.push(Box::new(uuids));
|
||||
|
||||
let client = self
|
||||
.pool
|
||||
.get()
|
||||
.await
|
||||
.map_err(|e| PinakesError::Database(format!("pool error: {e}")))?;
|
||||
|
||||
let param_refs: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = params
|
||||
.iter()
|
||||
.map(|p| p.as_ref() as &(dyn tokio_postgres::types::ToSql + Sync))
|
||||
.collect();
|
||||
let rows = client.execute(&sql, ¶m_refs).await?;
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
// Tags
|
||||
async fn create_tag(
|
||||
&self,
|
||||
|
|
@ -4186,11 +4277,9 @@ impl StorageBackend for PostgresBackend {
|
|||
)
|
||||
.await?;
|
||||
|
||||
if row.is_none() {
|
||||
let Some(row) = row else {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let row = row.unwrap();
|
||||
};
|
||||
|
||||
// Get authors
|
||||
let author_rows = client
|
||||
|
|
@ -4552,9 +4641,9 @@ impl StorageBackend for PostgresBackend {
|
|||
let rows = if let (Some(i), Some(a), Some(s), Some(p), Some(l)) =
|
||||
(isbn, author, series, publisher, language)
|
||||
{
|
||||
let author_pattern = format!("%{}%", a);
|
||||
let series_pattern = format!("%{}%", s);
|
||||
let publisher_pattern = format!("%{}%", p);
|
||||
let author_pattern = format!("%{}%", escape_like_pattern(a));
|
||||
let series_pattern = format!("%{}%", escape_like_pattern(s));
|
||||
let publisher_pattern = format!("%{}%", escape_like_pattern(p));
|
||||
client
|
||||
.query(
|
||||
"SELECT DISTINCT m.id, m.path, m.file_name, m.media_type, \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue