feat: add update_meta to SongRepositoryPort and PATCH /songs/{id}

This commit is contained in:
2026-04-08 03:39:29 +02:00
parent 6a3cdbe4c3
commit 1936ced395
4 changed files with 98 additions and 5 deletions

View File

@@ -48,12 +48,36 @@ pub async fn list_songs(
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: e.to_string() })))
}
#[derive(serde::Deserialize)]
pub struct UpdateSongRequest {
pub title: Option<String>,
pub artist: Option<String>,
pub original_key: Option<String>,
}
pub async fn update_song(
State(_state): State<Arc<AppState>>,
Path(_id): Path<String>,
Json(_body): Json<serde_json::Value>,
) -> StatusCode {
StatusCode::NOT_IMPLEMENTED
State(state): State<Arc<AppState>>,
Path(id): Path<String>,
Json(body): Json<UpdateSongRequest>,
) -> Result<Json<domain::SongSummary>, (StatusCode, Json<ErrorResponse>)> {
let uuid = Uuid::parse_str(&id).map_err(|_| {
(StatusCode::BAD_REQUEST, Json(ErrorResponse { error: "Invalid ID".into() }))
})?;
state.songs
.update_meta(
uuid,
body.title.as_deref(),
body.artist.as_deref(),
body.original_key.as_deref(),
)
.await
.map(Json)
.map_err(|e| match e {
domain::RepositoryError::NotFound =>
(StatusCode::NOT_FOUND, Json(ErrorResponse { error: "Not found".into() })),
e => (StatusCode::INTERNAL_SERVER_ERROR, Json(ErrorResponse { error: e.to_string() })),
})
}
pub async fn get_song(

View File

@@ -25,6 +25,16 @@ impl SongService {
pub async fn delete(&self, id: Uuid) -> Result<(), RepositoryError> {
self.repo.delete(id).await
}
pub async fn update_meta(
&self,
id: Uuid,
title: Option<&str>,
artist: Option<&str>,
original_key: Option<&str>,
) -> Result<domain::SongSummary, domain::RepositoryError> {
self.repo.update_meta(id, title, artist, original_key).await
}
}
pub struct SongSearchService {

View File

@@ -53,6 +53,13 @@ pub trait SongRepositoryPort: Send + Sync {
async fn list(&self) -> Result<Vec<SongSummary>, RepositoryError>;
async fn get(&self, id: Uuid) -> Result<Option<Song>, RepositoryError>;
async fn delete(&self, id: Uuid) -> Result<(), RepositoryError>;
async fn update_meta(
&self,
id: Uuid,
title: Option<&str>,
artist: Option<&str>,
original_key: Option<&str>,
) -> Result<SongSummary, RepositoryError>;
}
#[async_trait]

View File

@@ -102,6 +102,58 @@ impl SongRepositoryPort for SqliteSongRepository {
Ok(())
}
}
async fn update_meta(
&self,
id: Uuid,
title: Option<&str>,
artist: Option<&str>,
original_key: Option<&str>,
) -> Result<SongSummary, RepositoryError> {
let id_str = id.to_string();
let row = sqlx::query_as::<_, SongRow>(
"SELECT id, title, artist, original_key, preview_chords, body FROM songs WHERE id = ?"
)
.bind(&id_str)
.fetch_optional(&self.pool)
.await
.map_err(|e| RepositoryError::Internal(e.to_string()))?
.ok_or(RepositoryError::NotFound)?;
let mut song: Song = serde_json::from_str(&row.body)
.map_err(|e| RepositoryError::Internal(e.to_string()))?;
if let Some(t) = title { song.meta.title = t.to_string(); }
if let Some(a) = artist { song.meta.artist = a.to_string(); }
if let Some(k) = original_key { song.meta.original_key = Some(k.to_string()); }
let new_body = serde_json::to_string(&song)
.map_err(|e| RepositoryError::Internal(e.to_string()))?;
let new_title = title.unwrap_or(&row.title);
let new_artist = artist.unwrap_or(&row.artist);
let new_key: Option<&str> = original_key.or(row.original_key.as_deref());
sqlx::query(
"UPDATE songs SET title = ?, artist = ?, original_key = ?, body = ? WHERE id = ?"
)
.bind(new_title)
.bind(new_artist)
.bind(new_key)
.bind(&new_body)
.bind(&id_str)
.execute(&self.pool)
.await
.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: song.meta,
preview_chords,
})
}
}
#[async_trait]