quick fix
This commit is contained in:
+22
-3
@@ -308,12 +308,31 @@ COMMENT ON TABLE carddav.group_memberships IS 'Many-to-many relationship between
|
||||
-- 4. STORAGE SCHEMA — 100% Blob Storage Model
|
||||
-- ============================================================
|
||||
-- All file/folder metadata lives here. Actual file content is stored
|
||||
-- as content-addressable blobs on the filesystem via DedupService
|
||||
-- (.blobs/{prefix}/{hash}.blob). No physical directories are created
|
||||
-- for user folders — they are virtual records in this schema.
|
||||
-- as content-addressed blobs on the filesystem (.blobs/{prefix}/{hash}.blob).
|
||||
-- The storage.blobs table is the authoritative dedup index — no JSON
|
||||
-- files or in-memory HashMaps are used.
|
||||
-- No physical directories are created for user folders — they are
|
||||
-- virtual records in this schema.
|
||||
-- ============================================================
|
||||
CREATE SCHEMA IF NOT EXISTS storage;
|
||||
|
||||
-- Content-addressable blob index (dedup)
|
||||
-- One row per unique content hash; multiple storage.files rows may
|
||||
-- reference the same blob via blob_hash → storage.blobs.hash.
|
||||
CREATE TABLE IF NOT EXISTS storage.blobs (
|
||||
hash VARCHAR(64) PRIMARY KEY,
|
||||
size BIGINT NOT NULL,
|
||||
ref_count INTEGER NOT NULL DEFAULT 1 CHECK (ref_count >= 0),
|
||||
content_type TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Fast lookup for garbage collection (orphaned blobs with no references)
|
||||
CREATE INDEX IF NOT EXISTS idx_blobs_orphaned
|
||||
ON storage.blobs(ref_count) WHERE ref_count = 0;
|
||||
|
||||
COMMENT ON TABLE storage.blobs IS 'Content-addressable blob dedup index — one row per unique SHA-256 hash';
|
||||
|
||||
-- Virtual folders (replaces physical directories on disk)
|
||||
CREATE TABLE IF NOT EXISTS storage.folders (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
+11
-13
@@ -44,7 +44,7 @@ use crate::common::stubs::{
|
||||
StubFileReadPort, StubFileWritePort, StubFolderStoragePort,
|
||||
StubI18nService, StubFolderUseCase, StubFileUploadUseCase,
|
||||
StubFileRetrievalUseCase, StubFileManagementUseCase, StubFileUseCaseFactory,
|
||||
StubSearchUseCase,
|
||||
StubSearchUseCase, StubDedupPort,
|
||||
};
|
||||
|
||||
/// Factory for the different application components
|
||||
@@ -86,8 +86,10 @@ impl AppServiceFactory {
|
||||
&self.storage_path
|
||||
}
|
||||
|
||||
/// Initializes the core system services
|
||||
pub async fn create_core_services(&self) -> Result<CoreServices, DomainError> {
|
||||
/// Initializes the core system services.
|
||||
///
|
||||
/// Requires a `PgPool` because `DedupService` stores its index in PostgreSQL.
|
||||
pub async fn create_core_services(&self, db_pool: &Arc<PgPool>) -> Result<CoreServices, DomainError> {
|
||||
// Path service (still needed for blob storage root + thumbnails)
|
||||
let path_service = Arc::new(PathService::new(self.storage_path.clone()));
|
||||
|
||||
@@ -126,9 +128,9 @@ impl AppServiceFactory {
|
||||
);
|
||||
image_transcode_service.initialize().await?;
|
||||
|
||||
// Deduplication service — PRIMARY blob storage engine
|
||||
// Deduplication service — PRIMARY blob storage engine (PostgreSQL-backed index)
|
||||
let dedup_service = Arc::new(
|
||||
crate::infrastructure::services::dedup_service::DedupService::new(&self.storage_path)
|
||||
crate::infrastructure::services::dedup_service::DedupService::new(&self.storage_path, db_pool.clone())
|
||||
);
|
||||
dedup_service.initialize().await?;
|
||||
|
||||
@@ -416,8 +418,8 @@ impl AppServiceFactory {
|
||||
DomainError::internal_error("Database", "PostgreSQL database is required for blob storage model")
|
||||
})?;
|
||||
|
||||
// 1. Core services
|
||||
let core = self.create_core_services().await?;
|
||||
// 1. Core services (PgPool needed for DedupService index)
|
||||
let core = self.create_core_services(&pool).await?;
|
||||
|
||||
// 2. Repository services (requires PgPool for all metadata)
|
||||
let repos = self.create_repository_services(&core, &pool);
|
||||
@@ -705,12 +707,8 @@ impl Default for AppState {
|
||||
)
|
||||
);
|
||||
|
||||
// Create dummy dedup service
|
||||
let dummy_dedup_service: Arc<dyn DedupPort> = Arc::new(
|
||||
crate::infrastructure::services::dedup_service::DedupService::new(
|
||||
&std::path::PathBuf::from("./storage")
|
||||
)
|
||||
);
|
||||
// Stub dedup service (Default is only used for routing stubs, never for real I/O)
|
||||
let dummy_dedup_service: Arc<dyn DedupPort> = Arc::new(StubDedupPort);
|
||||
|
||||
// Core services using stubs
|
||||
let core_services = CoreServices {
|
||||
|
||||
@@ -533,6 +533,91 @@ impl SearchUseCase for StubSearchUseCase {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DedupPort
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
use crate::application::ports::dedup_ports::{
|
||||
BlobMetadataDto, DedupPort, DedupResultDto, DedupStatsDto,
|
||||
};
|
||||
|
||||
pub struct StubDedupPort;
|
||||
|
||||
#[async_trait]
|
||||
impl DedupPort for StubDedupPort {
|
||||
async fn store_bytes(
|
||||
&self,
|
||||
_content: &[u8],
|
||||
_content_type: Option<String>,
|
||||
) -> Result<DedupResultDto, DomainError> {
|
||||
Err(DomainError::internal_error(
|
||||
"DedupService",
|
||||
"DedupService not initialized",
|
||||
))
|
||||
}
|
||||
|
||||
async fn store_from_file(
|
||||
&self,
|
||||
_source_path: &Path,
|
||||
_content_type: Option<String>,
|
||||
) -> Result<DedupResultDto, DomainError> {
|
||||
Err(DomainError::internal_error(
|
||||
"DedupService",
|
||||
"DedupService not initialized",
|
||||
))
|
||||
}
|
||||
|
||||
async fn blob_exists(&self, _hash: &str) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
async fn get_blob_metadata(&self, _hash: &str) -> Option<BlobMetadataDto> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn read_blob(&self, _hash: &str) -> Result<Vec<u8>, DomainError> {
|
||||
Err(DomainError::internal_error(
|
||||
"DedupService",
|
||||
"DedupService not initialized",
|
||||
))
|
||||
}
|
||||
|
||||
async fn read_blob_bytes(&self, _hash: &str) -> Result<Bytes, DomainError> {
|
||||
Err(DomainError::internal_error(
|
||||
"DedupService",
|
||||
"DedupService not initialized",
|
||||
))
|
||||
}
|
||||
|
||||
async fn add_reference(&self, _hash: &str) -> Result<(), DomainError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn remove_reference(&self, _hash: &str) -> Result<bool, DomainError> {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn hash_bytes(&self, _content: &[u8]) -> String {
|
||||
String::new()
|
||||
}
|
||||
|
||||
async fn hash_file(&self, _path: &Path) -> Result<String, DomainError> {
|
||||
Ok(String::new())
|
||||
}
|
||||
|
||||
async fn get_stats(&self) -> DedupStatsDto {
|
||||
DedupStatsDto::default()
|
||||
}
|
||||
|
||||
async fn flush(&self) -> Result<(), DomainError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn verify_integrity(&self) -> Result<Vec<String>, DomainError> {
|
||||
Ok(Vec::new())
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MetadataCachePort
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user