1use chrono::{DateTime, Utc};
7use docbox_database::models::{
8 document_box::{DocumentBoxScopeRaw, WithScope},
9 file::FileWithExtra,
10 folder::{FolderId, FolderWithExtra},
11 link::LinkWithExtra,
12 shared::FolderPathSegment,
13 user::{User, UserId},
14};
15use garde::Validate;
16use mime::Mime;
17use serde::{Deserialize, Serialize};
18use serde_with::{serde_as, skip_serializing_none};
19use utoipa::ToSchema;
20use uuid::Uuid;
21
22#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
23pub enum SearchIndexType {
24 File,
25 Folder,
26 Link,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SearchIndexData {
31 #[serde(rename = "item_type")]
33 pub ty: SearchIndexType,
34
35 pub folder_id: FolderId,
39 pub document_box: DocumentBoxScopeRaw,
43
44 pub item_id: Uuid,
49 pub name: String,
51 pub mime: Option<String>,
53 pub content: Option<String>,
56 pub created_at: DateTime<Utc>,
58 pub created_by: Option<UserId>,
60 pub pages: Option<Vec<DocumentPage>>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct DocumentPage {
66 pub page: u64,
67 pub content: String,
68}
69
70#[skip_serializing_none]
71#[derive(Debug, Serialize, Deserialize)]
72pub struct UpdateSearchIndexData {
73 pub folder_id: FolderId,
74 pub name: String,
75 pub content: Option<String>,
76 pub pages: Option<Vec<DocumentPage>>,
77}
78
79pub struct FileSearchResults {
81 pub total_hits: u64,
83 pub results: Vec<PageResult>,
85}
86
87#[derive(Debug)]
88pub struct SearchResults {
89 pub results: Vec<FlattenedItemResult>,
90 pub total_hits: u64,
91}
92
93#[derive(Debug)]
95pub struct FlattenedItemResult {
96 pub item_ty: SearchIndexType,
98 pub item_id: Uuid,
100 pub document_box: DocumentBoxScopeRaw,
102 pub page_matches: Vec<PageResult>,
104 pub total_hits: u64,
106 pub score: SearchScore,
108
109 pub name_match: bool,
111
112 pub content_match: bool,
114}
115
116#[derive(Debug, Serialize, ToSchema)]
117#[serde(untagged)]
118pub enum SearchScore {
119 Integer(u64),
121 Float(f32),
123}
124
125#[derive(Debug, Serialize, ToSchema)]
126pub struct PageResult {
127 pub page: u64,
128 pub matches: Vec<String>,
129}
130
131#[derive(Default, Debug, Validate, Deserialize, Serialize, ToSchema)]
134#[serde(default)]
135pub struct AdminSearchRequest {
136 #[garde(skip)]
137 #[schema(value_type = Vec<String>)]
138 pub scopes: Vec<DocumentBoxScopeRaw>,
139
140 #[serde(flatten)]
141 #[garde(dive)]
142 pub request: SearchRequest,
143}
144
145#[derive(Default, Debug, Validate, Deserialize, Serialize, ToSchema)]
147#[serde(default)]
148pub struct FileSearchRequest {
149 #[garde(skip)]
151 pub query: Option<String>,
152
153 #[garde(skip)]
155 pub offset: Option<u64>,
156
157 #[garde(skip)]
159 pub limit: Option<u16>,
160}
161
162#[serde_as]
164#[derive(Debug, Clone, Deserialize, Serialize)]
165#[serde(transparent)]
166pub struct StringMime(#[serde_as(as = "serde_with::DisplayFromStr")] pub Mime);
167
168#[derive(Default, Debug, Validate, Deserialize, Serialize, ToSchema)]
170#[serde(default)]
171pub struct SearchRequest {
172 #[garde(skip)]
174 pub query: Option<String>,
175
176 #[garde(skip)]
178 pub neural: bool,
179
180 #[garde(skip)]
182 #[schema(value_type = Option<String>)]
183 pub mime: Option<StringMime>,
184
185 #[garde(skip)]
187 pub include_name: bool,
188
189 #[garde(skip)]
191 pub include_content: bool,
192
193 #[garde(dive)]
195 pub created_at: Option<SearchRange>,
196
197 #[garde(skip)]
199 pub created_by: Option<UserId>,
200
201 #[garde(skip)]
204 #[schema(value_type = Option<Uuid>)]
205 pub folder_id: Option<FolderId>,
206
207 #[garde(skip)]
209 pub size: Option<u16>,
210
211 #[garde(skip)]
213 pub offset: Option<u64>,
214
215 #[garde(range(max = 100))]
217 #[schema(maximum = 100)]
218 pub max_pages: Option<u16>,
219
220 #[garde(skip)]
222 pub pages_offset: Option<u64>,
223}
224
225#[derive(Default, Debug, Deserialize, Serialize, ToSchema)]
226pub struct SearchRange {
227 pub start: Option<DateTime<Utc>>,
228 pub end: Option<DateTime<Utc>>,
229}
230
231impl Validate for SearchRange {
232 type Context = ();
233
234 fn validate_into(
235 &self,
236 _ctx: &Self::Context,
237 parent: &mut dyn FnMut() -> garde::Path,
238 report: &mut garde::Report,
239 ) {
240 match (&self.start, &self.end) {
241 (None, None) => report.append(
242 parent(),
243 garde::Error::new("date range must have a start or end point"),
244 ),
245 (Some(start), Some(end)) => {
246 if start > end {
247 report.append(
248 parent().join("start"),
249 garde::Error::new("date range start cannot be after end"),
250 )
251 }
252 }
253 (None, Some(_)) | (Some(_), None) => {}
254 }
255 }
256}
257
258#[derive(Debug, Serialize, ToSchema)]
259#[serde(tag = "type")]
260pub enum SearchResultData {
261 File(FileWithExtra),
262 Folder(FolderWithExtra),
263 Link(LinkWithExtra),
264}
265
266#[derive(Debug, Serialize, ToSchema)]
267pub struct SearchResultResponse {
268 pub total_hits: u64,
269 pub results: Vec<SearchResultItem>,
270}
271
272#[derive(Debug, Serialize, ToSchema)]
273pub struct FileSearchResultResponse {
274 pub total_hits: u64,
275 pub results: Vec<PageResult>,
276}
277
278#[derive(Debug, Serialize, ToSchema)]
279pub struct AdminSearchResultResponse {
280 pub total_hits: u64,
281 pub results: Vec<WithScope<SearchResultItem>>,
282}
283
284#[derive(Debug, Serialize, ToSchema)]
285pub struct SearchResultItem {
286 pub score: SearchScore,
288 pub path: Vec<FolderPathSegment>,
290 #[serde(flatten)]
292 pub data: SearchResultData,
293
294 pub page_matches: Vec<PageResult>,
295 pub total_hits: u64,
296
297 pub name_match: bool,
298 pub content_match: bool,
299}
300
301#[derive(Default, Debug, Validate, Deserialize, Serialize, ToSchema)]
303#[serde(default)]
304pub struct UsersRequest {
305 #[garde(skip)]
307 pub offset: Option<u64>,
308
309 #[garde(skip)]
311 pub size: Option<u16>,
312}
313
314#[derive(Debug, Serialize)]
315pub struct AdminUsersResults {
316 pub results: Vec<User>,
318 pub total: i64,
320}