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:
raf 2026-03-07 16:55:43 +03:00
commit b8ff35acea
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
12 changed files with 514 additions and 239 deletions

View file

@ -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, &param_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, \