cleanup
This commit is contained in:
+288
-38
@@ -378,6 +378,13 @@ pub struct AssetObjectsQuery {
|
||||
pub limit: Option<i32>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/documents",
|
||||
params(DocumentListQuery),
|
||||
responses((status = 200, description = "List documents", body = [DocumentResponse])),
|
||||
tag = "Documents"
|
||||
)]
|
||||
pub async fn list_documents(
|
||||
State(state): State<AppState>,
|
||||
Query(params): Query<DocumentListQuery>,
|
||||
@@ -633,48 +640,18 @@ pub async fn list_documents(
|
||||
.load(&mut conn)?
|
||||
};
|
||||
|
||||
let type_ids: HashSet<Uuid> = docs.iter().filter_map(|doc| doc.document_type_id).collect();
|
||||
let doc_type_map: HashMap<Uuid, DocumentType> = if type_ids.is_empty() {
|
||||
HashMap::new()
|
||||
} else {
|
||||
let ids: Vec<Uuid> = type_ids.iter().copied().collect();
|
||||
document_types::table
|
||||
.filter(document_types::tenant_id.eq(tenant_id))
|
||||
.filter(document_types::id.eq_any(&ids))
|
||||
.load::<DocumentType>(&mut conn)?
|
||||
.into_iter()
|
||||
.map(|typ| (typ.id, typ))
|
||||
.collect()
|
||||
};
|
||||
|
||||
let doc_ids: Vec<Uuid> = docs.iter().map(|doc| doc.id).collect();
|
||||
let tags_map = load_tags_for_documents(&mut conn, &doc_ids)?;
|
||||
let mut correspondents_map = load_correspondents_for_documents(&mut conn, &doc_ids)?;
|
||||
drop(conn);
|
||||
|
||||
let primary_versions = load_primary_assets(&state, tenant_id, &docs)?;
|
||||
let mut response = Vec::with_capacity(doc_ids.len());
|
||||
for doc in docs {
|
||||
let tags = tags_map.get(&doc.id).cloned();
|
||||
let correspondents = correspondents_map.remove(&doc.id).unwrap_or_default();
|
||||
let current_version = primary_versions.get(&doc.id).cloned();
|
||||
let doc_type = doc
|
||||
.document_type_id
|
||||
.and_then(|id| doc_type_map.get(&id).cloned());
|
||||
response.push(to_document_response(
|
||||
&state,
|
||||
user_id,
|
||||
doc,
|
||||
tags,
|
||||
correspondents,
|
||||
current_version,
|
||||
doc_type,
|
||||
)?);
|
||||
}
|
||||
let response = hydrate_documents(&state, &mut conn, tenant_id, user_id, docs)?;
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/documents/check",
|
||||
params(DocumentCheckQuery),
|
||||
responses((status = 200, description = "Checksum lookup", body = DocumentCheckResponse)),
|
||||
tag = "Documents"
|
||||
)]
|
||||
pub async fn check_document(
|
||||
Query(query): Query<DocumentCheckQuery>,
|
||||
TenantScopedConn {
|
||||
@@ -728,6 +705,13 @@ pub async 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 async fn get_document(
|
||||
State(state): State<AppState>,
|
||||
Path(document_id): Path<Uuid>,
|
||||
@@ -773,6 +757,17 @@ pub async fn get_document(
|
||||
}))
|
||||
}
|
||||
|
||||
#[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 async fn upload_document(
|
||||
State(state): State<AppState>,
|
||||
TenantScopedConn {
|
||||
@@ -978,6 +973,13 @@ pub async fn upload_document(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/documents/{id}/assets",
|
||||
params(("id" = Uuid, Path, description = "Document ID"), AssetRequestQuery),
|
||||
responses((status = 202, description = "Asset generation requested")),
|
||||
tag = "Assets"
|
||||
)]
|
||||
pub async fn request_document_assets(
|
||||
Path(document_id): Path<Uuid>,
|
||||
Query(query): Query<AssetRequestQuery>,
|
||||
@@ -1014,6 +1016,13 @@ pub async fn request_document_assets(
|
||||
Ok(StatusCode::ACCEPTED)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/documents/bulk/reanalyze",
|
||||
request_body = BulkReanalyzeSelectionRequest,
|
||||
responses((status = 200, description = "Reanalyze queued", body = BulkReanalyzeResponse)),
|
||||
tag = "Documents"
|
||||
)]
|
||||
pub async fn reanalyze_selected_documents(
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
@@ -1065,6 +1074,13 @@ pub async fn reanalyze_selected_documents(
|
||||
Ok((StatusCode::ACCEPTED, Json(BulkReanalyzeResponse { queued })))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/api/documents/{id}/assets",
|
||||
params(("id" = Uuid, Path, description = "Document ID")),
|
||||
responses((status = 200, description = "Document assets", body = [DocumentAssetResponse])),
|
||||
tag = "Assets"
|
||||
)]
|
||||
pub async fn list_document_assets(
|
||||
State(state): State<AppState>,
|
||||
Path(document_id): Path<Uuid>,
|
||||
@@ -1089,6 +1105,13 @@ pub async fn list_document_assets(
|
||||
Ok(Json(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 = DocumentAssetDetailResponse)),
|
||||
tag = "Assets"
|
||||
)]
|
||||
pub async fn get_document_asset(
|
||||
State(state): State<AppState>,
|
||||
Path(asset_id): Path<Uuid>,
|
||||
@@ -1163,6 +1186,13 @@ pub async fn get_document_asset(
|
||||
Ok(Json(to_asset_detail_response(asset, object_responses)))
|
||||
}
|
||||
|
||||
#[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 async fn list_document_versions(
|
||||
Path(document_id): Path<Uuid>,
|
||||
TenantScopedConn {
|
||||
@@ -1192,6 +1222,16 @@ pub async fn list_document_versions(
|
||||
Ok(Json(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 async fn get_document_version(
|
||||
State(state): State<AppState>,
|
||||
Path((document_id, version_id)): Path<(Uuid, Uuid)>,
|
||||
@@ -1230,6 +1270,13 @@ pub async fn get_document_version(
|
||||
}))
|
||||
}
|
||||
|
||||
#[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 async fn download_with_token(
|
||||
State(state): State<AppState>,
|
||||
Path(token): Path<String>,
|
||||
@@ -1282,6 +1329,13 @@ pub async fn download_with_token(
|
||||
Ok(axum::response::Redirect::temporary(&presigned_url))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/documents/{id}",
|
||||
params(("id" = Uuid, Path, description = "Document ID")),
|
||||
responses((status = 204, description = "Document deleted")),
|
||||
tag = "Documents"
|
||||
)]
|
||||
pub async fn delete_document(
|
||||
Path(document_id): Path<Uuid>,
|
||||
TenantScopedConn {
|
||||
@@ -1304,6 +1358,14 @@ pub async fn delete_document(
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[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 async fn update_document(
|
||||
State(state): State<AppState>,
|
||||
Path(document_id): Path<Uuid>,
|
||||
@@ -1519,6 +1581,14 @@ pub async fn update_document(
|
||||
}))
|
||||
}
|
||||
|
||||
#[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 async fn restore_document(
|
||||
Path(document_id): Path<Uuid>,
|
||||
TenantScopedConn {
|
||||
@@ -1569,6 +1639,14 @@ pub async fn restore_document(
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[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 async fn move_document(
|
||||
Path(document_id): Path<Uuid>,
|
||||
TenantScopedConn {
|
||||
@@ -1597,6 +1675,13 @@ pub async fn move_document(
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/documents/bulk/move",
|
||||
request_body = BulkMoveRequest,
|
||||
responses((status = 200, description = "Bulk move outcome", body = BulkMoveResponse)),
|
||||
tag = "Documents"
|
||||
)]
|
||||
pub async fn bulk_move_documents(
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
@@ -1680,6 +1765,14 @@ pub async fn bulk_move_documents(
|
||||
Ok((StatusCode::OK, body.into_json()?))
|
||||
}
|
||||
|
||||
#[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 async fn assign_correspondents(
|
||||
Path(document_id): Path<Uuid>,
|
||||
TenantScopedConn {
|
||||
@@ -1761,6 +1854,13 @@ pub async fn assign_correspondents(
|
||||
no_content()
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/documents/bulk/correspondents",
|
||||
request_body = BulkCorrespondentsRequest,
|
||||
responses((status = 200, description = "Bulk correspondents outcome", body = BulkCorrespondentResponse)),
|
||||
tag = "Documents"
|
||||
)]
|
||||
pub async fn bulk_assign_correspondents(
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
@@ -1853,6 +1953,16 @@ pub async fn bulk_assign_correspondents(
|
||||
Ok((StatusCode::OK, body.into_json()?))
|
||||
}
|
||||
|
||||
#[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 async fn remove_correspondent(
|
||||
Path((document_id, correspondent_id)): Path<(Uuid, Uuid)>,
|
||||
TenantScopedConn {
|
||||
@@ -1892,6 +2002,14 @@ pub async fn remove_correspondent(
|
||||
no_content()
|
||||
}
|
||||
|
||||
#[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 async fn assign_tags(
|
||||
Path(document_id): Path<Uuid>,
|
||||
TenantScopedConn {
|
||||
@@ -1922,6 +2040,13 @@ pub async fn assign_tags(
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/api/documents/bulk/tags",
|
||||
request_body = BulkTagRequest,
|
||||
responses((status = 200, description = "Bulk tag outcome", body = BulkTagResponse)),
|
||||
tag = "Documents"
|
||||
)]
|
||||
pub async fn bulk_update_tags(
|
||||
TenantScopedConn {
|
||||
mut conn,
|
||||
@@ -2007,6 +2132,16 @@ pub async fn bulk_update_tags(
|
||||
Ok((StatusCode::OK, response.into_json()?))
|
||||
}
|
||||
|
||||
#[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 async fn remove_tag(
|
||||
Path((document_id, tag_id)): Path<(Uuid, Uuid)>,
|
||||
TenantScopedConn {
|
||||
@@ -2363,3 +2498,118 @@ fn load_document_type(
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn hydrate_documents(
|
||||
state: &AppState,
|
||||
conn: &mut PgConnection,
|
||||
tenant_id: Uuid,
|
||||
user_id: Uuid,
|
||||
docs: Vec<Document>,
|
||||
) -> AppResult<Vec<DocumentResponse>> {
|
||||
if docs.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
let type_ids: HashSet<Uuid> = docs.iter().filter_map(|doc| doc.document_type_id).collect();
|
||||
let doc_type_map: HashMap<Uuid, DocumentType> = if type_ids.is_empty() {
|
||||
HashMap::new()
|
||||
} else {
|
||||
let ids: Vec<Uuid> = type_ids.iter().copied().collect();
|
||||
document_types::table
|
||||
.filter(document_types::tenant_id.eq(tenant_id))
|
||||
.filter(document_types::id.eq_any(&ids))
|
||||
.load::<DocumentType>(conn)?
|
||||
.into_iter()
|
||||
.map(|typ| (typ.id, typ))
|
||||
.collect()
|
||||
};
|
||||
|
||||
let doc_ids: Vec<Uuid> = docs.iter().map(|doc| doc.id).collect();
|
||||
let tags_map = load_tags_for_documents(conn, &doc_ids)?;
|
||||
let mut correspondents_map = load_correspondents_for_documents(conn, &doc_ids)?;
|
||||
|
||||
let primary_versions = load_primary_assets(state, tenant_id, &docs)?;
|
||||
let mut responses = Vec::with_capacity(doc_ids.len());
|
||||
for doc in docs {
|
||||
let tags = tags_map.get(&doc.id).cloned();
|
||||
let correspondents = correspondents_map.remove(&doc.id).unwrap_or_default();
|
||||
let current_version = primary_versions.get(&doc.id).cloned();
|
||||
let doc_type = doc
|
||||
.document_type_id
|
||||
.and_then(|id| doc_type_map.get(&id).cloned());
|
||||
responses.push(to_document_response(
|
||||
state,
|
||||
user_id,
|
||||
doc,
|
||||
tags,
|
||||
correspondents,
|
||||
current_version,
|
||||
doc_type,
|
||||
)?);
|
||||
}
|
||||
|
||||
Ok(responses)
|
||||
}
|
||||
|
||||
#[derive(utoipa::OpenApi)]
|
||||
#[openapi(
|
||||
paths(
|
||||
crate::routes::documents::list_documents,
|
||||
crate::routes::documents::check_document,
|
||||
crate::routes::documents::upload_document,
|
||||
crate::routes::documents::get_document,
|
||||
crate::routes::documents::update_document,
|
||||
crate::routes::documents::delete_document,
|
||||
crate::routes::documents::restore_document,
|
||||
crate::routes::documents::download_with_token,
|
||||
crate::routes::documents::move_document,
|
||||
crate::routes::documents::assign_tags,
|
||||
crate::routes::documents::remove_tag,
|
||||
crate::routes::documents::bulk_move_documents,
|
||||
crate::routes::documents::bulk_update_tags,
|
||||
crate::routes::documents::bulk_assign_correspondents,
|
||||
crate::routes::documents::assign_correspondents,
|
||||
crate::routes::documents::remove_correspondent,
|
||||
crate::routes::documents::reanalyze_selected_documents,
|
||||
crate::routes::documents::list_document_assets,
|
||||
crate::routes::documents::request_document_assets,
|
||||
crate::routes::documents::get_document_asset,
|
||||
crate::routes::documents::list_document_versions,
|
||||
crate::routes::documents::get_document_version,
|
||||
),
|
||||
components(schemas(
|
||||
crate::routes::documents::DocumentListQuery,
|
||||
crate::routes::documents::DocumentStatusFilter,
|
||||
crate::routes::documents::AssetRequestQuery,
|
||||
crate::routes::documents::DocumentCheckQuery,
|
||||
crate::routes::documents::DocumentCheckResponse,
|
||||
crate::routes::documents::DocumentResponse,
|
||||
crate::routes::documents::DocumentDetailResponse,
|
||||
crate::routes::documents::DocumentMetadataUpdate,
|
||||
crate::routes::documents::DocumentTypeResponse,
|
||||
crate::routes::documents::TagResponse,
|
||||
crate::routes::documents::CorrespondentAssignmentInput,
|
||||
crate::routes::documents::AssignCorrespondentsRequest,
|
||||
crate::routes::documents::BulkCorrespondentAction,
|
||||
crate::routes::documents::BulkCorrespondentsRequest,
|
||||
crate::routes::documents::BulkCorrespondentResponse,
|
||||
crate::routes::documents::BulkMoveRequest,
|
||||
crate::routes::documents::BulkMoveResponse,
|
||||
crate::routes::documents::BulkTagAction,
|
||||
crate::routes::documents::BulkTagRequest,
|
||||
crate::routes::documents::BulkTagResponse,
|
||||
crate::routes::documents::AssignTagsRequest,
|
||||
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,
|
||||
))
|
||||
)]
|
||||
pub struct DocumentsApiDoc;
|
||||
|
||||
Reference in New Issue
Block a user