feat: add sort params to list/search, capo-aware GET /songs/{id}?apply_capo=true

This commit is contained in:
2026-04-08 04:05:57 +02:00
parent 41b9cb3d4c
commit 2558f19960
5 changed files with 92 additions and 32 deletions

View File

@@ -3,7 +3,7 @@ use axum::{
http::StatusCode,
Json,
};
use domain::RepositoryError;
use domain::{ChordTransposer, RepositoryError, SortField, SortOrder};
use serde::Deserialize;
use std::sync::Arc;
use uuid::Uuid;
@@ -11,6 +11,8 @@ use uuid::Uuid;
#[derive(Deserialize)]
pub struct ListQuery {
pub q: Option<String>,
pub sort: Option<String>,
pub order: Option<String>,
}
use crate::routes::tabs::{AppState, ErrorResponse, ParseRequest, resolve_html};
@@ -38,10 +40,19 @@ pub async fn list_songs(
State(state): State<Arc<AppState>>,
Query(params): Query<ListQuery>,
) -> Result<Json<Vec<domain::SongSummary>>, (StatusCode, Json<ErrorResponse>)> {
let sort = match params.sort.as_deref() {
Some("title") => SortField::Title,
Some("artist") => SortField::Artist,
_ => SortField::Date,
};
let order = match params.order.as_deref() {
Some("asc") => SortOrder::Asc,
_ => SortOrder::Desc,
};
let result = if let Some(q) = params.q.filter(|s| !s.is_empty()) {
state.search.search(&q).await
state.search.search(&q, sort, order).await
} else {
state.songs.list().await
state.songs.list(sort, order).await
};
result
.map(Json)
@@ -80,19 +91,37 @@ pub async fn update_song(
})
}
#[derive(Deserialize)]
pub struct GetSongQuery {
pub apply_capo: Option<bool>,
}
pub async fn get_song(
State(state): State<Arc<AppState>>,
Path(id): Path<String>,
Query(params): Query<GetSongQuery>,
) -> Result<Json<domain::Song>, (StatusCode, Json<ErrorResponse>)> {
let uuid = Uuid::parse_str(&id).map_err(|_| {
(StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Invalid ID".into() }))
})?;
match state.songs.get(uuid).await {
Ok(Some(song)) => Ok(Json(song)),
Ok(None) => Err((StatusCode::NOT_FOUND, Json(ErrorResponse { error: "Not found".into() }))),
Err(e) => Err((StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: e.to_string() }))),
}
let song = match state.songs.get(uuid).await {
Ok(Some(s)) => s,
Ok(None) => return Err((StatusCode::NOT_FOUND, Json(ErrorResponse { error: "Not found".into() }))),
Err(e) => return Err((StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: e.to_string() }))),
};
let song = if params.apply_capo.unwrap_or(false) {
if let Some(capo) = song.meta.capo {
ChordTransposer.transpose_song(&song, capo as i8)
} else {
song
}
} else {
song
};
Ok(Json(song))
}
pub async fn delete_song(