use serde::{Deserialize, Serialize}; use serde_json::Value; use utoipa::{IntoParams, OpenApi, ToSchema}; use uuid::Uuid; #[derive(OpenApi)] #[openapi( paths( doc::health_check, doc::login, doc::refresh, doc::logout, doc::me, doc::list_tenants, doc::select_tenant, doc::list_documents, doc::check_document, doc::upload_document, doc::get_document, doc::update_document, doc::delete_document, doc::download_with_token, doc::move_document, doc::assign_tags, doc::remove_tag, doc::bulk_move_documents, doc::bulk_update_tags, doc::bulk_assign_correspondents, doc::assign_correspondents, doc::remove_correspondent, doc::reanalyze_selected_documents, doc::list_document_assets, doc::request_document_assets, doc::get_document_asset, doc::create_folder, doc::ensure_folder_path, doc::get_folder, doc::list_folder_contents, doc::delete_folder, doc::update_folder, doc::list_tags, doc::create_tag, doc::update_tag, doc::delete_tag, doc::list_correspondents, doc::create_correspondent, doc::update_correspondent, doc::delete_correspondent, doc::list_webdav_tokens, doc::create_webdav_token, doc::delete_webdav_token, ), components( schemas( schemas::LoginRequest, schemas::AccessTokenResponse, schemas::TenantSnippet, schemas::TenantSelectionResponse, schemas::TenantSelectionRequest, schemas::TenantListResponse, schemas::LoginResponseVariants, schemas::DocumentResponse, schemas::DocumentDetailResponse, schemas::DocumentVersionResponse, schemas::DocumentVersionDetailResponse, schemas::DocumentAssetSummary, schemas::DocumentAssetDetail, schemas::DocumentAssetObject, schemas::DocumentCorrespondent, schemas::DocumentTag, schemas::UpdateDocumentRequest, schemas::BulkMoveDocumentsRequest, schemas::BulkMoveDocumentsResponse, schemas::AssignTagsRequest, schemas::MoveDocumentRequest, schemas::BulkTagRequest, schemas::BulkTagResponse, schemas::CorrespondentAssignment, schemas::BulkTagAction, schemas::BulkCorrespondentsRequest, schemas::BulkCorrespondentsResponse, schemas::BulkCorrespondentAction, schemas::AssignCorrespondentsRequest, schemas::ReanalyzeRequest, schemas::ReanalyzeResponse, schemas::DocumentAssetRequestParams, schemas::AssetObjectsQuery, schemas::DocumentCheckQuery, schemas::DocumentCheckResponse, schemas::UploadDocumentForm, schemas::CreateFolderRequest, schemas::EnsureFolderPathRequest, schemas::FolderResponse, schemas::FolderInfo, schemas::FolderContentsResponse, schemas::FolderDocumentSummary, schemas::UpdateFolderRequest, schemas::FolderContentsParams, schemas::TagCatalogEntry, schemas::CreateTagRequest, schemas::UpdateTagRequest, schemas::CorrespondentUsage, schemas::CorrespondentCatalogEntry, schemas::CreateCorrespondentRequest, schemas::UpdateCorrespondentRequest, schemas::WebdavTokenResponse, schemas::WebdavTokenCreatedResponse, schemas::CreateWebdavTokenRequest, ) ), tags( (name = "Health", description = "Service health"), (name = "Auth", description = "Authentication"), (name = "Documents", description = "Document management"), (name = "Assets", description = "Document assets"), (name = "Folders", description = "Folder management"), (name = "Tags", description = "Tag catalog"), (name = "Correspondents", description = "Correspondent catalog"), (name = "Profile", description = "User profile and WebDAV tokens") ) )] pub struct ApiDoc; #[allow(dead_code)] mod doc { use super::schemas::*; use uuid::Uuid; #[allow(dead_code)] fn __keep_uuid_import() { let _ = Uuid::nil(); } #[utoipa::path( get, path = "/api/health", responses((status = 200, description = "Service is healthy")), tag = "Health" )] pub(super) fn health_check() {} #[utoipa::path( post, path = "/api/auth/login", request_body = LoginRequest, responses( (status = 200, description = "Login succeeded", body = LoginResponseVariants), (status = 401, description = "Invalid credentials") ), tag = "Auth" )] pub(super) fn login() {} #[utoipa::path( post, path = "/api/auth/refresh", responses( (status = 200, description = "Refreshed access token", body = AccessTokenResponse), (status = 401, description = "Missing or invalid refresh token") ), tag = "Auth" )] pub(super) fn refresh() {} #[utoipa::path( post, path = "/api/auth/logout", responses((status = 204, description = "Session revoked")), tag = "Auth" )] pub(super) fn logout() {} #[utoipa::path( get, path = "/api/auth/me", responses((status = 200, description = "Authenticated principal", body = AccessTokenResponse)), tag = "Auth" )] pub(super) fn me() {} #[utoipa::path( get, path = "/api/auth/tenants", responses((status = 200, description = "Available tenants", body = TenantListResponse)), tag = "Auth" )] pub(super) fn list_tenants() {} #[utoipa::path( post, path = "/api/auth/select-tenant", request_body = TenantSelectionRequest, responses((status = 200, description = "Tenant selected", body = AccessTokenResponse)), tag = "Auth" )] pub(super) fn select_tenant() {} #[utoipa::path( get, path = "/api/documents", params(DocumentListQuery), responses((status = 200, description = "List documents", body = [DocumentResponse])), tag = "Documents" )] pub(super) fn list_documents() {} #[utoipa::path( post, path = "/api/documents", request_body = UploadDocumentForm, responses( (status = 201, description = "Document created", body = DocumentDetailResponse), (status = 200, description = "Existing document reused", body = DocumentDetailResponse), (status = 204, description = "Upload skipped because the document already exists") ), tag = "Documents" )] pub(super) fn upload_document() {} #[utoipa::path( get, path = "/api/documents/check", params(DocumentCheckQuery), responses((status = 200, description = "Checksum lookup", body = DocumentCheckResponse)), tag = "Documents" )] pub(super) fn check_document() {} #[utoipa::path( get, path = "/api/documents/{id}", params(("id" = Uuid, Path, description = "Document ID")), responses((status = 200, description = "Document detail", body = DocumentDetailResponse)), tag = "Documents" )] pub(super) fn get_document() {} #[utoipa::path( patch, path = "/api/documents/{id}", params(("id" = Uuid, Path, description = "Document ID")), request_body = UpdateDocumentRequest, responses((status = 200, description = "Updated document", body = DocumentDetailResponse)), tag = "Documents" )] pub(super) fn update_document() {} #[utoipa::path( delete, path = "/api/documents/{id}", params(("id" = Uuid, Path, description = "Document ID")), responses((status = 204, description = "Document deleted")), tag = "Documents" )] pub(super) fn delete_document() {} #[utoipa::path( get, path = "/api/documents/{id}/versions", params(("id" = Uuid, Path, description = "Document ID")), responses((status = 200, description = "Document versions", body = [DocumentVersionResponse])), tag = "Documents" )] pub(super) fn list_document_versions() {} #[utoipa::path( get, path = "/api/documents/{id}/versions/{version_id}", params( ("id" = Uuid, Path, description = "Document ID"), ("version_id" = Uuid, Path, description = "Version ID"), ), responses((status = 200, description = "Document version detail", body = DocumentVersionDetailResponse)), tag = "Documents" )] pub(super) fn get_document_version() {} #[utoipa::path( post, path = "/api/documents/{id}/restore", params(("id" = Uuid, Path, description = "Document ID")), request_body = RestoreDocumentRequest, responses((status = 204, description = "Document restored")), tag = "Documents" )] pub(super) fn restore_document() {} #[utoipa::path( get, path = "/download/{token}", params(("token" = String, Path, description = "Download token")), responses((status = 302, description = "Redirect to pre-signed URL")), tag = "Documents" )] pub(super) fn download_with_token() {} #[utoipa::path( patch, path = "/api/documents/{id}/folder", params(("id" = Uuid, Path, description = "Document ID")), request_body = MoveDocumentRequest, responses((status = 204, description = "Document moved")), tag = "Documents" )] pub(super) fn move_document() {} #[utoipa::path( post, path = "/api/documents/{id}/tags", params(("id" = Uuid, Path, description = "Document ID")), request_body = AssignTagsRequest, responses((status = 204, description = "Tags assigned")), tag = "Documents" )] pub(super) fn assign_tags() {} #[utoipa::path( delete, path = "/api/documents/{id}/tags/{tag_id}", params( ("id" = Uuid, Path, description = "Document ID"), ("tag_id" = Uuid, Path, description = "Tag ID") ), responses((status = 204, description = "Tag removed")), tag = "Documents" )] pub(super) fn remove_tag() {} #[utoipa::path( post, path = "/api/documents/bulk/move", request_body = BulkMoveDocumentsRequest, responses((status = 200, description = "Bulk move outcome", body = BulkMoveDocumentsResponse)), tag = "Documents" )] pub(super) fn bulk_move_documents() {} #[utoipa::path( post, path = "/api/documents/bulk/tags", request_body = BulkTagRequest, responses((status = 200, description = "Bulk tag outcome", body = BulkTagResponse)), tag = "Documents" )] pub(super) fn bulk_update_tags() {} #[utoipa::path( post, path = "/api/documents/bulk/correspondents", request_body = BulkCorrespondentsRequest, responses((status = 200, description = "Bulk correspondents outcome", body = BulkCorrespondentsResponse)), tag = "Documents" )] pub(super) fn bulk_assign_correspondents() {} #[utoipa::path( post, path = "/api/documents/{id}/correspondents", params(("id" = Uuid, Path, description = "Document ID")), request_body = AssignCorrespondentsRequest, responses((status = 204, description = "Correspondents assigned")), tag = "Documents" )] pub(super) fn assign_correspondents() {} #[utoipa::path( delete, path = "/api/documents/{id}/correspondents/{correspondent_id}", params( ("id" = Uuid, Path, description = "Document ID"), ("correspondent_id" = Uuid, Path, description = "Correspondent ID") ), responses((status = 204, description = "Correspondent removed")), tag = "Documents" )] pub(super) fn remove_correspondent() {} #[utoipa::path( post, path = "/api/documents/bulk/reanalyze", request_body = ReanalyzeRequest, responses((status = 200, description = "Reanalyze queued", body = ReanalyzeResponse)), tag = "Documents" )] pub(super) fn reanalyze_selected_documents() {} #[utoipa::path( get, path = "/api/documents/{id}/assets", params(("id" = Uuid, Path, description = "Document ID")), responses((status = 200, description = "Document assets", body = [DocumentAssetSummary])), tag = "Assets" )] pub(super) fn list_document_assets() {} #[utoipa::path( post, path = "/api/documents/{id}/assets", params( ("id" = Uuid, Path, description = "Document ID"), DocumentAssetRequestParams ), responses((status = 202, description = "Asset generation requested")), tag = "Assets" )] pub(super) fn request_document_assets() {} #[utoipa::path( get, path = "/api/assets/{asset_id}", params( ("asset_id" = Uuid, Path, description = "Asset ID"), AssetObjectsQuery ), responses((status = 200, description = "Asset detail", body = DocumentAssetDetail)), tag = "Assets" )] pub(super) fn get_document_asset() {} #[utoipa::path( post, path = "/api/folders", request_body = CreateFolderRequest, responses((status = 200, description = "Folder created", body = FolderResponse)), tag = "Folders" )] pub(super) fn create_folder() {} #[utoipa::path( post, path = "/api/folders/path", request_body = EnsureFolderPathRequest, responses((status = 200, description = "Folder path ensured", body = FolderResponse)), tag = "Folders" )] pub(super) fn ensure_folder_path() {} #[utoipa::path( get, path = "/api/folders/{id}", params(("id" = Uuid, Path, description = "Folder ID")), responses((status = 200, description = "Folder detail", body = FolderResponse)), tag = "Folders" )] pub(super) fn get_folder() {} #[utoipa::path( get, path = "/api/folders/{id}/contents", params( ("id" = Uuid, Path, description = "Folder ID"), FolderContentsParams ), responses((status = 200, description = "Folder contents", body = FolderContentsResponse)), tag = "Folders" )] pub(super) fn list_folder_contents() {} #[utoipa::path( delete, path = "/api/folders/{id}", params(("id" = Uuid, Path, description = "Folder ID")), responses((status = 204, description = "Folder deleted")), tag = "Folders" )] pub(super) fn delete_folder() {} #[utoipa::path( patch, path = "/api/folders/{id}", params(("id" = Uuid, Path, description = "Folder ID")), request_body = UpdateFolderRequest, responses((status = 204, description = "Folder updated")), tag = "Folders" )] pub(super) fn update_folder() {} #[utoipa::path( get, path = "/api/tags", responses((status = 200, description = "Tags", body = [TagCatalogEntry])), tag = "Tags" )] pub(super) fn list_tags() {} #[utoipa::path( post, path = "/api/tags", request_body = CreateTagRequest, responses((status = 200, description = "Tag created", body = TagCatalogEntry)), tag = "Tags" )] pub(super) fn create_tag() {} #[utoipa::path( patch, path = "/api/tags/{id}", params(("id" = Uuid, Path, description = "Tag ID")), request_body = UpdateTagRequest, responses((status = 200, description = "Tag updated", body = TagCatalogEntry)), tag = "Tags" )] pub(super) fn update_tag() {} #[utoipa::path( delete, path = "/api/tags/{id}", params(("id" = Uuid, Path, description = "Tag ID")), responses((status = 204, description = "Tag deleted")), tag = "Tags" )] pub(super) fn delete_tag() {} #[utoipa::path( get, path = "/api/correspondents", responses((status = 200, description = "Correspondents", body = [CorrespondentCatalogEntry])), tag = "Correspondents" )] pub(super) fn list_correspondents() {} #[utoipa::path( post, path = "/api/correspondents", request_body = CreateCorrespondentRequest, responses((status = 200, description = "Correspondent created", body = CorrespondentCatalogEntry)), tag = "Correspondents" )] pub(super) fn create_correspondent() {} #[utoipa::path( patch, path = "/api/correspondents/{id}", params(("id" = Uuid, Path, description = "Correspondent ID")), request_body = UpdateCorrespondentRequest, responses((status = 200, description = "Correspondent updated", body = CorrespondentCatalogEntry)), tag = "Correspondents" )] pub(super) fn update_correspondent() {} #[utoipa::path( delete, path = "/api/correspondents/{id}", params(("id" = Uuid, Path, description = "Correspondent ID")), responses((status = 204, description = "Correspondent deleted")), tag = "Correspondents" )] pub(super) fn delete_correspondent() {} #[utoipa::path( get, path = "/api/profile/webdav-tokens", responses((status = 200, description = "List WebDAV tokens", body = [WebdavTokenResponse])), tag = "Profile" )] pub(super) fn list_webdav_tokens() {} #[utoipa::path( post, path = "/api/profile/webdav-tokens", request_body = CreateWebdavTokenRequest, responses((status = 201, description = "WebDAV token created", body = WebdavTokenCreatedResponse)), tag = "Profile" )] pub(super) fn create_webdav_token() {} #[utoipa::path( delete, path = "/api/profile/webdav-tokens/{id}", params(("id" = Uuid, Path, description = "WebDAV token ID")), responses((status = 204, description = "WebDAV token revoked")), tag = "Profile" )] pub(super) fn delete_webdav_token() {} } #[cfg(test)] mod tests { use super::ApiDoc; use utoipa::OpenApi; #[test] fn openapi_serializes() { let spec = ApiDoc::openapi(); let _ = serde_json::to_string(&spec).expect("serialize openapi"); } } pub mod schemas { use super::*; #[derive(Serialize, Deserialize, ToSchema)] pub struct LoginRequest { pub username: String, pub password: String, #[schema(nullable)] pub preferred_tenant_slug: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct AccessTokenResponse { pub access_token: String, pub token_type: String, pub expires_in: i64, pub tenant: TenantSnippet, } #[derive(Serialize, Deserialize, ToSchema)] pub struct TenantSnippet { pub id: Uuid, pub slug: String, } #[derive(Serialize, Deserialize, ToSchema)] pub struct TenantSelectionResponse { pub access_token: String, pub tenants: Vec, } #[derive(Serialize, Deserialize, ToSchema)] pub struct TenantSelectionRequest { pub tenant_id: Uuid, } #[derive(Serialize, Deserialize, ToSchema)] pub struct TenantListResponse { pub tenants: Vec, } #[derive(Serialize, Deserialize, ToSchema)] #[serde(untagged)] pub enum LoginResponseVariants { Token(AccessTokenResponse), Selection(TenantSelectionResponse), } #[derive(Serialize, Deserialize, IntoParams, ToSchema)] #[into_params(parameter_in = Query)] pub struct DocumentListQuery { pub folder_id: Option, #[schema(nullable)] pub include_descendants: Option, pub query: Option, pub tags: Option, pub correspondents: Option, #[serde(default = "default_document_status_filter")] #[schema(default = "active")] pub status: DocumentStatusFilter, } fn default_document_status_filter() -> DocumentStatusFilter { DocumentStatusFilter::Active } #[derive(Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum DocumentStatusFilter { Active, Deleted, All, } #[derive(Serialize, Deserialize, ToSchema)] pub struct DocumentTag { pub id: Uuid, pub label: String, #[schema(nullable)] pub color: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct DocumentAssetObject { pub id: Uuid, pub ordinal: i32, pub metadata: Value, #[schema(nullable)] pub url: Option, #[schema(nullable)] pub expires_at: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct DocumentAssetSummary { pub id: Uuid, pub asset_type: String, pub mime_type: String, pub metadata: Value, #[schema(nullable)] pub cardinality: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct DocumentAssetDetail { pub id: Uuid, pub asset_type: String, pub mime_type: String, pub metadata: Value, pub created_at: String, #[schema(nullable)] pub cardinality: Option, pub objects: Vec, } #[derive(Serialize, Deserialize, ToSchema)] pub struct DocumentVersionResponse { pub id: Uuid, pub version_number: i32, pub size_bytes: i64, pub checksum: String, pub created_at: String, pub metadata: Value, } #[derive(Serialize, Deserialize, ToSchema)] pub struct DocumentVersionDetailResponse { #[serde(flatten)] pub version: DocumentVersionResponse, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub assets: Vec, pub download_path: String, } #[derive(Serialize, Deserialize, ToSchema)] pub struct DocumentCorrespondent { pub id: Uuid, pub name: String, pub metadata: Value, pub assigned_at: String, } #[derive(Serialize, Deserialize, ToSchema)] pub struct DocumentResponse { pub id: Uuid, pub filename: String, pub title: String, pub original_name: String, #[schema(nullable)] pub content_type: Option, #[schema(nullable)] pub folder_id: Option, pub uploaded_at: String, pub updated_at: String, #[schema(nullable)] pub deleted_at: Option, #[schema(nullable)] pub issued_at: Option, pub metadata: Value, pub tags: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub correspondents: Vec, #[schema(nullable)] pub current_version: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct DocumentDetailResponse { pub document: DocumentResponse, } #[derive(Serialize, Deserialize, ToSchema)] pub struct DocumentMetadataUpdate { pub value: Value, #[serde(default)] #[schema(default = false)] pub replace: bool, } #[derive(Serialize, Deserialize, ToSchema)] pub struct RestoreDocumentRequest { #[schema(nullable)] pub folder_id: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct UpdateDocumentRequest { #[schema(nullable)] pub title: Option, #[schema(nullable, value_type = Option)] pub issued_at: Option, #[schema(nullable)] pub metadata: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct BulkMoveDocumentsRequest { pub document_ids: Vec, #[schema(nullable)] pub folder_id: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct BulkMoveDocumentsResponse { pub updated: usize, } #[derive(Serialize, Deserialize, ToSchema)] pub struct AssignTagsRequest { pub tag_ids: Vec, } #[derive(Serialize, Deserialize, ToSchema)] pub struct MoveDocumentRequest { #[schema(nullable)] pub folder_id: Option, } #[derive(Serialize, Deserialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum BulkTagAction { Add, Remove, } #[derive(Serialize, Deserialize, ToSchema)] pub struct BulkTagRequest { pub document_ids: Vec, pub tag_ids: Vec, pub action: BulkTagAction, } #[derive(Serialize, Deserialize, ToSchema)] pub struct BulkTagResponse { pub added: usize, pub removed: usize, } #[derive(Serialize, Deserialize, ToSchema)] pub struct CorrespondentAssignment { pub correspondent_id: Uuid, } #[derive(Serialize, Deserialize, ToSchema)] pub struct AssignCorrespondentsRequest { pub assignments: Vec, #[serde(default)] pub replace: bool, } #[derive(Serialize, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] pub enum BulkCorrespondentAction { Add, Remove, } #[derive(Serialize, Deserialize, ToSchema)] pub struct BulkCorrespondentsRequest { pub document_ids: Vec, pub assignments: Vec, #[serde(default = "default_bulk_correspondent_action")] pub action: BulkCorrespondentAction, } fn default_bulk_correspondent_action() -> BulkCorrespondentAction { BulkCorrespondentAction::Add } #[derive(Serialize, Deserialize, ToSchema)] pub struct BulkCorrespondentsResponse { pub assigned: usize, pub removed: usize, } #[derive(Serialize, Deserialize, ToSchema)] pub struct ReanalyzeRequest { pub document_ids: Vec, #[serde(default)] pub force: bool, } #[derive(Serialize, Deserialize, ToSchema)] pub struct ReanalyzeResponse { pub queued: usize, } #[derive(Serialize, Deserialize, IntoParams, ToSchema)] #[into_params(parameter_in = Query)] pub struct DocumentAssetRequestParams { #[serde(default)] pub force: Option, } #[derive(Serialize, Deserialize, IntoParams, ToSchema)] #[into_params(parameter_in = Query)] pub struct AssetObjectsQuery { #[serde(default)] pub start: Option, #[serde(default)] pub limit: Option, } #[derive(Serialize, Deserialize, IntoParams, ToSchema)] #[into_params(parameter_in = Query)] pub struct DocumentCheckQuery { pub checksum: String, } #[derive(Serialize, Deserialize, ToSchema)] pub struct DocumentCheckResponse { pub exists: bool, #[schema(nullable)] pub document_id: Option, #[schema(nullable)] pub title: Option, #[schema(nullable)] pub filename: Option, #[schema(nullable)] pub version_id: Option, #[schema(nullable)] pub version_number: Option, #[schema(nullable)] pub uploaded_at: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct UploadDocumentForm { #[schema(value_type = String, format = Binary)] pub file: String, #[schema(nullable)] pub folder_id: Option, #[schema(nullable)] pub metadata: Option, #[schema(nullable)] pub title: Option, #[schema(nullable, value_type = Vec)] pub tag_ids: Option>, #[schema(nullable, value_type = Vec)] pub correspondents: Option>, #[schema(nullable, example = "2024-01-01T00:00:00Z")] pub issued_at: Option, #[schema(nullable)] pub skip_existing: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct CreateFolderRequest { pub name: String, #[schema(nullable)] pub parent_id: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct EnsureFolderPathRequest { #[schema(nullable)] pub parent_id: Option, pub segments: Vec, } #[derive(Serialize, Deserialize, ToSchema)] pub struct FolderInfo { pub id: Uuid, pub name: String, #[schema(nullable)] pub parent_id: Option, pub created_at: String, pub updated_at: String, } #[derive(Serialize, Deserialize, ToSchema)] pub struct FolderResponse { pub folder: FolderInfo, } #[derive(Serialize, Deserialize, IntoParams, ToSchema)] #[into_params(parameter_in = Query)] pub struct FolderContentsParams { #[serde(default)] pub include_documents: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct FolderDocumentSummary { pub id: Uuid, pub title: String, } #[derive(Serialize, Deserialize, ToSchema)] pub struct FolderContentsResponse { #[schema(nullable)] pub folder: Option, pub subfolders: Vec, pub documents: Vec, } #[derive(Serialize, Deserialize, ToSchema)] pub struct UpdateFolderRequest { #[schema(nullable)] pub parent_id: Option>, #[schema(nullable)] pub name: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct TagCatalogEntry { pub id: Uuid, pub label: String, #[schema(nullable)] pub color: Option, pub usage_count: i64, } #[derive(Serialize, Deserialize, ToSchema)] pub struct CreateTagRequest { pub label: String, #[schema(nullable)] pub color: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct UpdateTagRequest { #[schema(nullable)] pub label: Option>, #[schema(nullable)] pub color: Option>, } #[derive(Serialize, Deserialize, ToSchema)] pub struct CorrespondentUsage { pub total: i64, } #[derive(Serialize, Deserialize, ToSchema)] pub struct CorrespondentCatalogEntry { pub id: Uuid, pub name: String, pub metadata: Value, pub created_at: String, pub updated_at: String, pub usage: CorrespondentUsage, } #[derive(Serialize, Deserialize, ToSchema)] pub struct CreateCorrespondentRequest { pub name: String, #[schema(default, value_type = Object)] pub metadata: Value, } #[derive(Serialize, Deserialize, ToSchema)] pub struct UpdateCorrespondentRequest { #[schema(nullable)] pub name: Option, #[schema(nullable)] pub metadata: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct WebdavTokenResponse { pub id: Uuid, pub tenant_id: Uuid, #[schema(nullable)] pub label: Option, pub created_at: String, #[schema(nullable)] pub last_used_at: Option, #[schema(nullable)] pub expires_at: Option, #[schema(nullable)] pub revoked_at: Option, } #[derive(Serialize, Deserialize, ToSchema)] pub struct WebdavTokenCreatedResponse { pub token: String, pub token_info: WebdavTokenResponse, } #[derive(Serialize, Deserialize, ToSchema)] pub struct CreateWebdavTokenRequest { #[schema(nullable)] pub label: Option, #[schema(nullable, example = "2025-01-01T00:00:00Z")] pub expires_at: Option, } }