Readur/src/routes/search.rs

182 lines
5.2 KiB
Rust

use axum::{
extract::{Query, State},
http::StatusCode,
response::Json,
routing::get,
Router,
};
use std::sync::Arc;
use crate::{
auth::AuthUser,
models::{SearchRequest, SearchResponse, EnhancedDocumentResponse, SearchFacetsResponse, FacetItem},
AppState,
};
pub fn router() -> Router<Arc<AppState>> {
Router::new()
.route("/", get(search_documents))
.route("/enhanced", get(enhanced_search_documents))
.route("/facets", get(get_search_facets))
}
#[utoipa::path(
get,
path = "/api/search",
tag = "search",
security(
("bearer_auth" = [])
),
params(
SearchRequest
),
responses(
(status = 200, description = "Enhanced search results with relevance ranking, text snippets, and OCR-extracted content matching", body = SearchResponse),
(status = 401, description = "Unauthorized - valid authentication required"),
(status = 500, description = "Internal server error")
)
)]
async fn search_documents(
State(state): State<Arc<AppState>>,
auth_user: AuthUser,
Query(search_request): Query<SearchRequest>,
) -> Result<Json<SearchResponse>, StatusCode> {
let (documents, total) = state
.db
.search_documents(auth_user.user.id, search_request)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let response = SearchResponse {
documents: documents.into_iter().map(|doc| EnhancedDocumentResponse {
id: doc.id,
filename: doc.filename,
original_filename: doc.original_filename,
file_size: doc.file_size,
mime_type: doc.mime_type,
tags: doc.tags,
created_at: doc.created_at,
has_ocr_text: doc.ocr_text.is_some(),
ocr_confidence: doc.ocr_confidence,
ocr_word_count: doc.ocr_word_count,
ocr_processing_time_ms: doc.ocr_processing_time_ms,
ocr_status: doc.ocr_status,
search_rank: None,
snippets: Vec::new(),
}).collect(),
total,
query_time_ms: 0,
suggestions: Vec::new(),
};
Ok(Json(response))
}
#[utoipa::path(
get,
path = "/api/search/enhanced",
tag = "search",
security(
("bearer_auth" = [])
),
params(
SearchRequest
),
responses(
(status = 200, description = "Enhanced search results with snippets and suggestions", body = SearchResponse),
(status = 401, description = "Unauthorized")
)
)]
async fn enhanced_search_documents(
State(state): State<Arc<AppState>>,
auth_user: AuthUser,
Query(search_request): Query<SearchRequest>,
) -> Result<Json<SearchResponse>, StatusCode> {
// Generate suggestions before moving search_request
let suggestions = generate_search_suggestions(&search_request.query);
let (documents, total, query_time) = state
.db
.enhanced_search_documents_with_role(auth_user.user.id, auth_user.user.role, search_request)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let response = SearchResponse {
documents,
total,
query_time_ms: query_time,
suggestions,
};
Ok(Json(response))
}
fn generate_search_suggestions(query: &str) -> Vec<String> {
// Simple suggestion generation - could be enhanced with a proper suggestion system
let mut suggestions = Vec::new();
if query.len() > 3 {
// Common search variations
suggestions.push(format!("\"{}\"", query)); // Exact phrase
// Add wildcard suggestions
if !query.contains('*') {
suggestions.push(format!("{}*", query));
}
// Add similar terms (this would typically come from a thesaurus or ML model)
if query.contains("document") {
suggestions.push(query.replace("document", "file"));
suggestions.push(query.replace("document", "paper"));
}
}
suggestions.into_iter().take(3).collect()
}
#[utoipa::path(
get,
path = "/api/search/facets",
tag = "search",
security(
("bearer_auth" = [])
),
responses(
(status = 200, description = "Search facets with counts", body = SearchFacetsResponse),
(status = 401, description = "Unauthorized")
)
)]
async fn get_search_facets(
State(state): State<Arc<AppState>>,
auth_user: AuthUser,
) -> Result<Json<SearchFacetsResponse>, StatusCode> {
let user_id = auth_user.user.id;
let user_role = auth_user.user.role;
// Get MIME type facets
let mime_type_facets = state
.db
.get_mime_type_facets(user_id, user_role.clone())
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// Get tag facets
let tag_facets = state
.db
.get_tag_facets(user_id, user_role)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let response = SearchFacetsResponse {
mime_types: mime_type_facets
.into_iter()
.map(|(value, count)| FacetItem { value, count })
.collect(),
tags: tag_facets
.into_iter()
.map(|(value, count)| FacetItem { value, count })
.collect(),
};
Ok(Json(response))
}