feat(drive): add quota calculus per drive

- quota per drive
    - add 2 internal API endpoints to test purpose
        disabled by default, enable it via `OXICLOUD_ENABLE_ADMIN_INTERNAL_ENDPOINTS=true`

        this enable:

            /api/admin/internal/trigger-sweep
                to sweep the trash and recalculated quota

            /api/admin/internal/trigger-gc
                to garbage orphan blobs

        use full for end to end tests and validate lifecycles
This commit is contained in:
Edouard Vanbelle
2026-06-24 22:28:15 +02:00
parent b73f176024
commit a9a8604322
8 changed files with 391 additions and 17 deletions
@@ -214,7 +214,7 @@ impl ChunkedUploadHandler {
.await
{
tracing::warn!(
"⛔ CHUNKED UPLOAD REJECTED (quota): user={}, file={}, size={} — {}",
"⛔ CHUNKED UPLOAD REJECTED (user quota): user={}, file={}, size={} — {}",
auth_user.username,
request.filename,
request.total_size,
@@ -230,6 +230,37 @@ impl ChunkedUploadHandler {
.into_response();
}
// ── Per-drive quota (D4) ─────────────────────────────────
// Native-chunked declares `total_size` at session creation,
// so we can refuse here before any chunk is accepted — same
// wasted-bandwidth optimisation the multipart path has via
// the post-ingest check. No folder_id means root-level which
// the folder-permission check above already rejects.
if let Some(storage_svc) = state.storage_usage_service.as_ref()
&& let Some(fid_str) = request.folder_id.as_deref()
&& let Ok(fid) = uuid::Uuid::parse_str(fid_str)
&& let Err(err) = storage_svc
.check_drive_quota_by_folder(fid, request.total_size)
.await
{
tracing::warn!(
"⛔ CHUNKED UPLOAD REJECTED (drive quota): user={}, folder={}, file={}, size={} — {}",
auth_user.username,
fid,
request.filename,
request.total_size,
err.message
);
return (
StatusCode::INSUFFICIENT_STORAGE,
Json(serde_json::json!({
"error": err.message,
"error_type": "QuotaExceeded"
})),
)
.into_response();
}
// Validate chunk size if provided
let chunk_size = request.chunk_size.unwrap_or(DEFAULT_CHUNK_SIZE);
if chunk_size < 1024 * 1024 {