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) <noreply@anthropic.com>
This commit is contained in:
Stefan Hacker 2026-04-12 12:00:15 +02:00
parent 9369c851a0
commit 9b135e42b7
3 changed files with 38 additions and 3 deletions

View File

@ -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

View File

@ -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
}

View File

@ -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)}`