fix(#104): enforce storage quota on uploads & fix usage tracking

Backend:
- Add QuotaExceeded error kind mapped to HTTP 507 Insufficient Storage
- Add check_storage_quota() and get_user_storage_info() to StorageUsagePort
- Enforce quota in upload_file_with_cache, upload_file_with_thumbnails (AuthUser extractor)
- Enforce quota in chunked upload create_upload handler
- Add update_user_storage_usage_by_username() for username-based lookup
- Fix extract_username_from_path() to handle subfolders (take first segment only)
- Fix maybe_update_storage_usage() to use username-based lookup instead of passing
  username to get_user_by_id (which always failed silently)

Frontend:
- Parse and display quota error messages on upload failure (507 / QuotaExceeded)
- Stop remaining uploads when quota is exceeded
- Call refreshUserData() after uploads to update storage usage display
- Bump service worker cache to v9
This commit is contained in:
Dionisio
2026-02-14 10:34:07 +01:00
parent 1c5cf97cc5
commit 3f60765d9a
9 changed files with 239 additions and 8 deletions
+32 -2
View File
@@ -128,7 +128,15 @@ const fileOps = {
fileRowElements.icon.innerHTML = '<i class="fas fa-exclamation-circle"></i>';
fileRowElements.icon.classList.add('error');
}
resolve({ ok: false });
// Parse error body for quota-exceeded or other messages
let errorMsg = null;
let isQuotaError = false;
try {
const errBody = JSON.parse(xhr.responseText);
errorMsg = errBody.error || null;
isQuotaError = errBody.error_type === 'QuotaExceeded' || xhr.status === 507;
} catch (_) {}
resolve({ ok: false, errorMsg, isQuotaError });
}
});
@@ -208,7 +216,14 @@ const fileOps = {
console.log(`Successfully uploaded ${file.name}`, result.data);
} else {
console.error(`Upload error for ${file.name}`);
window.ui.showNotification('Error', `Error uploading file: ${file.name}`);
if (result.isQuotaError) {
const msg = result.errorMsg || window.i18n?.t('storage_quota_exceeded') || 'Storage quota exceeded';
window.ui.showNotification('Error', `${file.name}: ${msg}`);
// Stop uploading remaining files — quota is full
break;
} else {
window.ui.showNotification('Error', `Error uploading file: ${file.name}`);
}
}
}
@@ -218,6 +233,11 @@ const fileOps = {
// Wait for backend to persist, then reload
await new Promise(resolve => setTimeout(resolve, 800));
// Refresh storage usage display
if (typeof window.refreshUserData === 'function') {
try { await window.refreshUserData(); } catch (_) {}
}
try {
await window.loadFiles({ forceRefresh: true });
} catch (reloadError) {
@@ -330,6 +350,11 @@ const fileOps = {
console.log(`Uploaded: ${file.webkitRelativePath}`);
} else {
console.error(`Error uploading ${file.webkitRelativePath}`);
if (result.isQuotaError) {
const msg = result.errorMsg || window.i18n?.t('storage_quota_exceeded') || 'Storage quota exceeded';
window.ui.showNotification('Error', `${file.name}: ${msg}`);
break;
}
}
}
@@ -337,6 +362,11 @@ const fileOps = {
this._finishUploadToast(successCount, totalFiles);
await new Promise(resolve => setTimeout(resolve, 800));
// Refresh storage usage display
if (typeof window.refreshUserData === 'function') {
try { await window.refreshUserData(); } catch (_) {}
}
try {
await window.loadFiles({ forceRefresh: true });