Added merge
This commit is contained in:
@@ -86,20 +86,27 @@ pub async fn approve_queue_item(State(state): State<S>, Path(id): Path<Uuid>) ->
|
||||
let album_dir = sanitize_filename(album);
|
||||
let dest = state.config.storage_dir.join(&artist_dir).join(&album_dir).join(&filename);
|
||||
|
||||
let storage_path = if dest.exists() && !source.exists() {
|
||||
use crate::ingest::mover::MoveOutcome;
|
||||
let (storage_path, was_merged) = if dest.exists() && !source.exists() {
|
||||
// File already moved (e.g. auto-approved earlier but DB not finalized)
|
||||
dest.to_string_lossy().to_string()
|
||||
(dest.to_string_lossy().to_string(), false)
|
||||
} else {
|
||||
match crate::ingest::mover::move_to_storage(
|
||||
&state.config.storage_dir, artist, album, &filename, source,
|
||||
).await {
|
||||
Ok(p) => p.to_string_lossy().to_string(),
|
||||
Ok(MoveOutcome::Moved(p)) => (p.to_string_lossy().to_string(), false),
|
||||
Ok(MoveOutcome::Merged(p)) => (p.to_string_lossy().to_string(), true),
|
||||
Err(e) => return error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
}
|
||||
};
|
||||
|
||||
match db::approve_and_finalize(&state.pool, id, &storage_path).await {
|
||||
Ok(track_id) => (StatusCode::OK, Json(serde_json::json!({"track_id": track_id}))).into_response(),
|
||||
Ok(track_id) => {
|
||||
if was_merged {
|
||||
let _ = db::update_pending_status(&state.pool, id, "merged", None).await;
|
||||
}
|
||||
(StatusCode::OK, Json(serde_json::json!({"track_id": track_id}))).into_response()
|
||||
}
|
||||
Err(e) => error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
}
|
||||
}
|
||||
@@ -189,19 +196,26 @@ pub async fn batch_approve(State(state): State<S>, Json(body): Json<BatchIds>) -
|
||||
let album_dir = sanitize_filename(album);
|
||||
let dest = state.config.storage_dir.join(&artist_dir).join(&album_dir).join(&filename);
|
||||
|
||||
let rel_path = if dest.exists() && !source.exists() {
|
||||
dest.to_string_lossy().to_string()
|
||||
use crate::ingest::mover::MoveOutcome;
|
||||
let (rel_path, was_merged) = if dest.exists() && !source.exists() {
|
||||
(dest.to_string_lossy().to_string(), false)
|
||||
} else {
|
||||
match crate::ingest::mover::move_to_storage(
|
||||
&state.config.storage_dir, artist, album, &filename, source,
|
||||
).await {
|
||||
Ok(p) => p.to_string_lossy().to_string(),
|
||||
Ok(MoveOutcome::Moved(p)) => (p.to_string_lossy().to_string(), false),
|
||||
Ok(MoveOutcome::Merged(p)) => (p.to_string_lossy().to_string(), true),
|
||||
Err(e) => { errors.push(format!("{}: {}", id, e)); continue; }
|
||||
}
|
||||
};
|
||||
|
||||
match db::approve_and_finalize(&state.pool, *id, &rel_path).await {
|
||||
Ok(_) => ok += 1,
|
||||
Ok(_) => {
|
||||
if was_merged {
|
||||
let _ = db::update_pending_status(&state.pool, *id, "merged", None).await;
|
||||
}
|
||||
ok += 1;
|
||||
}
|
||||
Err(e) => errors.push(format!("{}: {}", id, e)),
|
||||
}
|
||||
}
|
||||
@@ -312,6 +326,110 @@ pub async fn update_album(
|
||||
}
|
||||
}
|
||||
|
||||
// --- Merges ---
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateMergeBody {
|
||||
pub artist_ids: Vec<i64>,
|
||||
}
|
||||
|
||||
pub async fn create_merge(State(state): State<S>, Json(body): Json<CreateMergeBody>) -> impl IntoResponse {
|
||||
if body.artist_ids.len() < 2 {
|
||||
return error_response(StatusCode::BAD_REQUEST, "need at least 2 artists to merge");
|
||||
}
|
||||
match db::insert_artist_merge(&state.pool, &body.artist_ids).await {
|
||||
Ok(id) => (StatusCode::OK, Json(serde_json::json!({"id": id}))).into_response(),
|
||||
Err(e) => error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_merges(State(state): State<S>) -> impl IntoResponse {
|
||||
match db::list_artist_merges(&state.pool).await {
|
||||
Ok(items) => (StatusCode::OK, Json(serde_json::to_value(items).unwrap())).into_response(),
|
||||
Err(e) => error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_merge(State(state): State<S>, Path(id): Path<Uuid>) -> impl IntoResponse {
|
||||
let merge = match db::get_artist_merge(&state.pool, id).await {
|
||||
Ok(Some(m)) => m,
|
||||
Ok(None) => return error_response(StatusCode::NOT_FOUND, "not found"),
|
||||
Err(e) => return error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
};
|
||||
|
||||
let source_ids: Vec<i64> = serde_json::from_str(&merge.source_artist_ids).unwrap_or_default();
|
||||
let artists = match db::get_artists_full_data(&state.pool, &source_ids).await {
|
||||
Ok(a) => a,
|
||||
Err(e) => return error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
};
|
||||
|
||||
let proposal: Option<serde_json::Value> = merge.proposal.as_deref()
|
||||
.and_then(|p| serde_json::from_str(p).ok());
|
||||
|
||||
(StatusCode::OK, Json(serde_json::json!({
|
||||
"merge": {
|
||||
"id": merge.id,
|
||||
"status": merge.status,
|
||||
"source_artist_ids": source_ids,
|
||||
"llm_notes": merge.llm_notes,
|
||||
"error_message": merge.error_message,
|
||||
"created_at": merge.created_at,
|
||||
"updated_at": merge.updated_at,
|
||||
},
|
||||
"artists": artists,
|
||||
"proposal": proposal,
|
||||
}))).into_response()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateMergeBody {
|
||||
pub proposal: serde_json::Value,
|
||||
}
|
||||
|
||||
pub async fn update_merge(
|
||||
State(state): State<S>,
|
||||
Path(id): Path<Uuid>,
|
||||
Json(body): Json<UpdateMergeBody>,
|
||||
) -> impl IntoResponse {
|
||||
let notes = body.proposal.get("notes")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("")
|
||||
.to_owned();
|
||||
let proposal_json = match serde_json::to_string(&body.proposal) {
|
||||
Ok(s) => s,
|
||||
Err(e) => return error_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
||||
};
|
||||
match db::update_merge_proposal(&state.pool, id, &proposal_json, ¬es).await {
|
||||
Ok(()) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(e) => error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn approve_merge(State(state): State<S>, Path(id): Path<Uuid>) -> impl IntoResponse {
|
||||
match crate::merge::execute_merge(&state, id).await {
|
||||
Ok(()) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
let _ = db::update_merge_status(&state.pool, id, "error", Some(&msg)).await;
|
||||
error_response(StatusCode::INTERNAL_SERVER_ERROR, &msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn reject_merge(State(state): State<S>, Path(id): Path<Uuid>) -> impl IntoResponse {
|
||||
match db::update_merge_status(&state.pool, id, "rejected", None).await {
|
||||
Ok(()) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(e) => error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn retry_merge(State(state): State<S>, Path(id): Path<Uuid>) -> impl IntoResponse {
|
||||
match db::update_merge_status(&state.pool, id, "pending", None).await {
|
||||
Ok(()) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(e) => error_response(StatusCode::INTERNAL_SERVER_ERROR, &e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
fn error_response(status: StatusCode, message: &str) -> axum::response::Response {
|
||||
|
||||
Reference in New Issue
Block a user