Bisher wurde immer die komplette calendar-data mitgeschickt, auch
wenn der Client nur getetag wollte. DAVx5 macht einen zweistufigen
Sync: erst calendar-query nach ETags, dann multiget fuer die
neuen/geaenderten Events. Server-seitig zu viel zu liefern bricht
diesen Ablauf - Client denkt er hat alles und ueberspringt die
zweite Stufe, aber die Events landen nicht in der Android-Kalender-
DB.
Jetzt: calendar-query schaut nach ob <c:calendar-data/> in den
angefragten Props steht und liefert entsprechend.
calendar-multiget liefert weiterhin immer die vollen Daten.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DAVx5 sendet bei calendar-query oft nur <time-range start=.../>
ohne end. Mein Code hat dann blind CalendarEvent.dtstart < None
gefiltert, was SQLAlchemy mit TypeError abbrechen liess - Ergebnis
HTTP 500, Sync scheitert komplett.
Zwei Korrekturen:
* end-Filter wird nur gesetzt wenn end wirklich vorhanden ist
* time-range-Parser strippt tzinfo, damit Vergleiche mit den
tz-naiven DB-Spalten keine Exception werfen
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* GET-Route akzeptiert jetzt auch HEAD - manche Clients pruefen
Existenz einer Ressource via HEAD bevor sie GET senden.
* Neue PROPPATCH-Route auf der Kalender-Collection: erkennt
calendar-color + displayname und persistiert beides. Andere
Properties werden als "angewendet" bestaetigt, damit DAVx5
und Apple Calendar nicht enttaeuscht sind.
Damit sollten die 500-Fehler beim Sync verschwinden. Falls nicht,
bitte Server- oder DAVx5-Log posten.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bisher war der href in der Response immer /dav/, auch wenn DAVx5
einen PROPFIND auf / oder /.well-known/caldav gemacht hat. Das
kann Clients verwirren - die erwarten, dass der Response-Pfad zum
angefragten Pfad passt. current-user-principal zeigt weiterhin
korrekt auf /dav/Adam/.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DAVx5 macht beim Account-Setup zuerst PROPFIND auf / bevor es
/.well-known/caldav probiert. Der Server antwortete mit 405
Method Not Allowed (weil / nur fuer SPA-GET registriert war),
woraufhin DAVx5 den gesamten Server als "kein DAV" verwirft.
Jetzt: PROPFIND und OPTIONS auf / werden an die DAV-Handler
delegiert (gleiches Verhalten wie auf /dav/). GET/HEAD auf /
laeuft unveraendert zur SPA.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Flask hat trotz expliziter OPTIONS-Route Auto-OPTIONS generiert,
wodurch der DAV-Header fehlte. DAVx5 sieht so keinen Calendar-
Access und lehnt den Server ab.
Konsolidiert zu einem Handler mit method-basiertem Dispatch und
provide_automatic_options=False, damit Flask nicht dazwischenfunkt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Aenderungen fuer besseren DAVx5-Support:
* /.well-known/caldav reagiert jetzt direkt auf PROPFIND/OPTIONS
ohne Redirect-Zickerei. GET/HEAD redirecten weiterhin auf /dav/
als visuelle Fallback.
* strict_slashes app-weit aus: /dav und /dav/ sind gleichwertig,
ebenso die Unterpfade. DAVx5 nutzt beides gemischt.
* Jede DAV-Response traegt jetzt den DAV-Header (1, 2, 3,
calendar-access), nicht nur OPTIONS.
* Kalender-Response enthaelt jetzt supported-report-set mit
calendar-query + calendar-multiget (DAVx5 prueft das).
* current-user-privilege-set wird mit konkreten Privilegien gefuellt
(read, write, write-properties, write-content, bind, unbind)
statt leer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Property-Elemente wurden unter einem Container mit demselben Tag
erzeugt, z.B.:
<current-user-principal>
<current-user-principal> <!-- falsch, doppelt -->
<href>/dav/adam/</href>
</current-user-principal>
</current-user-principal>
Clients wie DAVx5 und Thunderbird erkennen dadurch den Principal
nicht und melden "Kein CalDAV-Dienst gefunden". XML-Generierung
umgebaut - Response-Helfer bekommen jetzt eine populate_prop-
Callback, die die tatsaechlichen Property-Children direkt ins
<prop>-Element setzt.
Zusaetzlich:
* /.well-known/caldav und /carddav akzeptieren jetzt auch PROPFIND,
OPTIONS, HEAD (einige Clients halten die Methode beim ersten
Aufruf bei).
* Kalender-Response enthaelt current-user-privilege-set (leer, als
Signal dass der Client nicht ACL-abhaengig pruefen muss).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CalendarShare bekommt color-Spalte. Im Kalender-Menue kann jeder
Benutzer eine eigene Anzeigefarbe fuer einen mit ihm geteilten
Kalender setzen, ohne dass sich dadurch die Farbe beim
Eigentuemer oder anderen Share-Empfaengern aendert.
* Owner: Farbe aendert den Kalender direkt (wie bisher).
* Share-Empfaenger: Farbe landet in CalendarShare.color und wird
nur fuer ihn ausgeliefert (list_calendars injiziert sie in
'color', Owner-Farbe bleibt in 'owner_color' als Referenz).
Neuer Endpoint: PUT /calendars/<id>/my-color.
UI-Hinweis: "Nur fuer deine Ansicht - <Owner> behaelt seine Farbe".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
Neuer Event-Typ 'calendar' im Broadcaster. Wird bei Event-CRUD,
Serien-Ausnahmen, Freigaben hinzufuegen/entfernen und beim
Loeschen ganzer Kalender emittiert. Empfaenger: Eigentuemer +
alle User mit CalendarShare auf dem jeweiligen Kalender.
Frontend:
CalendarView oeffnet beim Mount eine EventSource zu
/api/sync/events und reloaded Kalenderliste + Events bei jedem
'calendar'-Event (300ms debounced). Damit sehen beteiligte
Nutzer Aenderungen in praktisch Echtzeit - kein F5 mehr noetig.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
User.calendars hat bereits backref='owner', mein zusaetzlich
hinzugefuegtes Calendar.owner kollidierte damit und SQLAlchemy
weigerte sich, die Mappers zu initialisieren ("Error creating
backref 'owner'..."). Damit waren alle Auth-Endpoints tot.
Jetzt nur noch Kommentar, die backref uebernimmt die Aufgabe.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sharing-Fix:
Calendar-Model hatte keine owner-Relation zu User - list_calendars
stuerzte beim Listen geteilter Kalender ab (c.owner.username ->
AttributeError). Jetzt mit explizitem foreign_keys Relationship.
Benutzer-Autocomplete:
"Kalender teilen" nutzt jetzt /users/search wie bei Dateien.
Tippt man 2+ Zeichen, erscheint ein Dropdown mit passenden
Benutzernamen. Klick uebernimmt den Namen.
Bestehende Freigaben werden im Menue angezeigt mit Muelleimer
zum Entfernen.
Privat-Flag fuer Termine:
CalendarEvent bekommt is_private-Spalte. Checkbox im Termin-
Dialog "🔒 Privat (Teilnehmer sehen nur den Zeitblock)".
Redaction greift an drei Stellen:
* GET /events: Nicht-Owner sehen summary="Privat", description
und location = null. Zeitfenster bleibt voll sichtbar.
* iCal-Export (/ical/<token>): Privat-Events werden mit
CLASS:PRIVATE ausgegeben und SUMMARY/DESCRIPTION/LOCATION
werden gestrippt.
* CalDAV: aktuell werden eh nur eigene Kalender exportiert,
also keine Redaction noetig. Kommt bei Share-Support rein.
Der Eigentuemer sieht natuerlich in seiner eigenen Ansicht alle
Details seines privaten Termins.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Vollstaendige CalDAV-Implementierung unter /dav/ - Thunderbird,
DAVx5, Apple Calendar und Outlook (CalDAV-Synchronizer) koennen
sich einfach ueber HTTP-Basic-Auth mit ihrem Mini-Cloud-Account
anmelden und ihre Kalender synchronisieren.
Unterstuetzte Methoden:
* OPTIONS - DAV-Capabilities
* PROPFIND - Discovery, Principal, Calendar-Home, Kalender,
Termin-Listings (Depth 0/1 beachtet)
* REPORT - calendar-query + calendar-multiget mit
optionalem Zeitraumfilter (<time-range>)
* GET - einzelner Termin als VCALENDAR
* PUT - Termin erstellen/aktualisieren (mit ETag-Check
via If-Match + If-None-Match)
* DELETE - Termin oder ganzer Kalender
* MKCALENDAR - neuen Kalender vom Client aus anlegen
iCal-Parser verarbeitet SUMMARY, DESCRIPTION, LOCATION, DTSTART,
DTEND, RRULE, EXDATE - inklusive Line-Folding (RFC 5545).
Ganztages-Termine (VALUE=DATE) werden korrekt erkannt.
ETags basieren auf updated_at-Zeitstempel und werden pro
PUT-Response zurueckgegeben, damit Clients Konflikte erkennen.
nginx.example.conf: /dav/ mit proxy_request_buffering off fuer
groessere PUTs und Weiterleitung der .well-known-URLs.
README: eigener "CalDAV-Zugriff"-Block mit Tabelle pro Client.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Klick auf einen wiederkehrenden Termin oeffnet zuerst einen Dialog:
"Nur diesen Termin" oder "Ganze Serie".
* Serie: bearbeitet den Master wie bisher
* Nur dieser: fuegt EXDATE fuer das geklickte Datum zum Master
hinzu und legt einen eigenstaendigen Ersatz-Termin mit den
bearbeiteten Daten an
Backend:
* CalendarEvent.exdates speichert Ausnahmedaten kommasepariert
* POST /events/<id>/exception fuegt EXDATE hinzu, erstellt
optional das Replacement-Event mit frischer UID
* _build_vevent schreibt jetzt EXDATE-Zeilen in die ical_data,
sodass CalDAV-Clients die Ausnahmen auch sehen werden
Frontend:
* FullCalendar rrule-Plugin bekommt die exdate-Liste und blendet
die uebersprungenen Tage aus
* Drag & Drop verschiebt weiterhin die ganze Serie (Shortcut -
fuer Einzelverschiebung Termin anklicken und bearbeiten)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Neues Berechtigungs-Modell fuer Benutzerfreigaben:
* FilePermission bekommt zwei neue Spalten:
- can_reshare (bool): darf dieser Nutzer die Freigabe weiterverteilen?
- granted_by (user_id): wer hat diese Freigabe erstellt?
* set_permission / create_share_link erlauben jetzt auch Nicht-Owner,
sofern sie can_reshare haben. Dabei gilt:
- Lesend + reshare -> kann nur lesend weiterteilen
- Schreibend + reshare -> kann lesend ODER schreibend weiterteilen
- Admin kann nur der Eigentuemer vergeben
- Jeder Re-Sharer kann wiederum can_reshare weitergeben
* remove_permission: Owner kann alle Freigaben entfernen; Re-Sharer
nur die von ihnen selbst erstellten.
* get_permissions: Owner sieht alle; Re-Sharer nur selbst-erstellte.
* list_files liefert my_permission + my_can_reshare pro Eintrag -
Frontend kann Rename/Delete/Share-Buttons gezielt ein- und
ausblenden statt blind alle anzuzeigen.
Frontend:
* Rename/Delete-Buttons nur fuer Write-Zugriff
* Share-Button nur fuer Owner oder Re-Sharer
* "darf weiterteilen" Checkbox neben Permission-Dropdown im Dialog
* Dropdown-Optionen nach eigenem Level gefiltert (Re-Sharer sieht
keine hoeheren Stufen als seine eigene)
* Hinweis-Text "Du hast X - du kannst maximal X weiterteilen"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
/files/<id>/save (Text/HTML/Spreadsheet) und der OnlyOffice-
Callback aktualisierten Inhalt + Checksum, riefen aber
notify_file_change nicht auf. Der Client bekam dadurch keinen
SSE-Trigger und merkte die neue Server-Version erst beim naechsten
30s-Fallback-Sync - wenn ueberhaupt.
Jetzt: beide Endpoints emittieren 'updated' an Owner + Share-
Empfaenger, Desktop- und Web-Clients reagieren sofort.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
list_files filterte Kinder-Dateien nach owner_id=current_user, wodurch
in einem freigegebenen Ordner (der einem anderen User gehoert) keine
Dateien angezeigt wurden. Jetzt wird beim Betreten eines Ordners die
Zugriffsberechtigung geprueft; bei eigenem Ordner wie gehabt, bei
freigegebenem Ordner werden alle Kinder-Dateien gelistet.
_check_file_access laeuft jetzt auch den Ordner-Baum hoch, damit
eine Permission auf einem Vorfahren-Ordner automatisch Zugriff auf
alle Nachkommen gewaehrt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Download-Endpoint unterstuetzt jetzt ?inline=1, wodurch
Content-Disposition auf inline statt attachment gesetzt wird.
PDF- und Bild-Preview nutzen diesen Parameter, damit der
Browser das PDF im Preview-Iframe rendert statt einen Download
auszuloesen. Normale Download-Buttons bleiben unveraendert.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Desktop-Client komplett ueberarbeitet nach Nextcloud-Vorbild:
- Persistentes SQLite-Journal (journal.rs) speichert letzten bekannten
Stand pro Datei - ueberlebt Client-Neustarts (Hauptbug behoben).
- Engine.rs neu: 3-Wege-Vergleich Local <-> Journal <-> Server mit
sauberer Konflikt-Kopie (inkl. Username + Zeitstempel).
- Loesch-Propagation: Lokal geloeschte Dateien landen im Server-
Papierkorb des Owners (auch bei Freigaben). Auf dem Server
geloeschte Dateien werden lokal entfernt.
- Lock-Flow repariert: frischer Token bei jedem Call, Fehler-Feedback.
Echtzeit-Sync:
- Backend: SSE-Endpoint /api/sync/events mit In-Memory-Broadcaster.
Events bei Create/Update/Delete/Lock/Unlock, Zustellung an Owner
plus alle User mit Share-Permission.
- Client: persistente SSE-Verbindung mit Auto-Reconnect. Events
triggern sofortigen Sync (<100ms). 30s-Polling bleibt als
Fallback fuer Netzwerk-Aussetzer.
Weitere Fixes:
- /api/sync/tree filtert is_trashed=False (Papierkorb wird nicht
mehr an Clients gesynct).
- Web-GUI: Lock/Unlock-Buttons pro Datei, Admin darf fremde Locks
zwangsweise loesen. Rename/Delete disabled bei fremdem Lock.
- Lock-Check im Backend bei PUT/DELETE (423 Locked Response).
- Background-Sync nur noch einmal pro Prozess gestartet, liest
sync_paths pro Iteration neu - add/remove wirkt sofort, kein
Client-Neustart mehr noetig.
- Watcher werden pro Sync-Pfad individuell verwaltet.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Problem: Lock verschwand nach 5 Minuten weil:
1. JWT-Token nach 15 Min ablief -> Heartbeat schlug still fehl
2. Server gab Lock nach 5 Min ohne Heartbeat frei
Fix Client:
- Token-Refresh alle 10 Minuten (vor dem 15-Min-Ablauf)
- Aktualisiert den Token in der shared API-Instanz
- Heartbeat nutzt immer den aktuellen Token
Fix Backend:
- Lock-Timeout von 5 auf 15 Minuten erhoeht
- Genug Puffer fuer Netzwerk-Probleme oder kurze Unterbrechungen
Timeline:
0s -> Lock + Heartbeat alle 10s
600s -> Token-Refresh
900s -> Lock wuerde erst jetzt ablaufen (15 Min ohne Heartbeat)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Upload-Auth:
- Akzeptiert jetzt sowohl SECRET_KEY als auch JWT_SECRET_KEY
(BUILD_UPLOAD_TOKEN in Entwicklungs-.env kann einer von beiden sein)
Settings-View:
- Zeigt verfuegbare Desktop/Mobile Clients zum Download an
(nur wenn mindestens ein Client vorhanden)
- Pro Client: Name, Dateiname, Download-Button
.env.example:
- Klarere Kommentare: "SECRET_KEY oder JWT_SECRET_KEY des Zielservers"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Backend: Upload-Auth prueft SECRET_KEY statt eigenen Token
(ein Token weniger zu verwalten)
- BUILD_UPLOAD_TOKEN in Entwicklungs-.env = SECRET_KEY vom Server
- .env.example: Klarer Kommentar dass CLOUD_URL + BUILD_UPLOAD_TOKEN
NUR auf der Entwicklungsmaschine gesetzt werden, nicht auf dem Server
- README: Desktop Sync Client Abschnitt mit Build-Anleitung und
Auto-Upload-Erklaerung
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- GET /api/clients - Verfuegbare Clients auflisten (oeffentlich)
- GET /api/clients/<platform>/download - Client herunterladen (oeffentlich)
- POST /api/clients/<platform>/upload - Build hochladen (BUILD_UPLOAD_TOKEN)
- Alte Version wird automatisch bei neuem Upload ersetzt
- Plattformen: linux, windows, mac, android, ios
Frontend:
- /clients - Download-Seite mit Grid aller verfuegbaren Clients
- Login-Seite zeigt "Desktop & Mobile Clients herunterladen" Link
wenn mindestens ein Client verfuegbar ist
build.sh:
- Nach jedem Build wird der Client automatisch auf CLOUD_URL
hochgeladen (wenn CLOUD_URL + BUILD_UPLOAD_TOKEN in .env gesetzt)
- Bestes Format pro Plattform: AppImage > .deb > Binary (Linux),
.msi > .exe (Windows), .dmg (Mac), .apk (Android), .ipa (iOS)
.env.example:
- CLOUD_URL: Oeffentliche URL der Cloud-Instanz
- BUILD_UPLOAD_TOKEN: Auth-Token fuer Build-Upload
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend - FileLock Model + API:
- POST /files/<id>/lock - Datei auschecken (sperren)
- POST /files/<id>/unlock - Datei einchecken (entsperren)
- POST /files/<id>/heartbeat - "Datei noch offen" (alle 60s)
- GET /files/<id>/lock-status - Sperrstatus abfragen
- GET /files/locks - Alle aktiven Sperren auflisten
- Auto-Unlock: Kein Heartbeat seit 5 Min -> Sperre wird freigegeben
- 423 Locked wenn bereits von anderem User gesperrt
- Admin kann fremde Sperren aufheben
Dateiliste + Sync-API:
- Lock-Info (locked, locked_by, locked_at) pro Datei mitgeliefert
- Sync-Tree enthaelt Lock-Status fuer Desktop/Mobile-Clients
Web-UI:
- Schloss-Icon mit Benutzername bei gesperrten Dateien
- Tooltip: "Ausgecheckt von Adam seit 14:30"
- Gesperrte Dateien: "Oeffnen nicht moeglich" Toast-Meldung
(eigene Sperren sind erlaubt)
Konflikt-Email an Admin:
- Wer hat die Konflikt-Kopie erstellt (Name + Email)
- Welche Datei (Name + Ordnerpfad)
- Name der Konflikt-Kopie
- Von wem gesperrt (Name + Email + seit wann)
- Erklaerungstext was passiert ist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- forcesavetype in Editor-Config: Ctrl+S speichert sofort zurueck
zum Server (statt erst beim Schliessen des Dokuments)
- ALLOW_PRIVATE_IP_ADDRESS + ALLOW_META_IP_ADDRESS fuer OnlyOffice
damit Callbacks an interne Docker-IPs funktionieren
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Document-Key nutzt jetzt Timestamp statt Checksum, damit OnlyOffice
bei jedem Oeffnen die aktuelle Version vom Server laedt statt eine
gecachte alte Version anzuzeigen.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Der @api_bp.route('/files/onlyoffice-callback') Dekorator war
versehentlich auf oo_download statt onlyoffice_callback gelandet.
Flask routete dadurch alle Callback-POSTs an oo_download, die dann
mit 'missing access_key argument' crashte (500 Error).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Gesamter Callback in try/except gewrapped (gibt immer error:0
zurueck, damit OnlyOffice nicht endlos retryt)
- JWT Body-Decoding mit graceful fallback auf Raw-Daten
- JWT-Header-Validierung entfernt (verursachte den 500 Crash)
- Download ohne extra JWT-Header (OnlyOffice-interne URLs
brauchen das nicht)
- Ausfuehrliches Logging: Status, Key, Dateiname, Groesse
- Saubere Imports am Anfang der Funktion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Problem: OnlyOffice sendete JWT-Token im Callback-Request und im
Body, unser Endpoint hat das ignoriert -> Speichern schlug fehl.
Fix:
- Callback validiert OnlyOffice JWT aus Authorization-Header
- Callback entpackt JWT-wrapped Body (OnlyOffice wraps den Body
in einen JWT-Token wenn JWT_ENABLED=true)
- Download der gespeicherten Datei sendet JWT-Header mit
- Besseres Error-Logging mit Traceback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Problem: OnlyOffice konnte Dateien nicht herunterladen weil unser
token_required-Decorator den Request ablehnte - OnlyOffice sendet
eigene Header die mit unserem JWT-System kollidieren.
Loesung: Eigener Endpunkt GET /files/oo-download/<access_key>
- Kein JWT noetig, stattdessen Einmal-Schluessel
- Schluessel wird beim Oeffnen des Editors generiert und in der DB gespeichert
- Schluessel enthaelt file_id + user_id, wird beim Download validiert
- OnlyOffice ruft diesen Endpunkt intern auf (http://minicloud:5000)
- Kein Token in der URL, keine JWT-Konflikte
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Problem: OnlyOffice versuchte Dateien ueber die oeffentliche URL
herunterzuladen (http://selftestcloud...) und bekam 401 weil der
Access-Token nach 15 Min. ablief.
Fix:
- Download-URL und Callback-URL nutzen jetzt die interne Docker-URL
(http://minicloud:5000) statt die oeffentliche URL
- Eigener 24h-Token fuer OnlyOffice Datei-Zugriff (statt des
kurzlebigen User-Access-Tokens)
- ONLYOFFICE_INTERNAL_URL konfigurierbar (Default: http://minicloud:5000)
So bleibt der gesamte Dateizugriff zwischen OnlyOffice und Mini-Cloud
im Docker-Netzwerk - schneller und kein externer Roundtrip.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- OnlyOffice und Mini-Cloud teilen sich den gleichen JWT_SECRET_KEY
- ONLYOFFICE_JWT_SECRET komplett entfernt (aus .env, docker-compose, Backend, Frontend)
- docker-compose: OnlyOffice liest JWT_SECRET=${JWT_SECRET_KEY}
- In .env nur noch ONLYOFFICE_URL setzen, fertig
- Admin-GUI zeigt: URL + "JWT nutzt JWT_SECRET_KEY aus .env"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OnlyOffice URL und JWT Secret kommen jetzt ausschliesslich aus der
.env Datei (Umgebungsvariablen), nicht mehr aus der Admin-GUI:
- ONLYOFFICE_URL und ONLYOFFICE_JWT_SECRET in .env setzen
- docker-compose liest das gleiche Secret fuer den OnlyOffice-Container
- Eine Quelle der Wahrheit, kein Sync zwischen .env und DB noetig
Admin-GUI zeigt jetzt nur noch den Status an:
- Konfiguriert / Nicht konfiguriert (Tag)
- Aktuelle URL
- JWT Secret gesetzt / Fehlt (Tag)
- Setup-Anleitung mit .env Beispiel
Behebt: "Sicherheitstoken nicht korrekt" wenn OnlyOffice laeuft
aber JWT Secret nicht uebereinstimmt
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Problem: Nach Umstellung auf env_file in docker-compose wurden die
relativen Pfade (./data/minicloud.db) aus .env falsch aufgeloest.
basedir zeigte auf / statt /app, dadurch wurde eine neue leere DB
unter /data/ erstellt statt die bestehende unter /app/data/ zu nutzen.
Ergebnis: Alle User weg, Login unmoeglich.
Fix:
- config.py: _resolve_path nutzt Path.cwd() fuer relative Pfade
(in Docker CWD=/app, in Dev CWD=backend/)
- .env.example: Absolute Docker-Pfade als Default
(/app/data/minicloud.db statt ./data/minicloud.db)
mit Kommentar fuer Entwicklungsumgebung
Auf dem Server muss die .env angepasst werden:
DATABASE_PATH=/app/data/minicloud.db
UPLOAD_PATH=/app/data/files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OnlyOffice Integration:
- DOCX, XLSX, PPTX nativ im Browser bearbeiten (wie Google Docs)
- Automatische Erkennung: Wenn OnlyOffice konfiguriert ist, wird der
vollwertige Editor geladen, sonst die einfache Vorschau als Fallback
- Backend: WOPI-aehnliche Endpunkte
- GET /files/<id>/onlyoffice-config - Editor-Konfiguration
- POST /files/onlyoffice-callback - Speicher-Callback von OnlyOffice
- GET /files/onlyoffice-status - Verfuegbarkeits-Check
- JWT-Signierung fuer sichere Kommunikation mit OnlyOffice
- Dokument-Key basiert auf file_id + checksum (Cache-Invalidierung)
Admin-Einstellungen:
- OnlyOffice URL + JWT Secret konfigurierbar
- Setup-Anleitung direkt in der UI (docker-compose auskommentieren)
docker-compose.yml:
- OnlyOffice Document Server als optionaler Service (auskommentiert)
- Einfach auskommentieren fuer volle Office-Bearbeitung
Preview:
- Oeffnet sich jetzt innerhalb der App (kein neuer Tab)
- Zurueck-Button in der Toolbar
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- share_delete_file nutzt _trash_recursive() statt db.session.delete()
- Geloeschte Dateien aus Share-Links landen im Papierkorb des Eigentümers
- Share-Dateiliste filtert getrashte Dateien aus (is_trashed=False)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Papierkorb:
- Dateien/Ordner werden beim Loeschen in den Papierkorb verschoben
(Soft-Delete) statt sofort geloescht
- Papierkorb-Seite in der Sidebar mit Tabelle aller geloeschten Elemente
- Pro Element: Wiederherstellen (am Originalort) oder endgueltig loeschen
- "Papierkorb leeren" Button loescht alles unwiderruflich
- Backend: is_trashed, trashed_at, original_parent_id Felder im File-Model
- Getrashte Dateien erscheinen nicht in der normalen Dateiliste
Bestaetigungsdialoge (vorher fehlend):
- Kontakte: "Moechtest du XY wirklich loeschen?"
- Kalender Events: Bestaetigung vor dem Loeschen
- Kalender: Bestaetigung vor dem Loeschen (mit Hinweis auf Events)
- E-Mail Nachrichten: Bestaetigung mit Betreff-Vorschau
- Share-Link Dateien: Bestaetigung beim Loeschen aus geteiltem Ordner
- Admin SFTP-Backup-Ziele: Bestaetigung
- Admin Email-Konten: Bestaetigung
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Problem: window.location.href sendet keinen Authorization-Header,
daher scheiterten alle direkten Downloads (Dateien + Ordner-ZIP)
mit 'Token fehlt'.
Loesung:
- Backend: token_required akzeptiert jetzt auch ?token=... als
Query-Parameter (Fallback wenn kein Authorization-Header)
- Frontend: downloadUrl() haengt den Access-Token automatisch als
Query-Parameter an die Download-URL an
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ZIP-Download fix:
- window.location.href statt a.download fuer API-Downloads
(a.download funktioniert nicht mit authentifizierten API-Routen)
Share-Status live:
- Dateiliste wird nach jeder Share-Aenderung automatisch neu geladen
(Link erstellen, Link loeschen, Benutzer-Freigabe setzen/entfernen)
- Gruenes Share-Icon aktualisiert sich sofort ohne F5
Ordner-ZIP bei Share-Links:
- "Ganzen Ordner als ZIP herunterladen" Button bei read/write Ordner-Shares
- Backend: GET /share/<token>/download-zip mit Passwort + Ablauf-Check
- Benachrichtigung an Ersteller bei ZIP-Download
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ordner-Download:
- Ordner koennen jetzt als ZIP heruntergeladen werden (rekursiv
mit allen Unterordnern und Dateien)
- ZIP-Icon statt Download-Icon bei Ordnern in der Dateiliste
- Backend erstellt ZIP mit ZIP_DEFLATED und allowZip64 fuer grosse Ordner
Share-Status visuell:
- Share-Button zeigt gruenes Personen-Icon (pi-users) wenn die Datei/
der Ordner bereits Freigaben hat (Links oder Benutzer-Berechtigungen)
- Normales Share-Icon (pi-share-alt) wenn keine Freigaben existieren
- Backend liefert has_shares und has_permissions pro Datei mit
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Geteilte Ordner koennen jetzt komplett durchnavigiert werden:
- Klick auf einen Unterordner oeffnet dessen Inhalt
- Breadcrumb-Navigation: Root > Unterordner > Unter-Unterordner
mit Klick zurueck auf jede Ebene
- Ordner-Zeilen haben Hover-Effekt und Cursor-Pointer
Backend:
- GET /share/<token>/files?parent_id=X - Unterordner auflisten
- Sicherheitspruefung: parent_id muss innerhalb des geteilten
Ordners liegen (_is_inside_shared_folder traversiert den Baum)
- Breadcrumb wird serverseitig berechnet
- Download + Delete erlauben jetzt Dateien in jeder Tiefe
(nicht nur direkte Kinder)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Share-Links fuer Ordner verhalten sich jetzt je nach Berechtigung:
read (Nur Lesen):
- Zeigt alle Dateien im Ordner mit Name, Groesse, Typ
- Download-Button pro Datei
- Kein Upload, kein Loeschen
write (Lesen+Schreiben):
- Zeigt alle Dateien im Ordner
- Download-Button pro Datei
- Loeschen-Button pro Datei
- Upload-Zone (Drag & Drop + Button)
- Nach Upload wird Dateiliste automatisch aktualisiert
upload_only (Nur Upload):
- Kein Dateilisting, kein Ordnername sichtbar
- Nur Upload-Zone
Backend-Endpunkte:
- GET /share/<token>/files - Dateien im geteilten Ordner auflisten
- GET /share/<token>/files/<id>/download - Einzeldatei herunterladen
- DELETE /share/<token>/files/<id> - Datei loeschen (nur write)
- Alle Endpunkte pruefen Passwort, Ablaufdatum und Berechtigung
- Dateien muessen direkte Kinder des geteilten Ordners sein (kein Ausbruch)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Behebt 500 Internal Server Error beim Erstellen von Share-Links
auf bestehenden Datenbanken, denen die neue 'permission'-Spalte fehlt.
Problem: db.create_all() erstellt nur neue Tabellen, fuegt aber keine
Spalten zu bestehenden Tabellen hinzu. Wenn das Model neue Spalten
bekommt, crasht die App auf alten Datenbanken.
Loesung: _auto_migrate() vergleicht beim Start jede Model-Definition
mit dem tatsaechlichen DB-Schema (via SQLAlchemy Inspector) und fuegt
fehlende Spalten per ALTER TABLE ADD COLUMN hinzu. Funktioniert fuer
alle Datentypen mit korrekten Defaults (VARCHAR, INTEGER, BOOLEAN, etc.).
Output im Log: [Auto-Migrate] Added column share_links.permission (VARCHAR(20))
Das macht die App update-sicher - neue Spalten in Models werden
automatisch zur bestehenden DB hinzugefuegt, ohne manuelles Migrieren.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dritter Link-Typ neben read und write:
- upload_only: Nur Dateien hochladen, kein Download, kein Ordnerinhalt
sichtbar, Ordnername wird nicht angezeigt
Backend-Absicherung:
- GET /share/<token>/download gibt 403 bei upload_only
- POST /share/<token>/upload erlaubt upload_only + write
- GET /share/<token>/info gibt download_allowed zurueck
Frontend Share-Dialog:
- Drei Optionen: Nur Lesen / Lesen+Hochladen / Nur Upload
- Bestehende Links zeigen Typ an
Frontend ShareView:
- upload_only: Zeigt nur Upload-Zone, kein Dateiname, kein Download
- Hinweistext 'Dieser Link erlaubt nur das Hochladen von Dateien'
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Share-Links haben jetzt ein permission-Feld (read/write):
- read (Standard): Nur Download erlaubt, kein Upload, kein Aendern
- write: Download + Upload in Ordner erlaubt
Backend-Absicherung:
- POST /share/<token>/upload prueft permission == 'write', gibt 403
bei read-only Links zurueck
- GET /share/<token>/info gibt permission + upload_allowed zurueck
- ShareLink-Model hat neues permission-Feld (default: 'read')
Frontend Share-Dialog:
- Dropdown "Berechtigung" beim Erstellen von Links
(Nur Lesen / Lesen+Hochladen)
- Bestehende Links zeigen Berechtigungslevel an
Frontend ShareView:
- Upload-Zone nur sichtbar wenn upload_allowed == true
- Bei read-only Links: kein Drag & Drop, kein Upload-Button
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Share-Links fuer Ordner erlauben jetzt auch Uploads:
Backend:
- POST /share/<token>/upload - Datei in freigegebenen Ordner hochladen
- Passwort-Schutz wird bei Upload ebenfalls geprueft
- share_info gibt jetzt upload_allowed zurueck (true bei Ordner-Shares)
- Email-Benachrichtigung an den Ersteller wenn jemand eine Datei
hochlaedt (Dateiname, Groesse, IP-Adresse)
Frontend (ShareView):
- Ordner-Shares zeigen jetzt eine Upload-Zone (Drag & Drop + Button)
- Fortschrittsbalken beim Upload mit Datei-Zaehler
- Erfolgs-/Fehlermeldung nach Upload
- Passwortgeschuetzte Ordner-Shares: erst entsperren, dann uploaden
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>