feat(content_hash): propagate etag and hash_content to */resources
This commit is contained in:
@@ -20,6 +20,7 @@ use crate::application::dtos::folder_dto::FolderDto;
|
||||
use crate::application::dtos::grant_dto::{ResourceContentDto, ResourceTypeDto};
|
||||
use crate::application::ports::favorites_ports::FavoritesUseCase;
|
||||
use crate::application::services::favorites_service::FavoritesService;
|
||||
use crate::domain::entities::file::File;
|
||||
use crate::interfaces::errors::AppError;
|
||||
use crate::interfaces::middleware::auth::AuthUser;
|
||||
|
||||
@@ -279,12 +280,18 @@ pub async fn list_favorites_resources(
|
||||
.as_deref()
|
||||
.unwrap_or("application/octet-stream");
|
||||
let size_bytes = row.size.max(0) as u64;
|
||||
// The favorites list query doesn't select
|
||||
// `blob_hash` — favorites UI displays metadata
|
||||
// only and doesn't trigger ETag-conditional
|
||||
// requests against these rows. If a caller
|
||||
// ever needs the content hash here, widen the
|
||||
// favorites SQL.
|
||||
// Route ETag through `File::compute_etag` so
|
||||
// this listing's `etag` byte-equals what
|
||||
// GET/HEAD/PROPFIND would return for the same
|
||||
// file. `blob_hash` is `None` only for
|
||||
// folder rows, which take the other branch.
|
||||
let modified_at_u = row.modified_at.timestamp() as u64;
|
||||
let content_hash = row.blob_hash.clone().unwrap_or_default();
|
||||
let etag = if content_hash.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
File::compute_etag(&content_hash, modified_at_u)
|
||||
};
|
||||
let dto = FileDto {
|
||||
id: row.resource_id.to_string(),
|
||||
name: row.name.clone(),
|
||||
@@ -293,7 +300,7 @@ pub async fn list_favorites_resources(
|
||||
mime_type: std::sync::Arc::from(mime),
|
||||
folder_id: row.parent_id.map(|u| u.to_string()),
|
||||
created_at: row.resource_created_at.timestamp() as u64,
|
||||
modified_at: row.modified_at.timestamp() as u64,
|
||||
modified_at: modified_at_u,
|
||||
icon_class: std::sync::Arc::from(icon_class_for(&row.name, mime)),
|
||||
icon_special_class: std::sync::Arc::from(icon_special_class_for(
|
||||
&row.name, mime,
|
||||
@@ -302,8 +309,8 @@ pub async fn list_favorites_resources(
|
||||
size_formatted: format_file_size(size_bytes),
|
||||
owner_id: Some(row.owner_id.to_string()),
|
||||
sort_date: None,
|
||||
content_hash: String::new(),
|
||||
etag: String::new(),
|
||||
content_hash,
|
||||
etag,
|
||||
};
|
||||
FavoritesResourceItemDto {
|
||||
resource_type: ResourceTypeDto::File,
|
||||
|
||||
@@ -26,6 +26,7 @@ use crate::application::ports::folder_ports::FolderUseCase;
|
||||
use crate::application::ports::trash_ports::TrashUseCase;
|
||||
use crate::application::services::folder_service::FolderService;
|
||||
use crate::common::di::AppState as GlobalAppState;
|
||||
use crate::domain::entities::file::File;
|
||||
use crate::interfaces::errors::AppError;
|
||||
use crate::interfaces::middleware::auth::AuthUser;
|
||||
|
||||
@@ -717,14 +718,20 @@ pub async fn list_folder_resources(
|
||||
.as_deref()
|
||||
.unwrap_or("application/octet-stream");
|
||||
let size_bytes = row.size.max(0) as u64;
|
||||
// `FolderResourceRow` (the UNION ALL row used
|
||||
// by this listing) doesn't carry `blob_hash`,
|
||||
// so neither `content_hash` nor `etag` can be
|
||||
// populated here without widening the SQL. The
|
||||
// REST file-listing UI doesn't issue
|
||||
// conditional requests against these rows —
|
||||
// file download / WebDAV PROPFIND go through
|
||||
// paths that DO carry the hash.
|
||||
// `blob_hash` is `Some(_)` for file rows in the
|
||||
// UNION ALL (`NULL` for folders). Route the
|
||||
// ETag formula through `File::compute_etag` —
|
||||
// the single source of truth shared with
|
||||
// GET/HEAD/PROPFIND/PUT response — so this
|
||||
// listing's `etag` byte-equals what a
|
||||
// conditional request would compare against.
|
||||
let modified_at_u = row.modified_at.timestamp() as u64;
|
||||
let content_hash = row.blob_hash.clone().unwrap_or_default();
|
||||
let etag = if content_hash.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
File::compute_etag(&content_hash, modified_at_u)
|
||||
};
|
||||
let dto = FileDto {
|
||||
id: row.id.to_string(),
|
||||
name: row.name.clone(),
|
||||
@@ -740,8 +747,8 @@ pub async fn list_folder_resources(
|
||||
size_formatted: format_file_size(size_bytes),
|
||||
owner_id: Some(row.owner_id.to_string()),
|
||||
sort_date: None,
|
||||
content_hash: String::new(),
|
||||
etag: String::new(),
|
||||
content_hash,
|
||||
etag,
|
||||
};
|
||||
FolderResourceItemDto {
|
||||
resource_type: ResourceTypeDto::File,
|
||||
|
||||
@@ -19,6 +19,7 @@ use crate::application::dtos::recent_dto::{
|
||||
};
|
||||
use crate::application::ports::recent_ports::RecentItemsUseCase;
|
||||
use crate::application::services::recent_service::RecentService;
|
||||
use crate::domain::entities::file::File;
|
||||
use crate::interfaces::errors::AppError;
|
||||
use crate::interfaces::middleware::auth::AuthUser;
|
||||
use uuid::Uuid;
|
||||
@@ -309,8 +310,16 @@ pub async fn list_recent_resources(
|
||||
.as_deref()
|
||||
.unwrap_or("application/octet-stream");
|
||||
let size_bytes = row.size.max(0) as u64;
|
||||
// Recents listing row doesn't carry blob_hash
|
||||
// (same reason as folder_handler / favorites).
|
||||
// Route ETag through `File::compute_etag` so this
|
||||
// listing matches GET/HEAD/PROPFIND byte-for-byte
|
||||
// for the same file.
|
||||
let modified_at_u = row.modified_at.timestamp() as u64;
|
||||
let content_hash = row.blob_hash.clone().unwrap_or_default();
|
||||
let etag = if content_hash.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
File::compute_etag(&content_hash, modified_at_u)
|
||||
};
|
||||
let dto = FileDto {
|
||||
id: row.resource_id.to_string(),
|
||||
name: row.name.clone(),
|
||||
@@ -319,7 +328,7 @@ pub async fn list_recent_resources(
|
||||
mime_type: std::sync::Arc::from(mime),
|
||||
folder_id: row.parent_id.map(|u| u.to_string()),
|
||||
created_at: row.resource_created_at.timestamp() as u64,
|
||||
modified_at: row.modified_at.timestamp() as u64,
|
||||
modified_at: modified_at_u,
|
||||
icon_class: std::sync::Arc::from(icon_class_for(&row.name, mime)),
|
||||
icon_special_class: std::sync::Arc::from(icon_special_class_for(
|
||||
&row.name, mime,
|
||||
@@ -328,8 +337,8 @@ pub async fn list_recent_resources(
|
||||
size_formatted: format_file_size(size_bytes),
|
||||
owner_id: Some(row.owner_id.to_string()),
|
||||
sort_date: None,
|
||||
content_hash: String::new(),
|
||||
etag: String::new(),
|
||||
content_hash,
|
||||
etag,
|
||||
};
|
||||
RecentResourceItemDto {
|
||||
resource_type: ResourceTypeDto::File,
|
||||
|
||||
Reference in New Issue
Block a user