feat(drive): add drive deletion

- conditions: drive must be empty
    - deletion forbidden on main personal drive
This commit is contained in:
Edouard Vanbelle
2026-06-24 21:17:24 +02:00
parent 33cfa876d0
commit 7d24015fc4
15 changed files with 673 additions and 2 deletions
+7
View File
@@ -33,6 +33,12 @@ pub enum ErrorKind {
DatabaseError,
/// Storage quota exceeded
QuotaExceeded,
/// State conflict — the request is well-formed and permitted, but
/// the resource is in a state that refuses it (e.g. "drive must
/// be empty before delete"). Maps to HTTP 409. Distinct from
/// `AlreadyExists` (which is a uniqueness violation) so audit
/// readers can tell them apart.
Conflict,
}
impl Display for ErrorKind {
@@ -48,6 +54,7 @@ impl Display for ErrorKind {
ErrorKind::UnsupportedOperation => write!(f, "Unsupported Operation"),
ErrorKind::DatabaseError => write!(f, "Database Error"),
ErrorKind::QuotaExceeded => write!(f, "Quota Exceeded"),
ErrorKind::Conflict => write!(f, "Conflict"),
}
}
}
@@ -177,6 +177,19 @@ pub trait DriveRepository: Send + Sync + 'static {
subject_ids: &[Uuid],
) -> Result<Vec<DriveWithRootName>, DriveRepositoryError>;
/// `true` when the drive holds no live (non-trashed) folders other
/// than its own root and no live files at all. Used by
/// `DriveManagementService::delete_drive` to enforce the
/// "empty-before-delete" rule — owners must clear / trash the
/// content first so a single click can't wipe a populated drive.
async fn is_empty(&self, drive_id: Uuid) -> Result<bool, DriveRepositoryError>;
/// Hard-delete a drive: its `role_grants` rows, its root folder,
/// and the drive row itself, in one transaction. Caller is
/// responsible for ensuring `is_empty` first; this method does
/// **not** re-check. Returns `NotFound` if the drive id is gone.
async fn delete_atomic(&self, drive_id: Uuid) -> Result<(), DriveRepositoryError>;
/// List every drive on the system, regardless of caller membership.
///
/// Used by the admin panel's `GET /api/admin/drives`. Distinct from