//! Handler for the `/v1/feed` endpoint (newest-first assertion browsing). use axum::{extract::State, Json}; use tracing::{instrument, warn}; use crate::{ dto::{FeedParams, QueryResponse}, error::Result, extractors::QsQuery, state::AppState, }; use stemedb_query::Query; use super::query::assertion_to_dto_with_warning; /// Browse all assertions in newest-first order with pagination. /// /// Returns assertions sorted by timestamp descending, useful for /// "what was just written?" dashboards and dev workflows. No lens /// resolution is applied — this is a raw chronological feed. /// /// # Pagination /// /// - `limit`: Max results per page (default 50, max 500) /// - `offset`: Number of results to skip (default 0) #[utoipa::path( get, path = "/v1/feed", params( ("limit" = Option, Query, description = "Max results (default 50, max 500)"), ("offset" = Option, Query, description = "Pagination offset (default 0)") ), responses( (status = 200, description = "Feed results", body = QueryResponse), (status = 500, description = "Internal server error", body = crate::dto::ErrorResponse) ), tag = "query" )] #[instrument(skip(state), fields(limit = params.limit, offset = params.offset))] pub async fn feed( State(state): State, QsQuery(params): QsQuery, ) -> Result> { metrics::counter!("stemedb_queries_total", "endpoint" => "feed").increment(1); let query_start = std::time::Instant::now(); // Fetch all assertions (no subject filter) let query = Query::builder().limit(usize::MAX).build(); let query_engine = state.query_engine(); let result = query_engine.execute(&query).await?; let mut assertions = result.assertions; if assertions.len() > 10_000 { warn!( count = assertions.len(), "Feed scanning large assertion set; consider adding index-backed pagination" ); } // Sort by timestamp descending (newest first) assertions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); let total_count = assertions.len(); let limit = params.clamped_limit(); let offset = params.offset; let has_more = offset + limit < total_count; // Apply offset + limit pagination let page: Vec<_> = assertions.into_iter().skip(offset).take(limit).collect(); // Convert to DTOs (no source enrichment for speed) let assertion_responses = page .into_iter() .map(|a| assertion_to_dto_with_warning(a, None)) .collect::>>()?; metrics::histogram!("stemedb_query_latency_seconds", "endpoint" => "feed") .record(query_start.elapsed().as_secs_f64()); Ok(Json(QueryResponse { assertions: assertion_responses, total_count, has_more, conflict_score: None, resolution_confidence: None, changes_since: None, })) }