proxy downloads

This commit is contained in:
2025-11-09 12:01:19 +01:00
parent d24d2c9249
commit 81571e4c64
9 changed files with 383 additions and 71 deletions
+44 -29
View File
@@ -20,10 +20,11 @@ use uuid::Uuid;
use crate::documents::{
asset::{
build_download_path, derive_document_title, filename_with_retained_extension,
load_asset_responses_with_conn, load_primary_assets, to_asset_detail_response,
to_asset_object_response, to_version_response, DocumentAssetDetailResponse,
DocumentAssetResponse, DocumentVersionDetailResponse, DocumentVersionResponse,
asset_object_disposition, build_download_path, derive_document_title,
filename_with_retained_extension, load_asset_responses_with_conn, load_primary_assets,
to_asset_detail_response, to_asset_object_response, to_version_response,
DocumentAssetDetailResponse, DocumentAssetResponse, DocumentVersionDetailResponse,
DocumentVersionResponse,
},
correspondents::{
insert_document_correspondents, normalize_correspondent_ids, DocumentCorrespondentResponse,
@@ -905,6 +906,7 @@ impl<'a> DocumentsService<'a> {
&self,
mut conn: PgPooledConnection,
tenant_id: Uuid,
user_id: Uuid,
asset_id: Uuid,
start: i32,
limit: i32,
@@ -945,25 +947,43 @@ impl<'a> DocumentsService<'a> {
.checked_add((PRESIGNED_URL_EXPIRY_SECONDS as i64) * 1000)
.ok_or_else(|| AppError::internal("failed to compute expiry timestamp"))?;
let storage = self.state.storage_for_tenant(tenant_id)?;
let storage = (!self.state.config.proxy_downloads)
.then(|| self.state.storage_for_tenant(tenant_id))
.transpose()?;
let mut object_responses = Vec::with_capacity(objects.len());
for object in objects {
let response_disposition = presign_disposition_for_asset(&asset, &object);
let response_disposition = asset_object_disposition(&asset, &object);
let url = storage
.presign_get_object(
&object.s3_key,
Duration::from_secs(PRESIGNED_URL_EXPIRY_SECONDS),
response_disposition.as_deref(),
)
.await
.storage_context("failed to generate asset URL")?;
let (url, object_expires_at) = if self.state.config.proxy_downloads {
let token = self
.state
.jwt
.generate_asset_download_token(asset.id, object.id, user_id, tenant_id)
.map_err(|err| {
error!(error = ?err, "failed to issue asset download token");
AppError::internal("failed to issue asset download token")
})?;
(format!("/download/{token}"), None)
} else {
let storage = storage
.as_ref()
.expect("storage preloaded when proxy disabled");
let url = storage
.presign_get_object(
&object.s3_key,
Duration::from_secs(PRESIGNED_URL_EXPIRY_SECONDS),
response_disposition.as_deref(),
)
.await
.storage_context("failed to generate asset URL")?;
(url, Some(expires_at))
};
object_responses.push(to_asset_object_response(
object,
Some(url),
Some(expires_at),
object_expires_at,
));
}
@@ -1008,7 +1028,7 @@ impl<'a> DocumentsService<'a> {
.first(conn)?;
let assets = load_asset_responses_with_conn(conn, tenant_id, version.id)?;
let download_path = build_download_path(self.state, &document, user_id)?;
let download_path = build_download_path(self.state, &document, version.id, user_id)?;
let version_core = to_version_response(version);
Ok(DocumentVersionDetailResponse {
@@ -1326,11 +1346,14 @@ impl<'a> DocumentsService<'a> {
current_version: Option<(DocumentVersionResponse, Vec<DocumentAssetResponse>)>,
) -> AppResult<DocumentResponse> {
let current_version = match current_version {
Some((version, assets)) => Some(DocumentVersionDetailResponse {
version,
assets,
download_path: build_download_path(self.state, &doc, user_id)?,
}),
Some((version, assets)) => {
let download_path = build_download_path(self.state, &doc, version.id, user_id)?;
Some(DocumentVersionDetailResponse {
version,
assets,
download_path,
})
}
None => None,
};
@@ -1466,11 +1489,3 @@ impl<'a> DocumentsService<'a> {
Ok(Some(detail))
}
}
fn presign_disposition_for_asset(
asset: &DocumentAsset,
object: &DocumentAssetObject,
) -> Option<String> {
let filename = format!("{}-{}", asset.asset_type, object.ordinal);
inline_content_disposition(&filename)
}