reduce to 1 object per asset

This commit is contained in:
2025-11-20 16:54:12 +01:00
parent 42f3917881
commit 3ce6b0ce93
24 changed files with 366 additions and 607 deletions
+25 -44
View File
@@ -16,17 +16,16 @@ use uuid::Uuid;
use crate::auth::{ensure_active_tenant_with_conn, jwt::DownloadSubject, TenantScopedConn};
use crate::documents::asset::{
asset_object_disposition, DocumentAssetDetailResponse, DocumentAssetResponse,
asset_disposition, DocumentAssetDetailResponse, DocumentAssetResponse,
DocumentVersionDetailResponse, DocumentVersionResponse,
};
#[allow(unused_imports)]
use crate::error::ApiErrorResponse;
use crate::error::{AppError, AppResult};
use crate::http::responders::{accepted_json, created_json, no_content, ok_json, JsonResponse};
use crate::models::{Document, DocumentAsset, DocumentAssetObject, DocumentVersion};
use crate::models::{Document, DocumentAsset, DocumentVersion};
use crate::schema::{
document_asset_objects, document_assets, document_versions, documents,
user_sessions::dsl as session_dsl,
document_assets, document_versions, documents, user_sessions::dsl as session_dsl,
};
use crate::services::correspondents::{
AssignCorrespondentsRequest, BulkCorrespondentAction, BulkCorrespondentResponse,
@@ -91,15 +90,6 @@ pub struct RestoreDocumentRequest {
pub folder_id: Option<Uuid>,
}
#[derive(Deserialize, Default, IntoParams, ToSchema)]
#[into_params(parameter_in = Query)]
pub struct AssetObjectsQuery {
#[serde(default)]
pub start: Option<i32>,
#[serde(default)]
pub limit: Option<i32>,
}
#[utoipa::path(
get,
path = "/api/documents",
@@ -467,12 +457,13 @@ pub async fn list_document_assets(
TenantScopedConn {
mut conn,
tenant_id,
user_id,
..
}: TenantScopedConn,
) -> AppResult<JsonResponse<Vec<DocumentAssetResponse>>> {
let service = DocumentsService::new(&state);
let assets = service
.list_document_assets(&mut conn, tenant_id, document_id)
.list_document_assets(&mut conn, tenant_id, user_id, document_id)
.await?;
ok_json(assets)
}
@@ -480,14 +471,13 @@ pub async fn list_document_assets(
#[utoipa::path(
get,
path = "/api/assets/{asset_id}",
params(("asset_id" = Uuid, Path, description = "Asset ID"), AssetObjectsQuery),
params(("asset_id" = Uuid, Path, description = "Asset ID")),
responses((status = 200, description = "Asset detail", body = DocumentAssetDetailResponse)),
tag = "Assets"
)]
pub async fn get_document_asset(
State(state): State<AppState>,
Path(asset_id): Path<Uuid>,
Query(query): Query<AssetObjectsQuery>,
TenantScopedConn {
conn,
tenant_id,
@@ -495,17 +485,9 @@ pub async fn get_document_asset(
..
}: TenantScopedConn,
) -> AppResult<JsonResponse<DocumentAssetDetailResponse>> {
let start = query.start.unwrap_or(1);
let limit = query.limit.unwrap_or(1);
if start < 1 {
return Err(AppError::bad_request("start must be at least 1"));
}
if limit < 1 {
return Err(AppError::bad_request("limit must be at least 1"));
}
let service = DocumentsService::new(&state);
let detail = service
.get_document_asset(conn, tenant_id, user_id, asset_id, start, limit)
.get_document_asset(conn, tenant_id, user_id, asset_id)
.await?;
ok_json(detail)
}
@@ -637,20 +619,7 @@ pub async fn download_with_token(
)
.await
}
DownloadSubject::AssetObject {
asset_id,
object_id,
} => {
if !state.config.proxy_downloads {
return Err(AppError::not_found());
}
let object: DocumentAssetObject = document_asset_objects::table
.find(*object_id)
.filter(document_asset_objects::asset_id.eq(*asset_id))
.filter(document_asset_objects::tenant_id.eq(claims.tenant_id))
.first(&mut conn)?;
DownloadSubject::Asset { asset_id } => {
let asset: DocumentAsset = document_assets::table
.find(*asset_id)
.filter(document_assets::tenant_id.eq(claims.tenant_id))
@@ -659,14 +628,28 @@ pub async fn download_with_token(
drop(conn);
let storage = state.storage_for_tenant(claims.tenant_id)?;
let disposition = asset_object_disposition(&asset, &object);
let disposition = asset_disposition(&asset);
if !state.config.proxy_downloads {
let presigned_url = storage
.presign_get_object(
&asset.s3_key,
Duration::from_secs(PRESIGNED_URL_EXPIRY_SECONDS),
disposition.as_deref(),
)
.await
.storage_context("failed to generate download URL")?;
return Ok(axum::response::Redirect::temporary(&presigned_url).into_response());
}
proxy_storage_object(
storage,
&object.s3_key,
&asset.s3_key,
disposition.as_deref(),
headers.get(header::RANGE).cloned(),
Some(asset.mime_type.as_str()),
Some(object.id.to_string()),
Some(asset.id.to_string()),
)
.await
}
@@ -1078,13 +1061,11 @@ pub async fn remove_tag(
crate::routes::documents::MoveDocumentRequest,
crate::routes::documents::BulkReanalyzeSelectionRequest,
crate::routes::documents::BulkReanalyzeResponse,
crate::routes::documents::AssetObjectsQuery,
crate::routes::documents::UploadDocumentForm,
crate::documents::asset::DocumentVersionResponse,
crate::documents::asset::DocumentVersionDetailResponse,
crate::documents::asset::DocumentAssetResponse,
crate::documents::asset::DocumentAssetDetailResponse,
crate::documents::asset::DocumentAssetObjectResponse,
crate::documents::correspondents::DocumentCorrespondentResponse,
crate::error::ApiErrorResponse,
))