a lot of things
This commit is contained in:
@@ -53,7 +53,7 @@ pub struct Document {
|
||||
pub original_name: String,
|
||||
pub content_type: Option<String>,
|
||||
pub folder_id: Option<Uuid>,
|
||||
pub current_version: i32,
|
||||
pub current_version_id: Uuid,
|
||||
pub uploaded_at: NaiveDateTime,
|
||||
pub updated_at: NaiveDateTime,
|
||||
pub deleted_at: Option<NaiveDateTime>,
|
||||
@@ -70,7 +70,7 @@ pub struct NewDocument {
|
||||
pub original_name: String,
|
||||
pub content_type: Option<String>,
|
||||
pub folder_id: Option<Uuid>,
|
||||
pub current_version: i32,
|
||||
pub current_version_id: Uuid,
|
||||
pub metadata: serde_json::Value,
|
||||
pub issued_at: Option<NaiveDateTime>,
|
||||
pub title: String,
|
||||
@@ -111,8 +111,6 @@ pub struct DocumentAsset {
|
||||
pub asset_type: String,
|
||||
pub s3_key: String,
|
||||
pub mime_type: String,
|
||||
pub width: Option<i32>,
|
||||
pub height: Option<i32>,
|
||||
pub metadata: serde_json::Value,
|
||||
pub created_at: NaiveDateTime,
|
||||
}
|
||||
@@ -125,8 +123,6 @@ pub struct NewDocumentAsset {
|
||||
pub asset_type: String,
|
||||
pub s3_key: String,
|
||||
pub mime_type: String,
|
||||
pub width: Option<i32>,
|
||||
pub height: Option<i32>,
|
||||
pub metadata: serde_json::Value,
|
||||
}
|
||||
|
||||
|
||||
+133
-228
@@ -77,40 +77,6 @@ impl From<Tag> for TagResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Default)]
|
||||
pub struct DocumentAssetGroupResponse {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail: Option<DocumentAssetResponse>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub preview: Option<DocumentAssetResponse>,
|
||||
}
|
||||
|
||||
impl DocumentAssetGroupResponse {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.thumbnail.is_none() && self.preview.is_none()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DocumentResponse {
|
||||
pub id: Uuid,
|
||||
pub filename: String,
|
||||
pub title: String,
|
||||
pub original_name: String,
|
||||
pub content_type: Option<String>,
|
||||
pub folder_id: Option<Uuid>,
|
||||
pub current_version: i32,
|
||||
pub uploaded_at: String,
|
||||
pub updated_at: String,
|
||||
pub deleted_at: Option<String>,
|
||||
pub issued_at: Option<String>,
|
||||
pub metadata: Value,
|
||||
pub tags: Vec<TagResponse>,
|
||||
#[serde(default, skip_serializing_if = "DocumentAssetGroupResponse::is_empty")]
|
||||
pub assets: DocumentAssetGroupResponse,
|
||||
pub download_path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct DocumentVersionResponse {
|
||||
pub id: Uuid,
|
||||
@@ -127,19 +93,41 @@ pub struct DocumentAssetResponse {
|
||||
pub id: Uuid,
|
||||
pub asset_type: String,
|
||||
pub mime_type: String,
|
||||
pub width: Option<i32>,
|
||||
pub height: Option<i32>,
|
||||
pub url: String,
|
||||
pub metadata: Value,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<String>,
|
||||
pub created_at: String,
|
||||
}
|
||||
|
||||
type PrimaryDocumentAssets = DocumentAssetGroupResponse;
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct DocumentCurrentVersionResponse {
|
||||
#[serde(flatten)]
|
||||
pub version: DocumentVersionResponse,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub assets: Vec<DocumentAssetResponse>,
|
||||
pub download_path: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct DocumentResponse {
|
||||
pub id: Uuid,
|
||||
pub filename: String,
|
||||
pub title: String,
|
||||
pub original_name: String,
|
||||
pub content_type: Option<String>,
|
||||
pub folder_id: Option<Uuid>,
|
||||
pub uploaded_at: String,
|
||||
pub updated_at: String,
|
||||
pub deleted_at: Option<String>,
|
||||
pub issued_at: Option<String>,
|
||||
pub metadata: Value,
|
||||
pub tags: Vec<TagResponse>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub current_version: Option<DocumentCurrentVersionResponse>,
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
pub struct DocumentDetailResponse {
|
||||
pub document: DocumentResponse,
|
||||
pub current_version: DocumentVersionResponse,
|
||||
pub assets: Vec<DocumentAssetResponse>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -256,18 +244,18 @@ pub async fn list_documents(
|
||||
let tags_map = load_tags_for_documents(&mut conn, &doc_ids)?;
|
||||
drop(conn);
|
||||
|
||||
let primary_assets = load_primary_assets(&state, &docs).await?;
|
||||
let primary_versions = load_primary_assets(&state, &docs).await?;
|
||||
|
||||
let mut response = Vec::with_capacity(doc_ids.len());
|
||||
for doc in docs {
|
||||
let tags = tags_map.get(&doc.id).cloned();
|
||||
let assets = primary_assets.get(&doc.id).cloned();
|
||||
let current_version = primary_versions.get(&doc.id).cloned();
|
||||
response.push(to_document_response(
|
||||
&state,
|
||||
user.user_id,
|
||||
doc,
|
||||
tags,
|
||||
assets,
|
||||
current_version,
|
||||
)?);
|
||||
}
|
||||
|
||||
@@ -287,8 +275,7 @@ pub async fn get_document(
|
||||
}
|
||||
|
||||
let current_version: DocumentVersion = document_versions::table
|
||||
.filter(document_versions::document_id.eq(document_id))
|
||||
.filter(document_versions::version_number.eq(doc.current_version))
|
||||
.find(doc.current_version_id)
|
||||
.first(&mut conn)?;
|
||||
|
||||
let tags_map = load_tags_for_documents(&mut conn, &[document_id])?;
|
||||
@@ -296,21 +283,7 @@ pub async fn get_document(
|
||||
drop(conn);
|
||||
|
||||
let assets = load_asset_responses(&state, version_id).await?;
|
||||
let mut grouped_assets = DocumentAssetGroupResponse::default();
|
||||
if let Some(asset) = assets
|
||||
.iter()
|
||||
.find(|asset| asset.asset_type == "thumbnail")
|
||||
.cloned()
|
||||
{
|
||||
grouped_assets.thumbnail = Some(asset);
|
||||
}
|
||||
if let Some(asset) = assets
|
||||
.iter()
|
||||
.find(|asset| asset.asset_type == "preview")
|
||||
.cloned()
|
||||
{
|
||||
grouped_assets.preview = Some(asset);
|
||||
}
|
||||
let version_response = to_version_response(current_version);
|
||||
|
||||
Ok(Json(DocumentDetailResponse {
|
||||
document: to_document_response(
|
||||
@@ -318,10 +291,8 @@ pub async fn get_document(
|
||||
user.user_id,
|
||||
doc,
|
||||
tags_map.get(&document_id).cloned(),
|
||||
Some(grouped_assets),
|
||||
Some((version_response, assets)),
|
||||
)?,
|
||||
current_version: to_version_response(current_version),
|
||||
assets,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -441,17 +412,12 @@ pub async fn request_document_assets(
|
||||
return Err(AppError::not_found());
|
||||
}
|
||||
|
||||
let version: DocumentVersion = document_versions::table
|
||||
.filter(document_versions::document_id.eq(document_id))
|
||||
.filter(document_versions::version_number.eq(document.current_version))
|
||||
.first(&mut conn)?;
|
||||
|
||||
enqueue_job(
|
||||
&mut conn,
|
||||
JOB_ANALYZE_DOCUMENT,
|
||||
json!({
|
||||
"document_id": document_id,
|
||||
"document_version_id": version.id,
|
||||
"document_version_id": document.current_version_id,
|
||||
"force": query.force,
|
||||
}),
|
||||
None,
|
||||
@@ -467,11 +433,9 @@ pub async fn reanalyze_all_documents(
|
||||
) -> AppResult<(StatusCode, Json<BulkReanalyzeResponse>)> {
|
||||
let mut conn = state.db()?;
|
||||
|
||||
let targets: Vec<(Uuid, Uuid)> = document_versions::table
|
||||
.inner_join(documents::table.on(document_versions::document_id.eq(documents::id)))
|
||||
let targets: Vec<(Uuid, Uuid)> = documents::table
|
||||
.filter(documents::deleted_at.is_null())
|
||||
.filter(document_versions::version_number.eq(documents::current_version))
|
||||
.select((documents::id, document_versions::id))
|
||||
.select((documents::id, documents::current_version_id))
|
||||
.load(&mut conn)?;
|
||||
|
||||
let mut queued = 0usize;
|
||||
@@ -511,12 +475,10 @@ pub async fn reanalyze_selected_documents(
|
||||
|
||||
let mut conn = state.db()?;
|
||||
|
||||
let targets: Vec<(Uuid, Uuid)> = document_versions::table
|
||||
.inner_join(documents::table.on(document_versions::document_id.eq(documents::id)))
|
||||
let targets: Vec<(Uuid, Uuid)> = documents::table
|
||||
.filter(documents::id.eq_any(&document_ids))
|
||||
.filter(documents::deleted_at.is_null())
|
||||
.filter(document_versions::version_number.eq(documents::current_version))
|
||||
.select((documents::id, document_versions::id))
|
||||
.select((documents::id, documents::current_version_id))
|
||||
.load(&mut conn)?;
|
||||
|
||||
if targets.len() != document_ids.len() {
|
||||
@@ -554,16 +516,44 @@ pub async fn list_document_assets(
|
||||
return Err(AppError::not_found());
|
||||
}
|
||||
|
||||
let version: DocumentVersion = document_versions::table
|
||||
.filter(document_versions::document_id.eq(document_id))
|
||||
.filter(document_versions::version_number.eq(document.current_version))
|
||||
.first(&mut conn)?;
|
||||
let version_id = document.current_version_id;
|
||||
drop(conn);
|
||||
|
||||
let assets = load_asset_responses(&state, version.id).await?;
|
||||
let assets = load_asset_responses(&state, version_id).await?;
|
||||
Ok(Json(assets))
|
||||
}
|
||||
|
||||
pub async fn get_document_asset(
|
||||
State(state): State<AppState>,
|
||||
Path((document_id, asset_id)): Path<(Uuid, Uuid)>,
|
||||
) -> AppResult<Json<DocumentAssetResponse>> {
|
||||
let mut conn = state.db()?;
|
||||
let document: Document = documents::table.find(document_id).first(&mut conn)?;
|
||||
if document.deleted_at.is_some() {
|
||||
return Err(AppError::not_found());
|
||||
}
|
||||
|
||||
let asset: DocumentAsset = document_assets::table.find(asset_id).first(&mut conn)?;
|
||||
let version: DocumentVersion = document_versions::table
|
||||
.find(asset.document_version_id)
|
||||
.first(&mut conn)?;
|
||||
|
||||
if version.document_id != document_id {
|
||||
return Err(AppError::not_found());
|
||||
}
|
||||
|
||||
let s3_key = asset.s3_key.clone();
|
||||
drop(conn);
|
||||
|
||||
let presigned_url = state
|
||||
.storage
|
||||
.presign_get_object(&s3_key, Duration::from_secs(PRESIGNED_URL_EXPIRY_SECONDS))
|
||||
.await
|
||||
.map_err(|err| AppError::internal(format!("failed to generate asset URL: {err}")))?;
|
||||
|
||||
Ok(Json(to_asset_response(asset, Some(presigned_url))))
|
||||
}
|
||||
|
||||
pub async fn download_document(
|
||||
State(state): State<AppState>,
|
||||
Path(document_id): Path<Uuid>,
|
||||
@@ -575,8 +565,7 @@ pub async fn download_document(
|
||||
}
|
||||
|
||||
let version: DocumentVersion = document_versions::table
|
||||
.filter(document_versions::document_id.eq(document_id))
|
||||
.filter(document_versions::version_number.eq(doc.current_version))
|
||||
.find(doc.current_version_id)
|
||||
.first(&mut conn)?;
|
||||
|
||||
let presigned_url = state
|
||||
@@ -614,8 +603,7 @@ pub async fn download_with_token(
|
||||
}
|
||||
|
||||
let version: DocumentVersion = document_versions::table
|
||||
.filter(document_versions::document_id.eq(claims.doc_id))
|
||||
.filter(document_versions::version_number.eq(doc.current_version))
|
||||
.find(doc.current_version_id)
|
||||
.first(&mut conn)?;
|
||||
|
||||
let now = Utc::now().naive_utc();
|
||||
@@ -697,8 +685,7 @@ pub async fn update_document(
|
||||
}
|
||||
|
||||
let current_version: DocumentVersion = document_versions::table
|
||||
.filter(document_versions::document_id.eq(document_id))
|
||||
.filter(document_versions::version_number.eq(document.current_version))
|
||||
.find(document.current_version_id)
|
||||
.first(&mut conn)?;
|
||||
|
||||
let tags_map = load_tags_for_documents(&mut conn, &[document_id])?;
|
||||
@@ -706,21 +693,7 @@ pub async fn update_document(
|
||||
drop(conn);
|
||||
|
||||
let assets = load_asset_responses(&state, version_id).await?;
|
||||
let mut grouped_assets = DocumentAssetGroupResponse::default();
|
||||
if let Some(asset) = assets
|
||||
.iter()
|
||||
.find(|asset| asset.asset_type == "thumbnail")
|
||||
.cloned()
|
||||
{
|
||||
grouped_assets.thumbnail = Some(asset);
|
||||
}
|
||||
if let Some(asset) = assets
|
||||
.iter()
|
||||
.find(|asset| asset.asset_type == "preview")
|
||||
.cloned()
|
||||
{
|
||||
grouped_assets.preview = Some(asset);
|
||||
}
|
||||
let version_response = to_version_response(current_version);
|
||||
|
||||
Ok(Json(DocumentDetailResponse {
|
||||
document: to_document_response(
|
||||
@@ -728,10 +701,8 @@ pub async fn update_document(
|
||||
user.user_id,
|
||||
document,
|
||||
tags_map.get(&document_id).cloned(),
|
||||
Some(grouped_assets),
|
||||
Some((version_response, assets)),
|
||||
)?,
|
||||
current_version: to_version_response(current_version),
|
||||
assets,
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -982,9 +953,8 @@ async fn process_upload(
|
||||
|
||||
let existing = documents::table
|
||||
.inner_join(
|
||||
document_versions::table.on(document_versions::document_id
|
||||
.eq(documents::id)
|
||||
.and(document_versions::version_number.eq(documents::current_version))),
|
||||
document_versions::table
|
||||
.on(document_versions::id.eq(documents::current_version_id)),
|
||||
)
|
||||
.filter(document_versions::checksum.eq(&checksum_hex))
|
||||
.select((documents::all_columns, document_versions::all_columns))
|
||||
@@ -1008,21 +978,7 @@ async fn process_upload(
|
||||
let tags = tags_map.get(&document.id).cloned();
|
||||
drop(conn);
|
||||
let assets = load_asset_responses(state, version.id).await?;
|
||||
let mut grouped_assets = DocumentAssetGroupResponse::default();
|
||||
if let Some(asset) = assets
|
||||
.iter()
|
||||
.find(|asset| asset.asset_type == "thumbnail")
|
||||
.cloned()
|
||||
{
|
||||
grouped_assets.thumbnail = Some(asset);
|
||||
}
|
||||
if let Some(asset) = assets
|
||||
.iter()
|
||||
.find(|asset| asset.asset_type == "preview")
|
||||
.cloned()
|
||||
{
|
||||
grouped_assets.preview = Some(asset);
|
||||
}
|
||||
let version_response = to_version_response(version.clone());
|
||||
|
||||
info!(
|
||||
document_id = %document.id,
|
||||
@@ -1037,10 +993,8 @@ async fn process_upload(
|
||||
user_id,
|
||||
document,
|
||||
tags,
|
||||
Some(grouped_assets),
|
||||
Some((version_response, assets)),
|
||||
)?,
|
||||
current_version: to_version_response(version),
|
||||
assets,
|
||||
},
|
||||
created: false,
|
||||
});
|
||||
@@ -1078,7 +1032,7 @@ async fn process_upload(
|
||||
original_name: original_name.clone(),
|
||||
content_type: content_type.clone(),
|
||||
folder_id,
|
||||
current_version: version_number,
|
||||
current_version_id: version_id,
|
||||
issued_at: None,
|
||||
title: derive_document_title(&original_name),
|
||||
metadata: metadata_value.clone(),
|
||||
@@ -1109,9 +1063,13 @@ async fn process_upload(
|
||||
};
|
||||
|
||||
let detail = DocumentDetailResponse {
|
||||
document: to_document_response(state, user_id, document, None, None)?,
|
||||
current_version: to_version_response(version.clone()),
|
||||
assets: Vec::new(),
|
||||
document: to_document_response(
|
||||
state,
|
||||
user_id,
|
||||
document,
|
||||
None,
|
||||
Some((to_version_response(version.clone()), Vec::new())),
|
||||
)?,
|
||||
};
|
||||
|
||||
if let Ok(mut conn) = state.db() {
|
||||
@@ -1171,111 +1129,61 @@ pub(crate) fn load_tags_for_documents(
|
||||
pub(crate) async fn load_primary_assets(
|
||||
state: &AppState,
|
||||
documents: &[Document],
|
||||
) -> AppResult<HashMap<Uuid, PrimaryDocumentAssets>> {
|
||||
) -> AppResult<HashMap<Uuid, (DocumentVersionResponse, Vec<DocumentAssetResponse>)>> {
|
||||
if documents.is_empty() {
|
||||
return Ok(HashMap::new());
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct RawPrimaryAssets {
|
||||
thumbnail: Option<DocumentAsset>,
|
||||
preview: Option<DocumentAsset>,
|
||||
let mut doc_to_version: HashMap<Uuid, Uuid> = HashMap::with_capacity(documents.len());
|
||||
let mut version_ids: Vec<Uuid> = Vec::with_capacity(documents.len());
|
||||
for doc in documents {
|
||||
doc_to_version.insert(doc.id, doc.current_version_id);
|
||||
version_ids.push(doc.current_version_id);
|
||||
}
|
||||
|
||||
let mut current_versions: HashMap<Uuid, i32> = HashMap::new();
|
||||
let mut doc_ids = Vec::with_capacity(documents.len());
|
||||
for doc in documents {
|
||||
current_versions.insert(doc.id, doc.current_version);
|
||||
doc_ids.push(doc.id);
|
||||
}
|
||||
version_ids.sort();
|
||||
version_ids.dedup();
|
||||
|
||||
let mut conn = state.db()?;
|
||||
let versions: Vec<DocumentVersion> = document_versions::table
|
||||
.filter(document_versions::document_id.eq_any(&doc_ids))
|
||||
.filter(document_versions::id.eq_any(&version_ids))
|
||||
.load(&mut conn)?;
|
||||
|
||||
let mut version_id_by_doc: HashMap<Uuid, Uuid> = HashMap::new();
|
||||
let mut doc_id_by_version: HashMap<Uuid, Uuid> = HashMap::new();
|
||||
let mut version_map: HashMap<Uuid, DocumentVersion> = HashMap::new();
|
||||
for version in versions {
|
||||
if let Some(current_number) = current_versions.get(&version.document_id) {
|
||||
if *current_number == version.version_number {
|
||||
version_id_by_doc.insert(version.document_id, version.id);
|
||||
doc_id_by_version.insert(version.id, version.document_id);
|
||||
}
|
||||
}
|
||||
version_map.insert(version.id, version);
|
||||
}
|
||||
|
||||
if version_id_by_doc.is_empty() {
|
||||
return Ok(HashMap::new());
|
||||
}
|
||||
|
||||
let version_ids: Vec<Uuid> = version_id_by_doc.values().copied().collect();
|
||||
|
||||
let assets: Vec<DocumentAsset> = document_assets::table
|
||||
.filter(document_assets::document_version_id.eq_any(&version_ids))
|
||||
.filter(
|
||||
document_assets::asset_type
|
||||
.eq_any(vec!["thumbnail".to_string(), "preview".to_string()]),
|
||||
)
|
||||
.order((
|
||||
document_assets::document_version_id.asc(),
|
||||
document_assets::created_at.asc(),
|
||||
))
|
||||
.load(&mut conn)?;
|
||||
|
||||
let mut raw_assets: HashMap<Uuid, RawPrimaryAssets> = HashMap::new();
|
||||
let mut assets_by_version: HashMap<Uuid, Vec<DocumentAssetResponse>> = HashMap::new();
|
||||
for asset in assets {
|
||||
if let Some(doc_id) = doc_id_by_version.get(&asset.document_version_id) {
|
||||
let entry = raw_assets.entry(*doc_id).or_default();
|
||||
let asset_type = asset.asset_type.clone();
|
||||
match asset_type.as_str() {
|
||||
"thumbnail" => {
|
||||
if entry.thumbnail.is_none() {
|
||||
entry.thumbnail = Some(asset);
|
||||
}
|
||||
}
|
||||
"preview" => {
|
||||
if entry.preview.is_none() {
|
||||
entry.preview = Some(asset);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let version_id = asset.document_version_id;
|
||||
let response = to_asset_response(asset, None);
|
||||
assets_by_version
|
||||
.entry(version_id)
|
||||
.or_default()
|
||||
.push(response);
|
||||
}
|
||||
|
||||
drop(conn);
|
||||
|
||||
let mut responses = HashMap::with_capacity(raw_assets.len());
|
||||
for (doc_id, raw) in raw_assets {
|
||||
let mut assets = PrimaryDocumentAssets::default();
|
||||
|
||||
if let Some(asset) = raw.preview {
|
||||
let s3_key = asset.s3_key.clone();
|
||||
let url = state
|
||||
.storage
|
||||
.presign_get_object(&s3_key, Duration::from_secs(PRESIGNED_URL_EXPIRY_SECONDS))
|
||||
.await
|
||||
.map_err(|err| AppError::internal(format!("failed to sign asset URL: {err}")))?;
|
||||
assets.preview = Some(to_asset_response(asset, url));
|
||||
}
|
||||
|
||||
if let Some(asset) = raw.thumbnail {
|
||||
let s3_key = asset.s3_key.clone();
|
||||
let url = state
|
||||
.storage
|
||||
.presign_get_object(&s3_key, Duration::from_secs(PRESIGNED_URL_EXPIRY_SECONDS))
|
||||
.await
|
||||
.map_err(|err| AppError::internal(format!("failed to sign asset URL: {err}")))?;
|
||||
assets.thumbnail = Some(to_asset_response(asset, url));
|
||||
}
|
||||
|
||||
if assets.thumbnail.is_some() || assets.preview.is_some() {
|
||||
responses.insert(doc_id, assets);
|
||||
let mut result: HashMap<Uuid, (DocumentVersionResponse, Vec<DocumentAssetResponse>)> =
|
||||
HashMap::with_capacity(doc_to_version.len());
|
||||
for (doc_id, version_id) in doc_to_version {
|
||||
if let Some(version) = version_map.remove(&version_id) {
|
||||
let assets = assets_by_version.remove(&version_id).unwrap_or_default();
|
||||
result.insert(doc_id, (to_version_response(version), assets));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(responses)
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub(crate) fn to_document_response(
|
||||
@@ -1283,9 +1191,18 @@ pub(crate) fn to_document_response(
|
||||
user_id: Uuid,
|
||||
doc: Document,
|
||||
tags: Option<Vec<Tag>>,
|
||||
assets: Option<DocumentAssetGroupResponse>,
|
||||
current_version: Option<(DocumentVersionResponse, Vec<DocumentAssetResponse>)>,
|
||||
) -> AppResult<DocumentResponse> {
|
||||
let download_path = build_download_path(state, doc.id, user_id)?;
|
||||
let current_version = if let Some((version, assets)) = current_version {
|
||||
let download_path = build_download_path(state, doc.id, user_id)?;
|
||||
Some(DocumentCurrentVersionResponse {
|
||||
version,
|
||||
assets,
|
||||
download_path,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(DocumentResponse {
|
||||
id: doc.id,
|
||||
@@ -1294,7 +1211,6 @@ pub(crate) fn to_document_response(
|
||||
original_name: doc.original_name,
|
||||
content_type: doc.content_type,
|
||||
folder_id: doc.folder_id,
|
||||
current_version: doc.current_version,
|
||||
uploaded_at: to_iso(doc.uploaded_at),
|
||||
updated_at: to_iso(doc.updated_at),
|
||||
deleted_at: doc.deleted_at.map(to_iso),
|
||||
@@ -1305,8 +1221,7 @@ pub(crate) fn to_document_response(
|
||||
.into_iter()
|
||||
.map(TagResponse::from)
|
||||
.collect(),
|
||||
assets: assets.unwrap_or_default(),
|
||||
download_path,
|
||||
current_version,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1330,13 +1245,13 @@ fn to_version_response(version: DocumentVersion) -> DocumentVersionResponse {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_asset_response(asset: DocumentAsset, url: String) -> DocumentAssetResponse {
|
||||
fn to_asset_response(asset: DocumentAsset, url: Option<String>) -> DocumentAssetResponse {
|
||||
let metadata = asset.metadata.clone();
|
||||
DocumentAssetResponse {
|
||||
id: asset.id,
|
||||
asset_type: asset.asset_type,
|
||||
mime_type: asset.mime_type,
|
||||
width: asset.width,
|
||||
height: asset.height,
|
||||
metadata,
|
||||
url,
|
||||
created_at: to_iso(asset.created_at),
|
||||
}
|
||||
@@ -1369,20 +1284,10 @@ async fn load_asset_responses(
|
||||
.load(&mut conn)?;
|
||||
drop(conn);
|
||||
|
||||
let mut responses = Vec::with_capacity(assets.len());
|
||||
for asset in assets {
|
||||
let url = state
|
||||
.storage
|
||||
.presign_get_object(
|
||||
&asset.s3_key,
|
||||
Duration::from_secs(PRESIGNED_URL_EXPIRY_SECONDS),
|
||||
)
|
||||
.await
|
||||
.map_err(|err| AppError::internal(format!("failed to sign asset URL: {err}")))?;
|
||||
responses.push(to_asset_response(asset, url));
|
||||
}
|
||||
|
||||
Ok(responses)
|
||||
Ok(assets
|
||||
.into_iter()
|
||||
.map(|asset| to_asset_response(asset, None))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub(crate) fn to_iso(dt: NaiveDateTime) -> String {
|
||||
|
||||
@@ -215,18 +215,18 @@ pub async fn list_folder_contents(
|
||||
let tags_map = load_tags_for_documents(&mut conn, &doc_ids)?;
|
||||
drop(conn);
|
||||
|
||||
let primary_assets = load_primary_assets(&state, &docs).await?;
|
||||
let primary_versions = load_primary_assets(&state, &docs).await?;
|
||||
|
||||
let mut documents = Vec::with_capacity(doc_ids.len());
|
||||
for doc in docs {
|
||||
let tags = tags_map.get(&doc.id).cloned();
|
||||
let assets = primary_assets.get(&doc.id).cloned();
|
||||
let current_version = primary_versions.get(&doc.id).cloned();
|
||||
documents.push(to_document_response(
|
||||
&state,
|
||||
user.user_id,
|
||||
doc,
|
||||
tags,
|
||||
assets,
|
||||
current_version,
|
||||
)?);
|
||||
}
|
||||
|
||||
@@ -399,17 +399,17 @@ pub async fn search_documents(
|
||||
let tags_map = load_tags_for_documents(&mut conn, &doc_ids)?;
|
||||
drop(conn);
|
||||
|
||||
let primary_assets = load_primary_assets(&state, &docs).await?;
|
||||
let primary_versions = load_primary_assets(&state, &docs).await?;
|
||||
let mut response = Vec::with_capacity(doc_ids.len());
|
||||
for doc in docs {
|
||||
let tags = tags_map.get(&doc.id).cloned();
|
||||
let assets = primary_assets.get(&doc.id).cloned();
|
||||
let current_version = primary_versions.get(&doc.id).cloned();
|
||||
response.push(to_document_response(
|
||||
&state,
|
||||
user.user_id,
|
||||
doc,
|
||||
tags,
|
||||
assets,
|
||||
current_version,
|
||||
)?);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ pub fn create_router(state: AppState) -> Router<()> {
|
||||
.patch(documents::update_document),
|
||||
)
|
||||
.route("/:id/download", get(documents::download_document))
|
||||
.route("/:id/assets/:asset_id", get(documents::get_document_asset))
|
||||
.route(
|
||||
"/:id/assets",
|
||||
get(documents::list_document_assets).post(documents::request_document_assets),
|
||||
|
||||
@@ -7,8 +7,6 @@ diesel::table! {
|
||||
asset_type -> Text,
|
||||
s3_key -> Text,
|
||||
mime_type -> Text,
|
||||
width -> Nullable<Int4>,
|
||||
height -> Nullable<Int4>,
|
||||
metadata -> Jsonb,
|
||||
created_at -> Timestamptz,
|
||||
}
|
||||
@@ -48,7 +46,6 @@ diesel::table! {
|
||||
#[max_length = 100]
|
||||
content_type -> Nullable<Varchar>,
|
||||
folder_id -> Nullable<Uuid>,
|
||||
current_version -> Int4,
|
||||
uploaded_at -> Timestamptz,
|
||||
updated_at -> Timestamptz,
|
||||
deleted_at -> Nullable<Timestamptz>,
|
||||
@@ -56,6 +53,7 @@ diesel::table! {
|
||||
issued_at -> Nullable<Timestamptz>,
|
||||
#[max_length = 255]
|
||||
title -> Varchar,
|
||||
current_version_id -> Uuid,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +126,6 @@ diesel::joinable!(document_assets -> document_versions (document_version_id));
|
||||
diesel::joinable!(document_tags -> documents (document_id));
|
||||
diesel::joinable!(document_tags -> tags (tag_id));
|
||||
diesel::joinable!(document_tags -> users (assigned_by));
|
||||
diesel::joinable!(document_versions -> documents (document_id));
|
||||
diesel::joinable!(documents -> folders (folder_id));
|
||||
diesel::joinable!(refresh_tokens -> users (user_id));
|
||||
|
||||
|
||||
@@ -377,8 +377,6 @@ fn persist_ocr_metadata(
|
||||
asset_type: OCR_TEXT_ASSET_TYPE.to_string(),
|
||||
s3_key: s3_key.to_string(),
|
||||
mime_type: "text/plain".to_string(),
|
||||
width: None,
|
||||
height: None,
|
||||
metadata: json!({
|
||||
"generated_at": Utc::now().to_rfc3339(),
|
||||
"source": source,
|
||||
|
||||
@@ -405,10 +405,10 @@ fn persist_assets_metadata(
|
||||
asset_type: asset.asset_type.to_string(),
|
||||
s3_key: asset.s3_key.to_string(),
|
||||
mime_type: "image/png".to_string(),
|
||||
width: asset.generated.width,
|
||||
height: asset.generated.height,
|
||||
metadata: json!({
|
||||
"generated_at": Utc::now().to_rfc3339(),
|
||||
"width": asset.generated.width,
|
||||
"height": asset.generated.height,
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -422,8 +422,6 @@ fn persist_assets_metadata(
|
||||
.set((
|
||||
document_assets::s3_key.eq(excluded(document_assets::s3_key)),
|
||||
document_assets::mime_type.eq(excluded(document_assets::mime_type)),
|
||||
document_assets::width.eq(excluded(document_assets::width)),
|
||||
document_assets::height.eq(excluded(document_assets::height)),
|
||||
document_assets::metadata.eq(excluded(document_assets::metadata)),
|
||||
))
|
||||
.execute(&mut conn)
|
||||
|
||||
Reference in New Issue
Block a user