From 9060905e7a8785dddd8570e6c167b96d89422334 Mon Sep 17 00:00:00 2001 From: Nils Schneider Date: Mon, 13 Oct 2025 14:49:37 +0200 Subject: [PATCH] assets --- backend/src/routes/documents.rs | 98 +++++++++++++++++++++------------ backend/src/routes/folders.rs | 18 ++---- frontend/src/index.jsx | 19 +++++-- 3 files changed, 79 insertions(+), 56 deletions(-) diff --git a/backend/src/routes/documents.rs b/backend/src/routes/documents.rs index a8b7033..dc53ebf 100644 --- a/backend/src/routes/documents.rs +++ b/backend/src/routes/documents.rs @@ -77,6 +77,20 @@ impl From for TagResponse { } } +#[derive(Serialize, Clone, Default)] +pub struct DocumentAssetGroupResponse { + #[serde(skip_serializing_if = "Option::is_none")] + pub thumbnail: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub preview: Option, +} + +impl DocumentAssetGroupResponse { + pub fn is_empty(&self) -> bool { + self.thumbnail.is_none() && self.preview.is_none() + } +} + #[derive(Serialize)] pub struct DocumentResponse { pub id: Uuid, @@ -92,8 +106,8 @@ pub struct DocumentResponse { pub issued_at: Option, pub metadata: Value, pub tags: Vec, - pub thumbnail: Option, - pub preview: Option, + #[serde(default, skip_serializing_if = "DocumentAssetGroupResponse::is_empty")] + pub assets: DocumentAssetGroupResponse, pub download_path: String, } @@ -119,11 +133,7 @@ pub struct DocumentAssetResponse { pub created_at: String, } -#[derive(Clone, Default)] -pub struct PrimaryDocumentAssets { - pub thumbnail: Option, - pub preview: Option, -} +type PrimaryDocumentAssets = DocumentAssetGroupResponse; #[derive(Serialize)] pub struct DocumentDetailResponse { @@ -251,18 +261,13 @@ pub async fn list_documents( let mut response = Vec::with_capacity(doc_ids.len()); for doc in docs { let tags = tags_map.get(&doc.id).cloned(); - let (thumbnail, preview) = if let Some(assets) = primary_assets.get(&doc.id) { - (assets.thumbnail.clone(), assets.preview.clone()) - } else { - (None, None) - }; + let assets = primary_assets.get(&doc.id).cloned(); response.push(to_document_response( &state, user.user_id, doc, tags, - thumbnail, - preview, + assets, )?); } @@ -291,14 +296,21 @@ pub async fn get_document( drop(conn); let assets = load_asset_responses(&state, version_id).await?; - let thumbnail = assets + let mut grouped_assets = DocumentAssetGroupResponse::default(); + if let Some(asset) = assets .iter() .find(|asset| asset.asset_type == "thumbnail") - .cloned(); - let preview = assets + .cloned() + { + grouped_assets.thumbnail = Some(asset); + } + if let Some(asset) = assets .iter() .find(|asset| asset.asset_type == "preview") - .cloned(); + .cloned() + { + grouped_assets.preview = Some(asset); + } Ok(Json(DocumentDetailResponse { document: to_document_response( @@ -306,8 +318,7 @@ pub async fn get_document( user.user_id, doc, tags_map.get(&document_id).cloned(), - thumbnail, - preview, + Some(grouped_assets), )?, current_version: to_version_response(current_version), assets, @@ -695,14 +706,21 @@ pub async fn update_document( drop(conn); let assets = load_asset_responses(&state, version_id).await?; - let thumbnail = assets + let mut grouped_assets = DocumentAssetGroupResponse::default(); + if let Some(asset) = assets .iter() .find(|asset| asset.asset_type == "thumbnail") - .cloned(); - let preview = assets + .cloned() + { + grouped_assets.thumbnail = Some(asset); + } + if let Some(asset) = assets .iter() .find(|asset| asset.asset_type == "preview") - .cloned(); + .cloned() + { + grouped_assets.preview = Some(asset); + } Ok(Json(DocumentDetailResponse { document: to_document_response( @@ -710,8 +728,7 @@ pub async fn update_document( user.user_id, document, tags_map.get(&document_id).cloned(), - thumbnail, - preview, + Some(grouped_assets), )?, current_version: to_version_response(current_version), assets, @@ -991,14 +1008,21 @@ async fn process_upload( let tags = tags_map.get(&document.id).cloned(); drop(conn); let assets = load_asset_responses(state, version.id).await?; - let thumbnail = assets + let mut grouped_assets = DocumentAssetGroupResponse::default(); + if let Some(asset) = assets .iter() .find(|asset| asset.asset_type == "thumbnail") - .cloned(); - let preview = assets + .cloned() + { + grouped_assets.thumbnail = Some(asset); + } + if let Some(asset) = assets .iter() .find(|asset| asset.asset_type == "preview") - .cloned(); + .cloned() + { + grouped_assets.preview = Some(asset); + } info!( document_id = %document.id, @@ -1009,7 +1033,11 @@ async fn process_upload( return Ok(UploadOutcome { detail: DocumentDetailResponse { document: to_document_response( - state, user_id, document, tags, thumbnail, preview, + state, + user_id, + document, + tags, + Some(grouped_assets), )?, current_version: to_version_response(version), assets, @@ -1081,7 +1109,7 @@ async fn process_upload( }; let detail = DocumentDetailResponse { - document: to_document_response(state, user_id, document, None, None, None)?, + document: to_document_response(state, user_id, document, None, None)?, current_version: to_version_response(version.clone()), assets: Vec::new(), }; @@ -1255,8 +1283,7 @@ pub(crate) fn to_document_response( user_id: Uuid, doc: Document, tags: Option>, - thumbnail: Option, - preview: Option, + assets: Option, ) -> AppResult { let download_path = build_download_path(state, doc.id, user_id)?; @@ -1278,8 +1305,7 @@ pub(crate) fn to_document_response( .into_iter() .map(TagResponse::from) .collect(), - thumbnail, - preview, + assets: assets.unwrap_or_default(), download_path, }) } diff --git a/backend/src/routes/folders.rs b/backend/src/routes/folders.rs index bc365f6..170b0e0 100644 --- a/backend/src/routes/folders.rs +++ b/backend/src/routes/folders.rs @@ -220,18 +220,13 @@ pub async fn list_folder_contents( let mut documents = Vec::with_capacity(doc_ids.len()); for doc in docs { let tags = tags_map.get(&doc.id).cloned(); - let (thumbnail, preview) = if let Some(assets) = primary_assets.get(&doc.id) { - (assets.thumbnail.clone(), assets.preview.clone()) - } else { - (None, None) - }; + let assets = primary_assets.get(&doc.id).cloned(); documents.push(to_document_response( &state, user.user_id, doc, tags, - thumbnail, - preview, + assets, )?); } @@ -408,18 +403,13 @@ pub async fn search_documents( let mut response = Vec::with_capacity(doc_ids.len()); for doc in docs { let tags = tags_map.get(&doc.id).cloned(); - let (thumbnail, preview) = if let Some(assets) = primary_assets.get(&doc.id) { - (assets.thumbnail.clone(), assets.preview.clone()) - } else { - (None, None) - }; + let assets = primary_assets.get(&doc.id).cloned(); response.push(to_document_response( &state, user.user_id, doc, tags, - thumbnail, - preview, + assets, )?); } diff --git a/frontend/src/index.jsx b/frontend/src/index.jsx index baa83bd..3bb5321 100644 --- a/frontend/src/index.jsx +++ b/frontend/src/index.jsx @@ -731,6 +731,8 @@ const DocumentsTable = ({ rowClasses.push('dragging'); } + const thumbnailAsset = doc?.assets?.thumbnail || null; + return ( - {doc.thumbnail ? ( + {thumbnailAsset ? ( {`Thumbnail @@ -1029,11 +1031,16 @@ const DetailPanel = ({ const makePreviewItem = useCallback((doc, detailEntry, fallbackUrl = null) => { if (!doc) return null; - const url = doc.thumbnail?.url || detailEntry?.document?.thumbnail?.url || fallbackUrl; + const detailDocument = detailEntry?.document || null; + const thumbnailAsset = + doc?.assets?.thumbnail || + detailDocument?.assets?.thumbnail || + detailEntry?.assets?.find((item) => item.asset_type === 'thumbnail') || + null; + const url = thumbnailAsset?.url || fallbackUrl; if (!url) return null; - const asset = detailEntry?.assets?.find((item) => item.asset_type === 'thumbnail'); - const width = asset?.width || 0; - const height = asset?.height || 0; + const width = thumbnailAsset?.width || 0; + const height = thumbnailAsset?.height || 0; const orientation = width > 0 && height > 0 ? (width >= height ? 'landscape' : 'portrait') : 'landscape'; return { id: doc.id,