Skip to main content

context69_contracts/
library.rs

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}