121 lines
3.2 KiB
Rust
121 lines
3.2 KiB
Rust
use std::path::Path as FsPath;
|
|
|
|
use uuid::Uuid;
|
|
|
|
use crate::error::{AppError, AppResult};
|
|
use crate::models::{Document, DocumentAsset, DocumentAssetObject, DocumentVersion};
|
|
use crate::state::AppState;
|
|
use crate::utils::time::to_iso;
|
|
|
|
use super::{
|
|
DocumentAssetDetailResponse, DocumentAssetObjectResponse, DocumentAssetResponse,
|
|
DocumentVersionResponse,
|
|
};
|
|
|
|
pub fn build_download_path(
|
|
state: &AppState,
|
|
document: &Document,
|
|
user_id: Uuid,
|
|
) -> AppResult<String> {
|
|
state
|
|
.jwt
|
|
.generate_download_token(document.id, user_id, document.tenant_id)
|
|
.map(|token| format!("/download/{token}"))
|
|
.map_err(|err| AppError::internal(format!("failed to generate download token: {err}")))
|
|
}
|
|
|
|
pub fn to_version_response(
|
|
version: DocumentVersion,
|
|
include_operations_summary: bool,
|
|
) -> DocumentVersionResponse {
|
|
DocumentVersionResponse {
|
|
id: version.id,
|
|
version_number: version.version_number,
|
|
s3_key: version.s3_key,
|
|
size_bytes: version.size_bytes,
|
|
checksum: version.checksum,
|
|
created_at: to_iso(version.created_at),
|
|
metadata: version.metadata,
|
|
operations_summary: if include_operations_summary {
|
|
Some(version.operations_summary)
|
|
} else {
|
|
None
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn to_asset_summary(asset: DocumentAsset) -> DocumentAssetResponse {
|
|
DocumentAssetResponse {
|
|
id: asset.id,
|
|
asset_type: asset.asset_type,
|
|
mime_type: asset.mime_type,
|
|
metadata: asset.metadata,
|
|
cardinality: asset.cardinality,
|
|
}
|
|
}
|
|
|
|
pub fn to_asset_detail_response(
|
|
asset: DocumentAsset,
|
|
objects: Vec<DocumentAssetObjectResponse>,
|
|
) -> DocumentAssetDetailResponse {
|
|
DocumentAssetDetailResponse {
|
|
id: asset.id,
|
|
asset_type: asset.asset_type,
|
|
mime_type: asset.mime_type,
|
|
metadata: asset.metadata,
|
|
created_at: to_iso(asset.created_at),
|
|
cardinality: asset.cardinality,
|
|
objects,
|
|
}
|
|
}
|
|
|
|
pub fn to_asset_object_response(
|
|
object: DocumentAssetObject,
|
|
url: Option<String>,
|
|
expires_at: Option<i64>,
|
|
) -> DocumentAssetObjectResponse {
|
|
DocumentAssetObjectResponse {
|
|
id: object.id,
|
|
ordinal: object.ordinal,
|
|
metadata: object.metadata,
|
|
url,
|
|
expires_at,
|
|
}
|
|
}
|
|
|
|
pub fn derive_document_title(original: &str) -> String {
|
|
let trimmed = original.trim();
|
|
if trimmed.is_empty() {
|
|
return "Document".to_string();
|
|
}
|
|
|
|
let stem = FsPath::new(trimmed)
|
|
.file_stem()
|
|
.and_then(|s| s.to_str())
|
|
.map(|s| s.trim())
|
|
.filter(|s| !s.is_empty())
|
|
.map(|s| s.to_string());
|
|
|
|
stem.unwrap_or_else(|| trimmed.to_string())
|
|
}
|
|
|
|
pub fn filename_with_retained_extension(title: &str, current_filename: &str) -> String {
|
|
let extension = FsPath::new(current_filename)
|
|
.extension()
|
|
.and_then(|ext| ext.to_str());
|
|
|
|
if let Some(ext) = extension {
|
|
if title
|
|
.rsplit_once('.')
|
|
.map(|(_, existing_ext)| existing_ext.eq_ignore_ascii_case(ext))
|
|
.unwrap_or(false)
|
|
{
|
|
title.to_string()
|
|
} else {
|
|
format!("{title}.{ext}")
|
|
}
|
|
} else {
|
|
title.to_string()
|
|
}
|
|
}
|