2026-02-12 09:41:25 +01:00
|
|
|
|
//! Domain persistence port for the File entity.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
//!
|
2026-02-12 09:41:25 +01:00
|
|
|
|
//! Defines the contract that any file storage implementation must fulfill.
|
|
|
|
|
|
//! This trait lives in the domain because File is a core entity of the system
|
|
|
|
|
|
//! and its persistence contracts belong to the domain layer, following
|
|
|
|
|
|
//! Clean/Hexagonal Architecture principles.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
//!
|
2026-02-12 09:41:25 +01:00
|
|
|
|
//! Concrete implementations (filesystem, PostgreSQL, S3, etc.) live in
|
|
|
|
|
|
//! the infrastructure layer.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
|
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
2025-03-17 21:28:08 +01:00
|
|
|
|
use async_trait::async_trait;
|
2026-02-08 13:40:23 +01:00
|
|
|
|
use bytes::Bytes;
|
|
|
|
|
|
use futures::Stream;
|
|
|
|
|
|
|
2026-02-14 01:29:34 +01:00
|
|
|
|
use crate::common::errors::DomainError;
|
2025-03-17 21:28:08 +01:00
|
|
|
|
use crate::domain::entities::file::File;
|
2025-03-19 00:44:27 +01:00
|
|
|
|
use crate::domain::services::path_service::StoragePath;
|
2025-03-17 21:28:08 +01:00
|
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
|
// ─────────────────────────────────────────────────────
|
2026-02-12 09:41:25 +01:00
|
|
|
|
// FileReadRepository — read/query operations
|
2026-02-08 13:40:23 +01:00
|
|
|
|
// ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Domain port for file **reading**.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
///
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Encapsulates every operation that queries state without modifying it:
|
|
|
|
|
|
/// get, list, content, stream, mmap, range, path resolution.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
#[async_trait]
|
|
|
|
|
|
pub trait FileReadRepository: Send + Sync + 'static {
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Gets a file by its ID.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn get_file(&self, id: &str) -> Result<File, DomainError>;
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Lists files in a folder.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn list_files(&self, folder_id: Option<&str>) -> Result<Vec<File>, DomainError>;
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Gets full content as bytes (only for small/medium files).
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn get_file_content(&self, id: &str) -> Result<Vec<u8>, DomainError>;
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Gets content as a stream (ideal for large files).
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn get_file_stream(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
id: &str,
|
|
|
|
|
|
) -> Result<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>, DomainError>;
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Stream of a byte range (HTTP Range Requests, video seek).
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn get_file_range_stream(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
id: &str,
|
|
|
|
|
|
start: u64,
|
|
|
|
|
|
end: Option<u64>,
|
|
|
|
|
|
) -> Result<Box<dyn Stream<Item = Result<Bytes, std::io::Error>> + Send>, DomainError>;
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Memory-mapped file for zero-copy access (10–100 MB).
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn get_file_mmap(&self, id: &str) -> Result<Bytes, DomainError>;
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Gets the logical storage path of a file.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn get_file_path(&self, id: &str) -> Result<StoragePath, DomainError>;
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Gets the parent folder ID from a path (WebDAV).
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn get_parent_folder_id(&self, path: &str) -> Result<String, DomainError>;
|
2025-03-17 21:28:08 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-08 13:40:23 +01:00
|
|
|
|
// ─────────────────────────────────────────────────────
|
2026-02-12 09:41:25 +01:00
|
|
|
|
// FileWriteRepository — write/mutation operations
|
2026-02-08 13:40:23 +01:00
|
|
|
|
// ─────────────────────────────────────────────────────
|
2025-03-17 21:28:08 +01:00
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Domain port for file **writing**.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
///
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Covers: upload (buffered + streaming), move, delete, update,
|
|
|
|
|
|
/// and deferred registration for write-behind cache.
|
2025-03-17 21:28:08 +01:00
|
|
|
|
#[async_trait]
|
2026-02-08 13:40:23 +01:00
|
|
|
|
pub trait FileWriteRepository: Send + Sync + 'static {
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Saves a new file from bytes.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn save_file(
|
2025-03-17 21:28:08 +01:00
|
|
|
|
&self,
|
|
|
|
|
|
name: String,
|
|
|
|
|
|
folder_id: Option<String>,
|
|
|
|
|
|
content_type: String,
|
|
|
|
|
|
content: Vec<u8>,
|
2026-02-08 13:40:23 +01:00
|
|
|
|
) -> Result<File, DomainError>;
|
|
|
|
|
|
|
2026-02-15 17:53:25 +01:00
|
|
|
|
/// Streaming upload — saves a file from a temp file already on disk.
|
|
|
|
|
|
async fn save_file_from_temp(
|
2026-02-03 17:59:04 +01:00
|
|
|
|
&self,
|
|
|
|
|
|
name: String,
|
|
|
|
|
|
folder_id: Option<String>,
|
|
|
|
|
|
content_type: String,
|
2026-02-15 17:53:25 +01:00
|
|
|
|
temp_path: &std::path::Path,
|
|
|
|
|
|
size: u64,
|
|
|
|
|
|
pre_computed_hash: Option<String>,
|
2026-02-08 13:40:23 +01:00
|
|
|
|
) -> Result<File, DomainError>;
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Moves a file to another folder.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn move_file(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
file_id: &str,
|
|
|
|
|
|
target_folder_id: Option<String>,
|
|
|
|
|
|
) -> Result<File, DomainError>;
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Renames a file (same folder, different name).
|
2026-02-14 01:29:34 +01:00
|
|
|
|
async fn rename_file(&self, file_id: &str, new_name: &str) -> Result<File, DomainError>;
|
2026-02-08 22:44:42 +01:00
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Deletes a file.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn delete_file(&self, id: &str) -> Result<(), DomainError>;
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Updates the content of an existing file.
|
2026-02-14 01:29:34 +01:00
|
|
|
|
async fn update_file_content(&self, file_id: &str, content: Vec<u8>)
|
|
|
|
|
|
-> Result<(), DomainError>;
|
2026-02-08 13:40:23 +01:00
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Registers file metadata WITHOUT writing content to disk (write-behind).
|
2026-02-08 13:40:23 +01:00
|
|
|
|
///
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Returns `(File, PathBuf)` where `PathBuf` is the destination path for
|
|
|
|
|
|
/// the deferred write that the `WriteBehindCache` will perform.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn register_file_deferred(
|
2025-03-17 21:28:08 +01:00
|
|
|
|
&self,
|
|
|
|
|
|
name: String,
|
|
|
|
|
|
folder_id: Option<String>,
|
|
|
|
|
|
content_type: String,
|
2026-02-08 13:40:23 +01:00
|
|
|
|
size: u64,
|
|
|
|
|
|
) -> Result<(File, PathBuf), DomainError>;
|
|
|
|
|
|
|
|
|
|
|
|
// ── Trash operations ──
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Moves a file to the trash
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn move_to_trash(&self, file_id: &str) -> Result<(), DomainError>;
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Restores a file from the trash to its original location
|
2026-02-14 01:29:34 +01:00
|
|
|
|
async fn restore_from_trash(
|
|
|
|
|
|
&self,
|
|
|
|
|
|
file_id: &str,
|
|
|
|
|
|
original_path: &str,
|
|
|
|
|
|
) -> Result<(), DomainError>;
|
2026-02-08 13:40:23 +01:00
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Permanently deletes a file (used by the trash)
|
2026-02-08 13:40:23 +01:00
|
|
|
|
async fn delete_file_permanently(&self, file_id: &str) -> Result<(), DomainError>;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ─────────────────────────────────────────────────────
|
2026-02-12 09:41:25 +01:00
|
|
|
|
// FileRepository — unified supertrait
|
2026-02-08 13:40:23 +01:00
|
|
|
|
// ─────────────────────────────────────────────────────
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Unified port for file persistence.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
///
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// It is a supertrait of `FileReadRepository + FileWriteRepository`.
|
|
|
|
|
|
/// Any type that implements both ports gets `FileRepository`
|
|
|
|
|
|
/// automatically via blanket impl.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
pub trait FileRepository: FileReadRepository + FileWriteRepository {}
|
|
|
|
|
|
|
2026-02-12 09:41:25 +01:00
|
|
|
|
/// Blanket implementation: any type that implements both ports
|
|
|
|
|
|
/// is automatically a FileRepository.
|
2026-02-08 13:40:23 +01:00
|
|
|
|
impl<T: FileReadRepository + FileWriteRepository> FileRepository for T {}
|