refactor: DDD/CQRS architecture, unified crate layout
- crates: common→application, api→presentation, infrastructure/*→adapters/* - new crates: api-types, infra-wiring - domain: errors/, models/, value_objects/, ports/, services/ - application: CQRS use cases (songs/, tabs/) w/ commands, queries, deps - unified DomainError replaces RepositoryError - workspace deps, unused dep cleanup - fix: parse plain-text chord lines (UG drops spans mid-song) - tests extracted to separate modules (tests/ dirs)
This commit is contained in:
34
crates/adapters/sqlite/src/search.rs
Normal file
34
crates/adapters/sqlite/src/search.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use async_trait::async_trait;
|
||||
use domain::{DomainError, SongSearchPort, SongSummary, SortField, SortOrder};
|
||||
|
||||
use crate::repository::SqliteSongRepository;
|
||||
use crate::row::{SongRow, row_to_summary, sort_clause};
|
||||
|
||||
#[async_trait]
|
||||
impl SongSearchPort for SqliteSongRepository {
|
||||
async fn search(
|
||||
&self,
|
||||
query: &str,
|
||||
sort: SortField,
|
||||
order: SortOrder,
|
||||
) -> Result<Vec<SongSummary>, DomainError> {
|
||||
let escaped = query
|
||||
.replace('\\', "\\\\")
|
||||
.replace('%', "\\%")
|
||||
.replace('_', "\\_");
|
||||
let pattern = format!("%{}%", escaped);
|
||||
let sql = format!(
|
||||
"SELECT id, title, artist, original_key, preview_chords, body FROM songs \
|
||||
WHERE (title LIKE ? ESCAPE '\\' OR artist LIKE ? ESCAPE '\\') {}",
|
||||
sort_clause(sort, order)
|
||||
);
|
||||
let rows = sqlx::query_as::<_, SongRow>(&sql)
|
||||
.bind(&pattern)
|
||||
.bind(&pattern)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?;
|
||||
|
||||
rows.into_iter().map(row_to_summary).collect()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user