feat: API endpoints for article history listing and provenance
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>master
parent
b9003cde54
commit
55fe828e58
@ -0,0 +1,78 @@
|
||||
//! Handlers for article history and provenance endpoints.
|
||||
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::response::IntoResponse;
|
||||
use axum::Json;
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::app_state::AppState;
|
||||
use crate::db;
|
||||
use crate::errors::AppError;
|
||||
use crate::middleware::auth::AuthUser;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct HistoryQuery {
|
||||
pub limit: Option<i64>,
|
||||
pub offset: Option<i64>,
|
||||
pub status: Option<String>,
|
||||
pub source_type: Option<String>,
|
||||
}
|
||||
|
||||
/// GET /api/v1/article-history
|
||||
///
|
||||
/// Returns paginated article history with optional filters.
|
||||
pub async fn list_history(
|
||||
auth_user: AuthUser,
|
||||
State(state): State<AppState>,
|
||||
Query(params): Query<HistoryQuery>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let limit = params.limit.unwrap_or(50).clamp(1, 200);
|
||||
let offset = params.offset.unwrap_or(0).max(0);
|
||||
|
||||
let items = db::article_history::list_history(
|
||||
&state.pool,
|
||||
auth_user.id,
|
||||
limit,
|
||||
offset,
|
||||
params.status.as_deref(),
|
||||
params.source_type.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let total = db::article_history::count_history(
|
||||
&state.pool,
|
||||
auth_user.id,
|
||||
params.status.as_deref(),
|
||||
params.source_type.as_deref(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(Json(serde_json::json!({
|
||||
"items": items,
|
||||
"total": total
|
||||
})))
|
||||
}
|
||||
|
||||
/// GET /api/v1/syntheses/:id/provenance
|
||||
///
|
||||
/// Returns all article history entries for the generation run
|
||||
/// that produced the given synthesis.
|
||||
pub async fn get_provenance(
|
||||
auth_user: AuthUser,
|
||||
State(state): State<AppState>,
|
||||
Path(synthesis_id): Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
// Verify the synthesis belongs to this user and get its job_id
|
||||
let synthesis = db::syntheses::get_by_id_for_user(&state.pool, synthesis_id, auth_user.id)
|
||||
.await?
|
||||
.ok_or_else(|| AppError::NotFound("Synthesis not found".into()))?;
|
||||
|
||||
let job_id = synthesis.job_id.ok_or_else(|| {
|
||||
AppError::NotFound("No tracing data available for this synthesis".into())
|
||||
})?;
|
||||
|
||||
let items = db::article_history::list_by_job_id(&state.pool, auth_user.id, job_id).await?;
|
||||
|
||||
Ok(Json(items))
|
||||
}
|
||||
Loading…
Reference in New Issue