feat(drive): add drive deletion
- conditions: drive must be empty
- deletion forbidden on main personal drive
This commit is contained in:
@@ -98,6 +98,7 @@ pub fn admin_routes() -> Router<Arc<AppState>> {
|
||||
// Drives — admin-wide view (distinct from `/api/drives` which
|
||||
// is filtered to the caller's role grants).
|
||||
.route("/drives", get(list_all_drives))
|
||||
.route("/drives/{id}", delete(delete_drive_admin))
|
||||
.route(
|
||||
"/drives/{id}/members",
|
||||
get(list_drive_members_admin).post(add_drive_member_admin),
|
||||
@@ -1957,3 +1958,40 @@ pub async fn remove_drive_member_admin(
|
||||
.map_err(AppError::from)?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
/// `DELETE /api/admin/drives/{id}` — admin-only drive delete (D3b).
|
||||
///
|
||||
/// Same shape as the user-facing `DELETE /api/drives/{id}`, but
|
||||
/// bypasses the per-drive `Manage` check (the admin guard at the
|
||||
/// route edge is the access control). The remaining invariants —
|
||||
/// default Personal drive is undeletable, drive must be empty — still
|
||||
/// apply: an admin can't accidentally wipe a populated drive or the
|
||||
/// default home folder of any user. Audit emits
|
||||
/// `drive.deleted_via_admin` on success.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/admin/drives/{id}",
|
||||
params(("id" = Uuid, Path, description = "Drive UUID")),
|
||||
responses(
|
||||
(status = 204, description = "Drive deleted"),
|
||||
(status = 401, description = "Unauthorized"),
|
||||
(status = 403, description = "Admin required"),
|
||||
(status = 405, description = "Default Personal drive — undeletable"),
|
||||
(status = 409, description = "Drive is not empty"),
|
||||
),
|
||||
security(("bearerAuth" = [])),
|
||||
tag = "admin"
|
||||
)]
|
||||
pub async fn delete_drive_admin(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
axum::extract::Path(drive_id): axum::extract::Path<Uuid>,
|
||||
) -> Result<impl IntoResponse, AppError> {
|
||||
let (admin_id, _) = admin_guard(&state, &headers).await?;
|
||||
state
|
||||
.drive_management_service
|
||||
.delete_drive(admin_id, true, drive_id)
|
||||
.await
|
||||
.map_err(AppError::from)?;
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
}
|
||||
|
||||
@@ -356,3 +356,42 @@ pub async fn remove_drive_member(
|
||||
Err(e) => AppError::from(e).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
/// `DELETE /api/drives/{id}` — Owner-only deletion (D3b).
|
||||
///
|
||||
/// Refuses (per `DriveManagementService::delete_drive`):
|
||||
/// - `404` when the caller lacks Manage on the drive (anti-enum).
|
||||
/// - `405` when the drive is the user's default Personal drive.
|
||||
/// - `409` when the drive still holds live folders/files; the caller
|
||||
/// must trash or move them first.
|
||||
///
|
||||
/// On success the drive row, its root folder, and every role grant
|
||||
/// scoped to the drive are removed in one transaction; cached drive
|
||||
/// roles are invalidated.
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/api/drives/{id}",
|
||||
params(("id" = Uuid, Path, description = "Drive UUID")),
|
||||
responses(
|
||||
(status = 204, description = "Drive deleted"),
|
||||
(status = 404, description = "Drive not found or caller lacks Manage"),
|
||||
(status = 405, description = "Default Personal drive — undeletable"),
|
||||
(status = 409, description = "Drive is not empty — move/trash contents first"),
|
||||
),
|
||||
security(("bearerAuth" = [])),
|
||||
tag = "drives"
|
||||
)]
|
||||
pub async fn delete_drive(
|
||||
State(state): State<Arc<AppState>>,
|
||||
auth_user: AuthUser,
|
||||
Path(drive_id): Path<Uuid>,
|
||||
) -> impl IntoResponse {
|
||||
match state
|
||||
.drive_management_service
|
||||
.delete_drive(auth_user.id, false, drive_id)
|
||||
.await
|
||||
{
|
||||
Ok(()) => StatusCode::NO_CONTENT.into_response(),
|
||||
Err(e) => AppError::from(e).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -425,6 +425,10 @@ pub fn create_api_routes(app_state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||
"/",
|
||||
get(drive_handler::list_drives).post(drive_handler::create_drive),
|
||||
)
|
||||
.route(
|
||||
"/{id}",
|
||||
axum::routing::delete(drive_handler::delete_drive),
|
||||
)
|
||||
.route(
|
||||
"/{id}/members",
|
||||
get(drive_handler::list_drive_members).post(drive_handler::add_drive_member),
|
||||
|
||||
Reference in New Issue
Block a user