feat: implement content retrieval for REST API endpoints
Co-authored-by: aider (openai/andrew/openrouter/anthropic/claude-sonnet-4) <aider@aider.chat>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use anyhow::Result;
|
use anyhow::{Result, anyhow};
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{ConnectInfo, Path, Query, State},
|
extract::{ConnectInfo, Path, Query, State},
|
||||||
http::{HeaderMap, StatusCode},
|
http::{HeaderMap, StatusCode},
|
||||||
@@ -11,6 +11,7 @@ use log::{debug, info, warn};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::io::Read;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
@@ -20,6 +21,7 @@ use tower_http::cors::CorsLayer;
|
|||||||
use tower::ServiceBuilder;
|
use tower::ServiceBuilder;
|
||||||
use tower_http::trace::TraceLayer;
|
use tower_http::trace::TraceLayer;
|
||||||
|
|
||||||
|
use crate::compression_engine::{CompressionType, get_compression_engine};
|
||||||
use crate::db;
|
use crate::db;
|
||||||
use crate::Args;
|
use crate::Args;
|
||||||
|
|
||||||
@@ -441,15 +443,26 @@ async fn handle_get_content_latest(
|
|||||||
})?
|
})?
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(_item) = item {
|
if let Some(item) = item {
|
||||||
// Get the actual content - this would need to be implemented
|
match get_item_content(&item, &state.data_dir).await {
|
||||||
// based on how content is stored and retrieved in your system
|
Ok(content) => {
|
||||||
let response = ApiResponse::<String> {
|
let response = ApiResponse {
|
||||||
success: false,
|
success: true,
|
||||||
data: None,
|
data: Some(content),
|
||||||
error: Some("Content retrieval not yet implemented".to_string()),
|
error: None,
|
||||||
};
|
};
|
||||||
Ok(Json(response))
|
Ok(Json(response))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to get content for item {}: {}", item.id.unwrap_or(0), e);
|
||||||
|
let response = ApiResponse::<String> {
|
||||||
|
success: false,
|
||||||
|
data: None,
|
||||||
|
error: Some(format!("Failed to retrieve content: {}", e)),
|
||||||
|
};
|
||||||
|
Ok(Json(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(StatusCode::NOT_FOUND)
|
Err(StatusCode::NOT_FOUND)
|
||||||
}
|
}
|
||||||
@@ -468,20 +481,37 @@ async fn handle_get_content(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(id) = item_id.parse::<i64>() {
|
if let Ok(id) = item_id.parse::<i64>() {
|
||||||
|
// 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;
|
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);
|
warn!("Failed to get item {} for content: {}", id, e);
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
})? {
|
})? {
|
||||||
// Get the actual content - this would need to be implemented
|
match get_item_content(&item, &state.data_dir).await {
|
||||||
// based on how content is stored and retrieved in your system
|
Ok(content) => {
|
||||||
let response = ApiResponse::<String> {
|
let response = ApiResponse {
|
||||||
success: false,
|
success: true,
|
||||||
data: None,
|
data: Some(content),
|
||||||
error: Some("Content retrieval not yet implemented".to_string()),
|
error: None,
|
||||||
};
|
};
|
||||||
Ok(Json(response))
|
Ok(Json(response))
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to get content for item {}: {}", id, e);
|
||||||
|
let response = ApiResponse::<String> {
|
||||||
|
success: false,
|
||||||
|
data: None,
|
||||||
|
error: Some(format!("Failed to retrieve content: {}", e)),
|
||||||
|
};
|
||||||
|
Ok(Json(response))
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(StatusCode::NOT_FOUND)
|
Err(StatusCode::NOT_FOUND)
|
||||||
}
|
}
|
||||||
@@ -703,6 +733,28 @@ async fn handle_openapi() -> Json<serde_json::Value> {
|
|||||||
Json(openapi_spec)
|
Json(openapi_spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_item_content(item: &db::Item, data_dir: &PathBuf) -> Result<String> {
|
||||||
|
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> {
|
async fn handle_swagger_ui() -> Html<&'static str> {
|
||||||
let html = r#"<!DOCTYPE html>
|
let html = r#"<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|||||||
Reference in New Issue
Block a user