use axum::extract::{Path, State}; use axum::http::StatusCode; use axum::Json; use breadarr_shared::dto::ReviewQueueEntry; use crate::api::AppState; use crate::scheduler; pub async fn list( State(state): State, ) -> Result>, (StatusCode, String)> { let conn = state.conn.lock().await; let mut stmt = conn .prepare( "SELECT rq.id, rq.raw_release_title, m.title, rq.confidence, rq.status, rq.created_at FROM review_queue rq LEFT JOIN media_item m ON m.id = rq.candidate_media_item_id WHERE rq.status = 'pending' ORDER BY rq.created_at DESC", ) .map_err(internal)?; let rows = stmt .query_map([], |row| { Ok(ReviewQueueEntry { id: row.get(0)?, raw_release_title: row.get(1)?, candidate_media_title: row.get(2)?, confidence: row.get(3)?, status: row.get(4)?, created_at: row.get(5)?, }) }) .map_err(internal)? .collect::>>() .map_err(internal)?; Ok(Json(rows)) } pub async fn approve( State(state): State, Path(id): Path, ) -> Result { let Some(qbit) = &state.qbit else { return Err(( StatusCode::PRECONDITION_FAILED, "qbit.base_url is not configured".into(), )); }; let prep = { let conn = state.conn.lock().await; scheduler::prepare_review_approval(&conn, id).map_err(internal)? }; let prepared = match prep { scheduler::ApprovalPrep::Ready(p) => p, scheduler::ApprovalPrep::NotPending => { return Err((StatusCode::CONFLICT, "review item is not pending".into())) } scheduler::ApprovalPrep::MissingGrabData => { return Err(( StatusCode::UNPROCESSABLE_ENTITY, "review item has no stored link/source to grab".into(), )) } scheduler::ApprovalPrep::CouldNotResolveEpisode => { return Err(( StatusCode::UNPROCESSABLE_ENTITY, "could not resolve which episode this release is".into(), )) } scheduler::ApprovalPrep::NotMonitoredOrAlreadyHave => { return Err(( StatusCode::CONFLICT, "episode is not monitored or is already downloaded".into(), )) } }; let torrent_hash = scheduler::grab_prepared_approval(qbit, &state.qbit_category, &prepared) .await .map_err(internal)?; { let conn = state.conn.lock().await; scheduler::finalize_review_approval( &conn, id, &prepared, &state.qbit_category, torrent_hash.as_deref(), ) .map_err(internal)?; } Ok(StatusCode::NO_CONTENT) } pub async fn reject( State(state): State, Path(id): Path, ) -> Result { let conn = state.conn.lock().await; scheduler::reject_review(&conn, id).map_err(internal)?; Ok(StatusCode::NO_CONTENT) } fn internal(e: E) -> (StatusCode, String) { (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()) }