From 35ae5776c0a0bfad62bbc13c253d86ddd6d0a27e Mon Sep 17 00:00:00 2001 From: Andrew Phillips Date: Sun, 10 Aug 2025 21:46:27 -0300 Subject: [PATCH] feat: implement content retrieval for REST API endpoints Co-authored-by: aider (openai/andrew/openrouter/anthropic/claude-sonnet-4) --- src/modes/server.rs | 90 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/src/modes/server.rs b/src/modes/server.rs index 5af4829..40f290a 100644 --- a/src/modes/server.rs +++ b/src/modes/server.rs @@ -1,4 +1,4 @@ -use anyhow::Result; +use anyhow::{Result, anyhow}; use axum::{ extract::{ConnectInfo, Path, Query, State}, http::{HeaderMap, StatusCode}, @@ -11,6 +11,7 @@ use log::{debug, info, warn}; use serde::{Deserialize, Serialize}; use serde_json::json; use std::collections::HashMap; +use std::io::Read; use std::net::SocketAddr; use std::path::PathBuf; use std::str::FromStr; @@ -20,6 +21,7 @@ use tower_http::cors::CorsLayer; use tower::ServiceBuilder; use tower_http::trace::TraceLayer; +use crate::compression_engine::{CompressionType, get_compression_engine}; use crate::db; use crate::Args; @@ -441,15 +443,26 @@ async fn handle_get_content_latest( })? }; - if let Some(_item) = item { - // Get the actual content - this would need to be implemented - // based on how content is stored and retrieved in your system - let response = ApiResponse:: { - success: false, - data: None, - error: Some("Content retrieval not yet implemented".to_string()), - }; - Ok(Json(response)) + if let Some(item) = item { + match get_item_content(&item, &state.data_dir).await { + Ok(content) => { + let response = ApiResponse { + success: true, + data: Some(content), + error: None, + }; + Ok(Json(response)) + } + Err(e) => { + warn!("Failed to get content for item {}: {}", item.id.unwrap_or(0), e); + let response = ApiResponse:: { + success: false, + data: None, + error: Some(format!("Failed to retrieve content: {}", e)), + }; + Ok(Json(response)) + } + } } else { Err(StatusCode::NOT_FOUND) } @@ -468,20 +481,37 @@ async fn handle_get_content( } if let Ok(id) = item_id.parse::() { + // Validate that item ID is positive to prevent path traversal issues + if id <= 0 { + warn!("Invalid item ID {} from {}", id, addr); + return Err(StatusCode::BAD_REQUEST); + } + let mut conn = state.db.lock().await; - if let Some(_item) = db::get_item(&mut *conn, id).map_err(|e| { + if let Some(item) = db::get_item(&mut *conn, id).map_err(|e| { warn!("Failed to get item {} for content: {}", id, e); StatusCode::INTERNAL_SERVER_ERROR })? { - // Get the actual content - this would need to be implemented - // based on how content is stored and retrieved in your system - let response = ApiResponse:: { - success: false, - data: None, - error: Some("Content retrieval not yet implemented".to_string()), - }; - Ok(Json(response)) + match get_item_content(&item, &state.data_dir).await { + Ok(content) => { + let response = ApiResponse { + success: true, + data: Some(content), + error: None, + }; + Ok(Json(response)) + } + Err(e) => { + warn!("Failed to get content for item {}: {}", id, e); + let response = ApiResponse:: { + success: false, + data: None, + error: Some(format!("Failed to retrieve content: {}", e)), + }; + Ok(Json(response)) + } + } } else { Err(StatusCode::NOT_FOUND) } @@ -703,6 +733,28 @@ async fn handle_openapi() -> Json { Json(openapi_spec) } +async fn get_item_content(item: &db::Item, data_dir: &PathBuf) -> Result { + let item_id = item.id.ok_or_else(|| anyhow!("Item missing ID"))?; + + // Validate that item ID is positive to prevent path traversal issues + if item_id <= 0 { + return Err(anyhow!("Invalid item ID: {}", item_id)); + } + + let mut item_path = data_dir.clone(); + item_path.push(item_id.to_string()); + + let compression_type = CompressionType::from_str(&item.compression)?; + let compression_engine = get_compression_engine(compression_type)?; + + // Read the content using the compression engine + let mut reader = compression_engine.open(item_path)?; + let mut content = String::new(); + reader.read_to_string(&mut content)?; + + Ok(content) +} + async fn handle_swagger_ui() -> Html<&'static str> { let html = r#"