feat: add SongSearchService and GET /songs?q= search endpoint
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
use async_trait::async_trait;
|
||||
use domain::{
|
||||
RepositoryError, Song, SongMeta, SongRepositoryPort, SongSummary, StoredSong,
|
||||
RepositoryError, Song, SongMeta, SongRepositoryPort, SongSearchPort, SongSummary, StoredSong,
|
||||
song_preview_chords,
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SqliteSongRepository {
|
||||
pool: SqlitePool,
|
||||
}
|
||||
@@ -122,6 +123,43 @@ impl SongRepositoryPort for SqliteSongRepository {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SongSearchPort for SqliteSongRepository {
|
||||
async fn search(&self, query: &str) -> Result<Vec<SongSummary>, RepositoryError> {
|
||||
let pattern = format!("%{}%", query);
|
||||
let rows = sqlx::query_as::<_, SongRow>(
|
||||
"SELECT id, title, artist, original_key, preview_chords, body FROM songs \
|
||||
WHERE title LIKE ? OR artist LIKE ? ORDER BY created_at DESC"
|
||||
)
|
||||
.bind(&pattern)
|
||||
.bind(&pattern)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| RepositoryError::Internal(e.to_string()))?;
|
||||
|
||||
rows.into_iter()
|
||||
.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()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SqliteRepositoryFactory;
|
||||
|
||||
impl SqliteRepositoryFactory {
|
||||
|
||||
Reference in New Issue
Block a user