fix(persistence): extract row_to_summary helper, escape LIKE wildcards
This commit is contained in:
@@ -65,26 +65,7 @@ impl SongRepositoryPort for SqliteSongRepository {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| RepositoryError::Internal(e.to_string()))?;
|
.map_err(|e| RepositoryError::Internal(e.to_string()))?;
|
||||||
|
|
||||||
rows.into_iter()
|
rows.into_iter().map(row_to_summary).collect()
|
||||||
.map(|row| {
|
|
||||||
let id = Uuid::parse_str(&row.id)
|
|
||||||
.map_err(|e| RepositoryError::Internal(e.to_string()))?;
|
|
||||||
let preview_chords: Vec<String> = serde_json::from_str(&row.preview_chords)
|
|
||||||
.map_err(|e| RepositoryError::Internal(e.to_string()))?;
|
|
||||||
Ok(SongSummary {
|
|
||||||
id,
|
|
||||||
meta: SongMeta {
|
|
||||||
title: row.title,
|
|
||||||
artist: row.artist,
|
|
||||||
original_key: row.original_key,
|
|
||||||
capo: None,
|
|
||||||
tuning: None,
|
|
||||||
tempo: None,
|
|
||||||
},
|
|
||||||
preview_chords,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get(&self, id: Uuid) -> Result<Option<Song>, RepositoryError> {
|
async fn get(&self, id: Uuid) -> Result<Option<Song>, RepositoryError> {
|
||||||
@@ -126,10 +107,11 @@ impl SongRepositoryPort for SqliteSongRepository {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl SongSearchPort for SqliteSongRepository {
|
impl SongSearchPort for SqliteSongRepository {
|
||||||
async fn search(&self, query: &str) -> Result<Vec<SongSummary>, RepositoryError> {
|
async fn search(&self, query: &str) -> Result<Vec<SongSummary>, RepositoryError> {
|
||||||
let pattern = format!("%{}%", query);
|
let escaped = query.replace('\\', "\\\\").replace('%', "\\%").replace('_', "\\_");
|
||||||
|
let pattern = format!("%{}%", escaped);
|
||||||
let rows = sqlx::query_as::<_, SongRow>(
|
let rows = sqlx::query_as::<_, SongRow>(
|
||||||
"SELECT id, title, artist, original_key, preview_chords, body FROM songs \
|
"SELECT id, title, artist, original_key, preview_chords, body FROM songs \
|
||||||
WHERE title LIKE ? OR artist LIKE ? ORDER BY created_at DESC"
|
WHERE (title LIKE ? ESCAPE '\\' OR artist LIKE ? ESCAPE '\\') ORDER BY created_at DESC"
|
||||||
)
|
)
|
||||||
.bind(&pattern)
|
.bind(&pattern)
|
||||||
.bind(&pattern)
|
.bind(&pattern)
|
||||||
@@ -137,8 +119,11 @@ impl SongSearchPort for SqliteSongRepository {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| RepositoryError::Internal(e.to_string()))?;
|
.map_err(|e| RepositoryError::Internal(e.to_string()))?;
|
||||||
|
|
||||||
rows.into_iter()
|
rows.into_iter().map(row_to_summary).collect()
|
||||||
.map(|row| {
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn row_to_summary(row: SongRow) -> Result<SongSummary, RepositoryError> {
|
||||||
let id = Uuid::parse_str(&row.id)
|
let id = Uuid::parse_str(&row.id)
|
||||||
.map_err(|e| RepositoryError::Internal(e.to_string()))?;
|
.map_err(|e| RepositoryError::Internal(e.to_string()))?;
|
||||||
let preview_chords: Vec<String> = serde_json::from_str(&row.preview_chords)
|
let preview_chords: Vec<String> = serde_json::from_str(&row.preview_chords)
|
||||||
@@ -155,9 +140,6 @@ impl SongSearchPort for SqliteSongRepository {
|
|||||||
},
|
},
|
||||||
preview_chords,
|
preview_chords,
|
||||||
})
|
})
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SqliteRepositoryFactory;
|
pub struct SqliteRepositoryFactory;
|
||||||
|
|||||||
Reference in New Issue
Block a user