feat(mounts): P2 — read-write REST for external mounts
Adds full CRUD on mount contents, mirroring the P1 read pattern (handlers/ services classify; authorization stays in the service via the mount-root folder grant; the provider does the I/O). - mkdir / rename / delete / move-within branch inside FolderService and FileManagementService (router injected into both) - streaming upload via a new ExternalUploadService: the upload handler detects a mount destination BEFORE the CAS ingest and streams the multipart body straight to the provider (no BLAKE3/dedup). `write_stream` now takes a lifetime-bound boxed stream so the borrowing multipart field can be passed without buffering. - deletes on mounts are permanent (no trash): the trash-first folder handler routes `ext:` ids straight to the provider delete; file delete goes through the branched delete_and_cleanup - cross-backend move/copy (mount ↔ native, or between mounts) is forbidden (UnsupportedOperation); the mount root itself cannot be renamed/moved/deleted - every mutation emits a `target:"audit" event="external_mount.write"` line - shared mount_dto builders synthesize FolderDto/FileDto from a provider MountStat Tests: 529 unit + integration tests for mkdir/rename/delete, file rename/delete, streaming upload, cross-boundary forbid, and stranger-denied — all against real Postgres + a real provider (testcontainers).
This commit is contained in:
+14
-2
@@ -524,7 +524,7 @@ impl AppServiceFactory {
|
||||
let folder_service = Arc::new(FolderService::new(
|
||||
repos.folder_repository.clone(),
|
||||
authz.clone(),
|
||||
mount_router,
|
||||
mount_router.clone(),
|
||||
));
|
||||
|
||||
// Built before the upload/management services so the plugin lifecycle
|
||||
@@ -581,7 +581,15 @@ impl AppServiceFactory {
|
||||
Some(core.file_content_cache.clone()),
|
||||
authz.clone(),
|
||||
)
|
||||
.with_file_lifecycle_hook(file_lifecycle.clone()),
|
||||
.with_file_lifecycle_hook(file_lifecycle.clone())
|
||||
.with_mount_router(mount_router.clone()),
|
||||
);
|
||||
|
||||
// Streams uploads to external mount providers (bypasses the CAS).
|
||||
let external_upload_service = Arc::new(
|
||||
crate::application::services::external_upload_service::ExternalUploadService::new(
|
||||
authz.clone(),
|
||||
),
|
||||
);
|
||||
|
||||
let file_use_case_factory = Arc::new(AppFileUseCaseFactory::new(
|
||||
@@ -618,6 +626,7 @@ impl AppServiceFactory {
|
||||
delta_upload_service,
|
||||
file_retrieval_service,
|
||||
file_management_service,
|
||||
external_upload_service,
|
||||
file_use_case_factory,
|
||||
i18n_service,
|
||||
trash_service, // Already set via parameter
|
||||
@@ -1876,6 +1885,9 @@ pub struct ApplicationServices {
|
||||
Arc<crate::application::services::delta_upload_service::DeltaUploadService>,
|
||||
pub file_retrieval_service: Arc<FileRetrievalService>,
|
||||
pub file_management_service: Arc<FileManagementService>,
|
||||
/// Streams uploads straight to an external mount provider (bypasses the CAS).
|
||||
pub external_upload_service:
|
||||
Arc<crate::application::services::external_upload_service::ExternalUploadService>,
|
||||
pub file_use_case_factory: Arc<dyn FileUseCaseFactory>,
|
||||
pub i18n_service: Arc<I18nApplicationService>,
|
||||
pub trash_service: Option<Arc<TrashService>>,
|
||||
|
||||
Reference in New Issue
Block a user