From 9b135e42b7c4f9a324dbacf46331da41dc4c4268 Mon Sep 17 00:00:00 2001 From: Stefan Hacker Date: Sun, 12 Apr 2026 12:00:15 +0200 Subject: [PATCH] feat: Freigaben-Aenderung live + "Ordner nicht mehr verfuegbar"-Handling Backend: set_permission und remove_permission feuern jetzt ein SSE-Event vom Typ 'permission' an Target-User + Owner + weitere Share-Empfaenger. Damit aktualisieren sich die Dateilisten aller Beteiligten in Echtzeit - auch beim Betroffenen, der gerade seinen Zugriff verliert. Frontend: FilesView wrapped loadFiles in safeLoadCurrentFolder(). Bei 403/404 erscheint ein Toast "Dieser Ordner wurde geloescht oder die Freigabe wurde entfernt" und nach 600ms wird zurueck zum Root navigiert. Greift beim Direktaufruf, beim Ordnerwechsel und bei durch SSE ausgeloesten Auto-Reloads. Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/app/api/files.py | 10 ++++++++++ frontend/src/stores/files.js | 7 +++++++ frontend/src/views/FilesView.vue | 24 +++++++++++++++++++++--- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/backend/app/api/files.py b/backend/app/api/files.py index 979b2ba..9866a0d 100644 --- a/backend/app/api/files.py +++ b/backend/app/api/files.py @@ -665,6 +665,11 @@ def set_permission(file_id): db.session.commit() + # SSE: notify target user (they just got/updated access) + owner + other + # share recipients so everyone's file list refreshes. + notify_file_change(f.owner_id, f.id, 'permission', + shared_with=[target.id, *_share_recipients(f)]) + # Notify user via email if is_new: try: @@ -692,8 +697,13 @@ def remove_permission(file_id, perm_id): if not is_owner and perm.granted_by != user.id: return jsonify({'error': 'Du kannst nur selbst erstellte Freigaben entfernen'}), 403 + target_user_id = perm.user_id db.session.delete(perm) db.session.commit() + + notify_file_change(f.owner_id, f.id, 'permission', + shared_with=[target_user_id, *_share_recipients(f)]) + return jsonify({'message': 'Berechtigung entfernt'}), 200 diff --git a/frontend/src/stores/files.js b/frontend/src/stores/files.js index 89e8dee..bf8d24b 100644 --- a/frontend/src/stores/files.js +++ b/frontend/src/stores/files.js @@ -17,6 +17,13 @@ export const useFilesStore = defineStore('files', () => { const response = await apiClient.get('/files', { params }) files.value = response.data.files breadcrumb.value = response.data.breadcrumb + } catch (err) { + // Let the caller handle access/deletion errors - just clear the list + if (err.response && (err.response.status === 403 || err.response.status === 404)) { + files.value = [] + breadcrumb.value = [] + } + throw err } finally { loading.value = false } diff --git a/frontend/src/views/FilesView.vue b/frontend/src/views/FilesView.vue index e1769c4..ef22f1e 100644 --- a/frontend/src/views/FilesView.vue +++ b/frontend/src/views/FilesView.vue @@ -764,8 +764,26 @@ async function doDelete() { } } +async function safeLoadCurrentFolder() { + try { + await filesStore.loadFiles(currentParentId()) + } catch (err) { + const status = err.response?.status + if (status === 403 || status === 404) { + toast.add({ + severity: 'warn', + summary: 'Kein Zugriff', + detail: 'Dieser Ordner wurde geloescht oder die Freigabe wurde entfernt.', + life: 5000, + }) + // Redirect to root after short delay so user sees the toast + setTimeout(() => router.push('/files'), 600) + } + } +} + watch(() => route.params.folderId, () => { - filesStore.loadFiles(currentParentId()) + safeLoadCurrentFolder() }) // Live updates: subscribe to server-sent events so that lock changes / @@ -778,12 +796,12 @@ function scheduleReload() { if (reloadDebounce) return reloadDebounce = setTimeout(() => { reloadDebounce = null - filesStore.loadFiles(currentParentId()) + safeLoadCurrentFolder() }, 300) } onMounted(() => { - filesStore.loadFiles(currentParentId()) + safeLoadCurrentFolder() if (auth.accessToken) { const url = `/api/sync/events?token=${encodeURIComponent(auth.accessToken)}`