use axum::extract::{Path, Query, State};
use axum::Json;
use serde::Deserialize;
use serde_json::{json, Value};
use uuid::Uuid;
use crate::domain::candidates::ExtractionCandidate;
use crate::domain::CandidateStatus;
use crate::error::{AppError, AppResult};
use crate::services::candidates as svc;
use crate::state::AppState;
#[derive(Debug, Deserialize)]
pub struct ListQuery {
pub status: Option<String>,
pub limit: Option<i64>,
}
pub async fn list(
State(state): State<AppState>,
Query(q): Query<ListQuery>,
) -> AppResult<Json<Vec<ExtractionCandidate>>> {
let status = parse_status(q.status.as_deref())?;
let candidates = svc::list_candidates(&state.pool, status, q.limit).await?;
Ok(Json(candidates))
}
pub async fn list_for_document(
State(state): State<AppState>,
Path(document_id): Path<Uuid>,
Query(q): Query<ListQuery>,
) -> AppResult<Json<Vec<ExtractionCandidate>>> {
let status = parse_status(q.status.as_deref())?;
let candidates =
svc::list_candidates_for_document(&state.pool, document_id, status).await?;
Ok(Json(candidates))
}
pub async fn accept(
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> AppResult<Json<Value>> {
let outcome = svc::accept_candidate(&state.pool, id).await?;
Ok(Json(serde_json::to_value(outcome)?))
}
pub async fn reject(
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> AppResult<Json<Value>> {
let candidate = svc::reject_candidate(&state.pool, id).await?;
Ok(Json(json!({
"candidate_id": candidate.id,
"status": candidate.status,
})))
}
fn parse_status(raw: Option<&str>) -> AppResult<Option<CandidateStatus>> {
match raw {
None => Ok(None),
Some(s) => serde_json::from_value::<CandidateStatus>(Value::String(s.to_string()))
.map(Some)
.map_err(|_| AppError::Validation(format!("invalid candidate status `{s}`"))),
}
}