use chrono::{DateTime, Utc};
use docbox_database::models::{
document_box::{DocumentBoxScopeRaw, WithScope},
file::FileWithExtra,
folder::{FolderId, FolderWithExtra},
link::LinkWithExtra,
shared::FolderPathSegment,
user::{User, UserId},
};
use garde::Validate;
use mime::Mime;
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, skip_serializing_none};
use utoipa::ToSchema;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum SearchIndexType {
File,
Folder,
Link,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchIndexData {
#[serde(rename = "item_type")]
pub ty: SearchIndexType,
pub folder_id: FolderId,
pub document_box: DocumentBoxScopeRaw,
pub item_id: Uuid,
pub name: String,
pub mime: Option<String>,
pub content: Option<String>,
pub created_at: DateTime<Utc>,
pub created_by: Option<UserId>,
pub pages: Option<Vec<DocumentPage>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentPage {
pub page: u64,
pub content: String,
}
#[skip_serializing_none]
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateSearchIndexData {
pub folder_id: FolderId,
pub name: String,
pub content: Option<String>,
pub pages: Option<Vec<DocumentPage>>,
}
#[derive(Debug)]
pub struct FileSearchResults {
pub total_hits: u64,
pub results: Vec<PageResult>,
}
#[derive(Debug)]
pub struct SearchResults {
pub results: Vec<FlattenedItemResult>,
pub total_hits: u64,
}
#[derive(Debug)]
pub struct FlattenedItemResult {
pub item_ty: SearchIndexType,
pub item_id: Uuid,
pub document_box: DocumentBoxScopeRaw,
pub page_matches: Vec<PageResult>,
pub total_hits: u64,
pub score: SearchScore,
pub name_match: bool,
pub content_match: bool,
}
#[derive(Debug, Serialize, ToSchema)]
#[serde(untagged)]
pub enum SearchScore {
Integer(u64),
Float(f32),
}
#[derive(Debug, Serialize, ToSchema)]
pub struct PageResult {
pub page: u64,
pub matches: Vec<String>,
}
#[derive(Default, Debug, Validate, Deserialize, Serialize, ToSchema)]
#[serde(default)]
pub struct AdminSearchRequest {
#[garde(skip)]
#[schema(value_type = Vec<String>)]
pub scopes: Vec<DocumentBoxScopeRaw>,
#[serde(flatten)]
#[garde(dive)]
pub request: SearchRequest,
}
#[derive(Default, Debug, Validate, Deserialize, Serialize, ToSchema)]
#[serde(default)]
pub struct FileSearchRequest {
#[garde(skip)]
pub query: Option<String>,
#[garde(skip)]
pub offset: Option<u64>,
#[garde(skip)]
pub limit: Option<u16>,
}
#[serde_as]
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(transparent)]
pub struct StringMime(#[serde_as(as = "serde_with::DisplayFromStr")] pub Mime);
#[derive(Default, Debug, Validate, Deserialize, Serialize, ToSchema)]
#[serde(default)]
pub struct SearchRequest {
#[garde(skip)]
pub query: Option<String>,
#[garde(skip)]
pub neural: bool,
#[garde(skip)]
#[schema(value_type = Option<String>)]
pub mime: Option<StringMime>,
#[garde(skip)]
pub include_name: bool,
#[garde(skip)]
pub include_content: bool,
#[garde(dive)]
pub created_at: Option<SearchRange>,
#[garde(skip)]
pub created_by: Option<UserId>,
#[garde(skip)]
#[schema(value_type = Option<Uuid>)]
pub folder_id: Option<FolderId>,
#[garde(skip)]
pub size: Option<u16>,
#[garde(skip)]
pub offset: Option<u64>,
#[garde(range(max = 100))]
#[schema(maximum = 100)]
pub max_pages: Option<u16>,
#[garde(skip)]
pub pages_offset: Option<u64>,
}
#[derive(Default, Debug, Deserialize, Serialize, ToSchema)]
pub struct SearchRange {
pub start: Option<DateTime<Utc>>,
pub end: Option<DateTime<Utc>>,
}
impl Validate for SearchRange {
type Context = ();
fn validate_into(
&self,
_ctx: &Self::Context,
parent: &mut dyn FnMut() -> garde::Path,
report: &mut garde::Report,
) {
match (&self.start, &self.end) {
(None, None) => report.append(
parent(),
garde::Error::new("date range must have a start or end point"),
),
(Some(start), Some(end)) => {
if start > end {
report.append(
parent().join("start"),
garde::Error::new("date range start cannot be after end"),
)
}
}
(None, Some(_)) | (Some(_), None) => {}
}
}
}
#[derive(Debug, Serialize, ToSchema)]
#[serde(tag = "type")]
pub enum SearchResultData {
File(FileWithExtra),
Folder(FolderWithExtra),
Link(LinkWithExtra),
}
#[derive(Debug, Serialize, ToSchema)]
pub struct SearchResultResponse {
pub total_hits: u64,
pub results: Vec<SearchResultItem>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct FileSearchResultResponse {
pub total_hits: u64,
pub results: Vec<PageResult>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct AdminSearchResultResponse {
pub total_hits: u64,
pub results: Vec<WithScope<SearchResultItem>>,
}
#[derive(Debug, Serialize, ToSchema)]
pub struct SearchResultItem {
pub score: SearchScore,
pub path: Vec<FolderPathSegment>,
#[serde(flatten)]
pub data: SearchResultData,
pub page_matches: Vec<PageResult>,
pub total_hits: u64,
pub name_match: bool,
pub content_match: bool,
}
#[derive(Default, Debug, Validate, Deserialize, Serialize, ToSchema)]
#[serde(default)]
pub struct UsersRequest {
#[garde(skip)]
pub offset: Option<u64>,
#[garde(skip)]
pub size: Option<u16>,
}
#[derive(Debug, Serialize)]
pub struct AdminUsersResults {
pub results: Vec<User>,
pub total: i64,
}