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:
@@ -177,6 +177,22 @@ impl FolderHandler {
|
||||
Path(id): Path<String>,
|
||||
) -> impl IntoResponse {
|
||||
let user_id = auth_user.id;
|
||||
|
||||
// External mounts have no trash — a permanent provider delete is the
|
||||
// only option. Route `ext:` ids straight to the mount-aware service
|
||||
// delete, skipping the (always-failing) trash attempt.
|
||||
if state.mount_router.is_mount_id(&id) {
|
||||
return match state
|
||||
.applications
|
||||
.folder_service
|
||||
.delete_folder_with_perms(&id, user_id)
|
||||
.await
|
||||
{
|
||||
Ok(_) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(err) => AppError::from(err).into_response(),
|
||||
};
|
||||
}
|
||||
|
||||
// Check if trash service is available
|
||||
// FIXME: permissions !!
|
||||
if let Some(trash_service) = &state.trash_service {
|
||||
|
||||
Reference in New Issue
Block a user