1use anyhow::Result;
2use chrono::{DateTime, NaiveDate, Utc};
3use serde::{Deserialize, Serialize};
4use serde_json::{Value, json};
5use utoipa::ToSchema;
6use uuid::Uuid;
7
8use super::Visibility;
9
10#[derive(Debug, Clone, Copy, Serialize, Deserialize, ToSchema, PartialEq, Eq)]
11#[serde(rename_all = "snake_case")]
12pub enum LibraryIngestStatus {
13 Pending,
14 Running,
15 Succeeded,
16 Failed,
17}
18
19impl LibraryIngestStatus {
20 pub fn as_str(self) -> &'static str {
21 match self {
22 Self::Pending => "pending",
23 Self::Running => "running",
24 Self::Succeeded => "succeeded",
25 Self::Failed => "failed",
26 }
27 }
28}
29
30impl std::str::FromStr for LibraryIngestStatus {
31 type Err = anyhow::Error;
32
33 fn from_str(value: &str) -> Result<Self, Self::Err> {
34 match value {
35 "pending" => Ok(Self::Pending),
36 "running" => Ok(Self::Running),
37 "succeeded" => Ok(Self::Succeeded),
38 "failed" => Ok(Self::Failed),
39 other => Err(anyhow::anyhow!(
40 "unsupported library ingest status: {other}"
41 )),
42 }
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
47pub struct CreateFolderRequest {
48 #[serde(default)]
49 pub parent_folder_id: Option<Uuid>,
50 pub name: String,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
54pub struct MoveFolderRequest {
55 #[serde(default)]
56 pub target_folder_id: Option<Uuid>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
60pub struct MoveFileRequest {
61 #[serde(default)]
62 pub target_folder_id: Option<Uuid>,
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
66pub struct CreateTextRequest {
67 #[serde(default)]
68 pub folder_id: Option<Uuid>,
69 pub title: String,
70 pub content: String,
71 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub source_uri: Option<String>,
73 #[serde(default, skip_serializing_if = "Option::is_none")]
74 pub summary: Option<String>,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
78pub struct UpsertLibraryTextRequest {
79 pub external_id: String,
80 #[serde(default)]
81 pub folder_id: Option<Uuid>,
82 pub title: String,
83 pub content: String,
84 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub source_uri: Option<String>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub summary: Option<String>,
88 #[serde(default, skip_serializing_if = "Option::is_none")]
89 pub published_at: Option<NaiveDate>,
90 #[serde(default = "default_metadata_json")]
91 #[schema(value_type = Object)]
92 pub metadata_json: Value,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
96pub struct LibraryFileSummary {
97 pub file_id: Uuid,
98 pub group_key: String,
99 pub project_key: String,
100 pub visibility: Visibility,
101 #[serde(default)]
102 pub folder_id: Option<Uuid>,
103 pub filename: String,
104 pub media_type: String,
105 pub size_bytes: i64,
106 pub ingest_status: LibraryIngestStatus,
107 #[serde(default, skip_serializing_if = "Option::is_none")]
108 pub error_message: Option<String>,
109 pub created_at: DateTime<Utc>,
110 pub updated_at: DateTime<Utc>,
111 #[serde(default, skip_serializing_if = "Option::is_none")]
112 pub ingested_at: Option<DateTime<Utc>>,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
116pub struct LibraryFolderNode {
117 pub group_key: String,
118 pub project_key: String,
119 pub visibility: Visibility,
120 #[serde(default)]
121 pub folder_id: Option<Uuid>,
122 #[serde(default)]
123 pub parent_folder_id: Option<Uuid>,
124 pub name: String,
125 pub path: String,
126 pub processing_count: usize,
127 #[schema(no_recursion)]
128 pub children: Vec<LibraryFolderNode>,
129 pub files: Vec<LibraryFileSummary>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
133pub struct LibraryFolderResponse {
134 pub folder_id: Uuid,
135 pub group_key: String,
136 pub project_key: String,
137 pub visibility: Visibility,
138 #[serde(default)]
139 pub parent_folder_id: Option<Uuid>,
140 pub name: String,
141 pub path: String,
142 pub created_at: DateTime<Utc>,
143 pub updated_at: DateTime<Utc>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
147pub struct LibraryTreeResponse {
148 pub root: LibraryFolderNode,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
152pub struct LibraryIngestJobResponse {
153 pub job_id: Uuid,
154 pub group_key: String,
155 pub project_key: String,
156 pub visibility: Visibility,
157 pub file_id: Uuid,
158 pub status: LibraryIngestStatus,
159 #[serde(default, skip_serializing_if = "Option::is_none")]
160 pub docling_task_id: Option<String>,
161 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub error_message: Option<String>,
163 pub created_at: DateTime<Utc>,
164 #[serde(default, skip_serializing_if = "Option::is_none")]
165 pub started_at: Option<DateTime<Utc>>,
166 #[serde(default, skip_serializing_if = "Option::is_none")]
167 pub finished_at: Option<DateTime<Utc>>,
168 pub updated_at: DateTime<Utc>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
172#[serde(rename_all = "snake_case")]
173pub enum LibraryPreviewContentFormat {
174 PlainText,
175 Markdown,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
179pub struct LibraryDocumentSectionPreview {
180 pub document_id: i64,
181 pub section_key: String,
182 pub section_label: String,
183 pub sort_order: i32,
184 pub title: String,
185 pub preview_text: String,
186 #[serde(default = "default_preview_content_format")]
187 pub content_format: LibraryPreviewContentFormat,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
191pub struct LibraryFileDetailResponse {
192 pub file_id: Uuid,
193 pub group_key: String,
194 pub project_key: String,
195 pub visibility: Visibility,
196 #[serde(default)]
197 pub folder_id: Option<Uuid>,
198 pub folder_path: String,
199 pub filename: String,
200 pub media_type: String,
201 pub size_bytes: i64,
202 pub sha256: String,
203 pub ingest_status: LibraryIngestStatus,
204 #[serde(default, skip_serializing_if = "Option::is_none")]
205 pub error_message: Option<String>,
206 pub created_at: DateTime<Utc>,
207 pub updated_at: DateTime<Utc>,
208 #[serde(default, skip_serializing_if = "Option::is_none")]
209 pub ingested_at: Option<DateTime<Utc>>,
210 pub sections: Vec<LibraryDocumentSectionPreview>,
211 pub jobs: Vec<LibraryIngestJobResponse>,
212}
213
214#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
215pub struct LibraryUploadResponse {
216 pub files: Vec<LibraryFileSummary>,
217 pub jobs: Vec<LibraryIngestJobResponse>,
218}
219
220fn default_preview_content_format() -> LibraryPreviewContentFormat {
221 LibraryPreviewContentFormat::PlainText
222}
223
224fn default_metadata_json() -> Value {
225 json!({})
226}