feat: API endpoints for article history listing and provenance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
master
oabrivard 3 months ago
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))
}

@ -1,3 +1,4 @@
pub mod article_history;
pub mod admin;
pub mod api_keys;
pub mod auth;

@ -54,6 +54,9 @@ pub fn build_router(state: AppState, config: &AppConfig) -> Router {
// to avoid ambiguity with path parameter matching
.route("/syntheses/generate", post(handlers::generation::trigger_generate))
.route("/syntheses/generate/{job_id}/progress", get(handlers::generation::progress_stream))
// Article history & provenance routes (authenticated)
.route("/article-history", get(handlers::article_history::list_history))
.route("/syntheses/{id}/provenance", get(handlers::article_history::get_provenance))
// Syntheses CRUD routes (authenticated)
.route("/syntheses", get(handlers::syntheses::list))
.route("/syntheses/{id}", get(handlers::syntheses::get))

Loading…
Cancel
Save