use serde::{Deserialize, Serialize}; use utoipa::ToSchema; /** * Data Transfer Object for file search criteria. * * This structure represents all possible search parameters that can be used * to filter files and folders in the system. It supports various filter types * including name matching, file types, date ranges, and size constraints. */ #[derive(Debug, Clone, Hash, Serialize, Deserialize, ToSchema)] pub struct SearchCriteriaDto { /// Optional text to search in file/folder names #[serde(skip_serializing_if = "Option::is_none")] pub name_contains: Option, /// Optional list of file extensions to include (e.g., "pdf", "jpg") #[serde(skip_serializing_if = "Option::is_none")] pub file_types: Option>, /// Optional minimum creation date (seconds since epoch) #[serde(skip_serializing_if = "Option::is_none")] pub created_after: Option, /// Optional maximum creation date (seconds since epoch) #[serde(skip_serializing_if = "Option::is_none")] pub created_before: Option, /// Optional minimum modification date (seconds since epoch) #[serde(skip_serializing_if = "Option::is_none")] pub modified_after: Option, /// Optional maximum modification date (seconds since epoch) #[serde(skip_serializing_if = "Option::is_none")] pub modified_before: Option, /// Optional minimum file size in bytes #[serde(skip_serializing_if = "Option::is_none")] pub min_size: Option, /// Optional maximum file size in bytes #[serde(skip_serializing_if = "Option::is_none")] pub max_size: Option, /// Optional folder ID to limit search scope #[serde(skip_serializing_if = "Option::is_none")] pub folder_id: Option, /// Whether to search recursively within subfolders (default: true) #[serde(default = "default_recursive")] pub recursive: bool, /// Maximum number of results to return #[serde(default = "default_limit")] pub limit: usize, /// Offset for pagination #[serde(default)] pub offset: usize, /// Sort order for results: "relevance", "name", "name_desc", "date", "date_desc", "size", "size_desc" #[serde(default = "default_sort_by")] pub sort_by: String, } /// Default value for recursive search (true) fn default_recursive() -> bool { true } /// Default limit for search results (100) fn default_limit() -> usize { 100 } /// Default sort_by value fn default_sort_by() -> String { "relevance".to_string() } impl Default for SearchCriteriaDto { fn default() -> Self { Self { name_contains: None, file_types: None, created_after: None, created_before: None, modified_after: None, modified_before: None, min_size: None, max_size: None, folder_id: None, recursive: default_recursive(), limit: default_limit(), offset: 0, sort_by: default_sort_by(), } } } /// A file search result enriched with server-computed metadata #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct SearchFileResultDto { /// File ID pub id: String, /// File name pub name: String, /// Path to the file (relative) pub path: String, /// Size in bytes pub size: u64, /// MIME type pub mime_type: String, /// Parent folder ID pub folder_id: Option, /// Creation timestamp pub created_at: u64, /// Last modification timestamp pub modified_at: u64, /// Relevance score (0-100) computed server-side pub relevance_score: u32, /// Human-readable file size (e.g., "2.5 MB") pub size_formatted: String, /// CSS icon class for the file type (e.g., "fas fa-file-pdf") pub icon_class: String, /// Extra CSS class for icon styling (e.g., "pdf-icon", "code-icon js-icon") pub icon_special_class: String, /// Content category: "document", "image", "video", "audio", "archive", "code", "other" pub category: String, /// Raw BLAKE3 content hash. Feeds `FileDto::content_hash` and /// `File::compute_etag` when search results are converted to /// `FileDto` (NC REPORT/SEARCH response). Defaults to `String::new()` /// for backward-compatible deserialisation of cached results /// that pre-date the column. #[serde(default)] pub blob_hash: String, /// Plain-text fragment around the first content match, present only for /// hits discovered through the full-text content index. #[serde(default, skip_serializing_if = "Option::is_none")] pub snippet: Option, /// Where the match came from: "name" (filename matched the query) or /// "content" (discovered via the full-text content index). #[serde(default, skip_serializing_if = "Option::is_none")] pub match_source: Option, } /// A folder search result enriched with server-computed metadata #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct SearchFolderResultDto { /// Folder ID pub id: String, /// Folder name pub name: String, /// Path to the folder (relative) pub path: String, /// Parent folder ID pub parent_id: Option, /// Creation timestamp pub created_at: u64, /// Last modification timestamp pub modified_at: u64, /// Whether it is a root folder pub is_root: bool, /// Relevance score (0-100) computed server-side pub relevance_score: u32, } /** * Data Transfer Object for search results. * * This structure encapsulates the results of a search operation, including * both files and folders that match the search criteria, along with pagination * information and server-computed metadata. */ #[derive(Debug, Serialize, Deserialize, ToSchema)] pub struct SearchResultsDto { /// Files matching the search criteria (enriched with metadata) pub files: Vec, /// Folders matching the search criteria (enriched with metadata) pub folders: Vec, /// Total count of matching items (for pagination) pub total_count: Option, /// Limit used in the search pub limit: usize, /// Offset used in the search pub offset: usize, /// Whether there are more results available pub has_more: bool, /// Query execution time in milliseconds (server-side) pub query_time_ms: u64, /// Sort order used pub sort_by: String, } impl SearchResultsDto { /// Creates a new empty search results object pub fn empty() -> Self { Self { files: Vec::new(), folders: Vec::new(), total_count: None, limit: 0, offset: 0, has_more: false, query_time_ms: 0, sort_by: "relevance".to_string(), } } /// Creates a new search results object from files and folders pub fn new( files: Vec, folders: Vec, limit: usize, offset: usize, total_count: Option, query_time_ms: u64, sort_by: String, ) -> Self { let has_more = match total_count { Some(total) => (offset + files.len() + folders.len()) < total, None => false, }; Self { files, folders, total_count, limit, offset, has_more, query_time_ms, sort_by, } } } /// DTO for search suggestion results (quick prefix search) #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct SearchSuggestionsDto { /// Suggested file/folder names matching the query prefix pub suggestions: Vec, /// Query execution time in milliseconds pub query_time_ms: u64, } /// Individual search suggestion item #[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct SearchSuggestionItem { /// The suggested name pub name: String, /// Type: "file" or "folder" pub item_type: String, /// Item ID for navigation pub id: String, /// Path for context pub path: String, /// CSS icon class pub icon_class: String, /// Extra CSS class for icon styling pub icon_special_class: String, /// Relevance score pub relevance_score: u32, }