@@ -6,8 +6,11 @@ use std::sync::Arc;
66
77use crate :: error:: AppError ;
88use crate :: http:: { Request , Response , ResponseBody } ;
9+ use crate :: search:: { perform_search, SearchParams , SearchResult } ;
910use crate :: upload:: UploadHandler ;
1011use crate :: utils:: parse_query_params;
12+ use log:: debug;
13+ use std:: time:: Instant ;
1114
1215/// Register all internal routes under /_irondrop/.
1316pub fn register_internal_routes (
@@ -28,6 +31,22 @@ pub fn register_internal_routes(
2831 Box :: new ( |_| Ok ( create_health_check_response ( ) ) ) ,
2932 ) ;
3033
34+ // Compatibility routes for legacy endpoints
35+ router. register_exact (
36+ "GET" ,
37+ "/_health" ,
38+ Box :: new ( |_| Ok ( create_health_check_response ( ) ) ) ,
39+ ) ;
40+
41+ // Legacy monitor endpoint compatibility
42+ if let Some ( stats_arc) = stats. clone ( ) {
43+ router. register_exact (
44+ "GET" ,
45+ "/monitor" ,
46+ Box :: new ( move |req : & Request | handle_monitor_request ( req, Some ( stats_arc. as_ref ( ) ) ) ) ,
47+ ) ;
48+ }
49+
3150 // Static assets (new namespace)
3251 router. register_prefix (
3352 "GET" ,
@@ -88,6 +107,15 @@ pub fn register_internal_routes(
88107 Box :: new ( move |req : & Request | handle_monitor_request ( req, Some ( stats_arc. as_ref ( ) ) ) ) ,
89108 ) ;
90109 }
110+
111+ // Search endpoint
112+ if let Some ( base_arc) = base_dir {
113+ router. register_exact (
114+ "GET" ,
115+ "/_irondrop/search" ,
116+ Box :: new ( move |req : & Request | handle_search_api_request ( req, & base_arc) ) ,
117+ ) ;
118+ }
91119}
92120
93121pub fn create_health_check_response ( ) -> Response {
@@ -499,3 +527,123 @@ fn normalize_path(path: &std::path::Path) -> Result<std::path::PathBuf, AppError
499527 }
500528 Ok ( components. iter ( ) . collect ( ) )
501529}
530+
531+ /// URL decode function for parsing query parameters
532+ fn url_decode ( s : & str ) -> String {
533+ let mut result = String :: with_capacity ( s. len ( ) ) ;
534+ let mut chars = s. chars ( ) ;
535+ while let Some ( ch) = chars. next ( ) {
536+ if ch == '%' {
537+ let hex: String = chars. by_ref ( ) . take ( 2 ) . collect ( ) ;
538+ if let Ok ( byte) = u8:: from_str_radix ( & hex, 16 ) {
539+ result. push ( byte as char ) ;
540+ } else {
541+ result. push ( ch) ;
542+ }
543+ } else if ch == '+' {
544+ result. push ( ' ' ) ;
545+ } else {
546+ result. push ( ch) ;
547+ }
548+ }
549+ result
550+ }
551+
552+ /// Handle search API requests with optimizations
553+ pub fn handle_search_api_request (
554+ request : & Request ,
555+ base_dir : & Arc < std:: path:: PathBuf > ,
556+ ) -> Result < Response , AppError > {
557+ let start_time = Instant :: now ( ) ;
558+
559+ // Parse query parameters manually
560+ let query_params: HashMap < String , String > =
561+ if let Some ( query_string) = request. path . split ( '?' ) . nth ( 1 ) {
562+ query_string
563+ . split ( '&' )
564+ . filter_map ( |param| {
565+ let mut parts = param. splitn ( 2 , '=' ) ;
566+ match ( parts. next ( ) , parts. next ( ) ) {
567+ ( Some ( key) , Some ( value) ) => Some ( ( url_decode ( key) , url_decode ( value) ) ) ,
568+ _ => None ,
569+ }
570+ } )
571+ . collect ( )
572+ } else {
573+ HashMap :: new ( )
574+ } ;
575+
576+ let search_query = query_params. get ( "q" ) . ok_or ( AppError :: BadRequest ) ?;
577+
578+ // Validate query length for performance
579+ if search_query. len ( ) < 2 {
580+ return Err ( AppError :: BadRequest ) ;
581+ }
582+ if search_query. len ( ) > 100 {
583+ return Err ( AppError :: BadRequest ) ;
584+ }
585+
586+ let search_path = query_params. get ( "path" ) . map_or ( "/" , |v| v) ;
587+ let limit = query_params
588+ . get ( "limit" )
589+ . and_then ( |v| v. parse :: < usize > ( ) . ok ( ) )
590+ . unwrap_or ( 50 )
591+ . min ( 200 ) ; // Cap at 200 results
592+ let offset = query_params
593+ . get ( "offset" )
594+ . and_then ( |v| v. parse :: < usize > ( ) . ok ( ) )
595+ . unwrap_or ( 0 ) ;
596+
597+ let params = SearchParams {
598+ query : search_query. clone ( ) ,
599+ path : search_path. to_string ( ) ,
600+ limit,
601+ offset,
602+ case_sensitive : false ,
603+ } ;
604+
605+ // Perform optimized search with caching and indexing
606+ let mut results = perform_search ( base_dir, & params) ?;
607+
608+ // Sort by relevance score
609+ results. sort_by ( |a, b| {
610+ b. score
611+ . partial_cmp ( & a. score )
612+ . unwrap_or ( std:: cmp:: Ordering :: Equal )
613+ } ) ;
614+
615+ // Apply pagination
616+ let _total_count = results. len ( ) ;
617+ let paginated_results: Vec < SearchResult > =
618+ results. into_iter ( ) . skip ( offset) . take ( limit) . collect ( ) ;
619+
620+ let _elapsed_ms = start_time. elapsed ( ) . as_millis ( ) ;
621+
622+ // Create simple JSON manually to avoid serde dependency
623+ let json_items: Vec < String > = paginated_results
624+ . iter ( )
625+ . map ( |result| {
626+ format ! (
627+ r#"{{"name":"{}","path":"{}","size":"{}","type":"{}"}}"# ,
628+ result. name. replace( '"' , r#"\""# ) ,
629+ result. path. replace( '"' , r#"\""# ) ,
630+ result. size,
631+ result. file_type
632+ )
633+ } )
634+ . collect ( ) ;
635+
636+ let json_response = format ! ( "[{}]" , json_items. join( "," ) ) ;
637+
638+ Ok ( Response {
639+ status_code : 200 ,
640+ status_text : "OK" . to_string ( ) ,
641+ headers : {
642+ let mut map = HashMap :: new ( ) ;
643+ map. insert ( "Content-Type" . to_string ( ) , "application/json" . to_string ( ) ) ;
644+ map. insert ( "Access-Control-Allow-Origin" . to_string ( ) , "*" . to_string ( ) ) ;
645+ map
646+ } ,
647+ body : ResponseBody :: Text ( json_response) ,
648+ } )
649+ }
0 commit comments