Commit Graph

69 Commits

Author SHA1 Message Date
Stefan Hacker 2610e3b183 ui(files): Upload-Pfeil vor dem Ordner-Icon im Button "Ordner"
Damit ist auf den ersten Blick erkennbar, dass auch der Ordner-Button
einen Upload ausloest (und nicht bloss eine Ordner-Aktion).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 16:00:36 +02:00
Stefan Hacker 9f6132a400 feat: Auswahl-Dropdowns zeigen "(geteilt von <Name>)" bei Freigaben
Wenn der eigene und ein freigegebener Kalender/Adressbuch/Aufgabenliste
denselben Namen tragen, sind sie in den Anlegen-Dialogen jetzt
unterscheidbar.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:53:46 +02:00
Stefan Hacker ed944339c4 feat: Listen/Kalender/Adressbuch-Namen im 3-Punkte-Menue umbenennbar
Stift-Icon neben dem Namen oeffnet Inline-Editor (Eingabefeld + Check/X).
Enter speichert, ESC bricht ab. Nur fuer Eigentuemer sichtbar.
Backend-PUT-Endpunkte sind bereits vorhanden.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:52:12 +02:00
Stefan Hacker 2ef186e262 feat: Liste/Kalender/Adressbuch beim Anlegen waehlbar (nur Schreibrecht)
- ContactsView: Adressbuch-Auswahl im Kontakt-Dialog (versteckt bei nur
  einem beschreibbaren Buch). Neuer-Kontakt-Button disabled wenn keiner.
- TasksView: gleiches fuer Aufgabenlisten.
- CalendarView: writableCalendars (eigene + Schreibfreigaben) ersetzt
  ownCalendars in Event-Dialog und Import-Auswahl. Auswahlfeld nur ab 2.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:44:44 +02:00
Stefan Hacker 4d67819cac feat: Vor-/Nachname, geteilte Listen zeigen Eigentuemer
Backend:
- User.first_name / User.last_name (nullable, Auto-Migrate fuegt sie an)
  full_name/display_name als Properties + in to_dict
- TaskList.owner-Relationship ergaenzt (fehlte, daher wurden geteilte
  Listen beim Empfaenger nicht korrekt aufgeloest)
- /auth/me GET + PUT (Profil bearbeiten: Vorname, Nachname, E-Mail)
- /users/search findet jetzt auch nach Vor-/Nachname und liefert
  full_name/display_name mit
- list_tasklists/list_calendars/list_addressbooks liefern
  owner_full_name und owner_display_name

Frontend:
- Sidebars bei Kontakten/Kalender/Aufgaben: "(geteilt von <Voller Name>)"
  mit Fallback auf Username
- User-Search-Popup zeigt vollen Namen neben Username
- SettingsView: Vorname/Nachname/E-Mail bearbeiten

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:34:22 +02:00
Stefan Hacker e4dd555bd1 feat(tasks): Berechtigung bestehender Freigaben nachtraeglich aendern
Stift-Icon neben Freigabe oeffnet Inline-Editor mit Select "Lesen" /
"Lesen+Schreiben" (analog zu Kontakten/Kalender).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:26:59 +02:00
Stefan Hacker 3eb038abd8 feat(tasks): Benutzer-Suche beim Teilen (statt Freitext)
Analog zu Kontakten/Kalender: ab 2 Zeichen werden Vorschlaege per
/users/search eingeblendet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:21:14 +02:00
Stefan Hacker 9bb22eb17b feat: Admin-Sicht System-Zeit + TZ-Liste in README/.env.example
- /api/settings gibt zusaetzlich timezone, timezone_abbr, server_time,
  ntp_server zurueck (alle read-only, aus Config/ENV).
- AdminView zeigt neuen Abschnitt "System-Zeit" mit Zeitzone, aktueller
  Server-Zeit und NTP-Server samt Hinweis "wird in der .env festgelegt".
- .env.example: Liste gaengiger TZ-Werte mit Link zur IANA-Vollliste.
- README.md: neuer Abschnitt "Zeitzone & NTP" mit Werte-Tabelle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:19:40 +02:00
Stefan Hacker ba3e619963 feat: Aufgaben (Tasks) mit CalDAV VTODO-Sync
Neuer Menuepunkt "Aufgaben" unterhalb Kontakte.

Backend:
- TaskList + Task + TaskListShare Models
- REST-API: CRUD, Teilen, my-color, Import/Export (.ics mit VTODO, CSV)
- CalDAV: Task-Listen tauchen als Calendar-Collection mit
  supported-calendar-component-set=VTODO im autodiscovery auf
- PROPFIND/REPORT/GET/PUT/DELETE/PROPPATCH/MKCOL fuer /dav/<user>/tl-<id>/
- SSE-Notifications bei Aenderungen

Frontend:
- TasksView mit Listen-Sidebar, Suche, "Erledigte ausblenden"
- Mehrfachauswahl + Bulk-Loeschen, Status-Toggle per Checkbox
- Editor mit Titel/Beschreibung/Faellig/Prioritaet/Status/Fortschritt
- Teilen, Farbe persoenlich anpassen, Import/Export

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 15:07:06 +02:00
Stefan Hacker 2ce088e96b feat: Import/Export fuer Kontakte und Kalender + Bulk-Loeschen Kontakte
Kontakte:
- Mehrfachauswahl in der Liste (Checkbox-Spalte) mit Bulk-Loeschen
- Export als Sammel-vCard (.vcf), als ZIP mit Einzel-vCards oder als CSV
- Import aus vCard (mehrere im File moeglich) oder CSV; Match per UID,
  bestehende Kontakte werden aktualisiert

Kalender:
- Export als iCalendar (.ics) oder CSV
- Import aus .ics oder CSV; bestehende Termine via UID aktualisiert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:23:23 +02:00
Stefan Hacker c6241519a6 feat(calendar): Hinweis bei passwortgeschuetztem iCal-Link
Browser/Kalender-App fragen sonst nach Benutzername+Passwort - der
Benutzername muss leer bleiben.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:13:50 +02:00
Stefan Hacker f6626da114 feat(calendar): Mehrfachauswahl + Bulk-Loeschen in der Listen-Ansicht
Checkbox-Spalte plus Header-Checkbox "Alle". Bulk-Aktion mit
Bestaetigung loescht ausgewaehlte Termine; Read-Only-Eintraege
werden uebersprungen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:11:12 +02:00
Stefan Hacker e96c84b5f7 feat(ui): Browser-Titel "Mini-Cloud - <username>" + Wolken-Favicon
Titel reagiert reaktiv auf Login/Logout. Favicon ist die Wolke aus
der Sidebar (pi-cloud-Style).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:05:51 +02:00
Stefan Hacker 1eba5d0adc revert(contacts): Titel-Feld wieder raus, nur Anrede (Herr/Frau/Divers)
Sync-Probleme durch zusammengesetzten PREFIX vermeiden.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:52:23 +02:00
Stefan Hacker 655b789e06 feat(contacts): Anrede + Titel als getrennte Dropdowns
Anrede: Herr/Frau/Divers (fest), Titel: Dr./Prof./Dipl.-Ing./... (editierbar).
Beim Speichern werden beide zu vCard-PREFIX zusammengesetzt, beim Laden
wieder aufgesplittet.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:37:41 +02:00
Stefan Hacker 50df055794 feat(contacts): Anrede als Dropdown (Herr/Frau/Divers/Dr./Prof.)
editable bleibt aktiv, damit eigene Werte weiterhin moeglich sind.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:35:59 +02:00
Stefan Hacker 848e4b9b0f fix(contacts): Inputs in .field-row fuellen Container, kein Ueberlappen mehr
Anrede/Suffix/PLZ etc. hatten max-width-Container, das InputText darin
behielt aber die Default-Breite und ueberlief. Globale CSS-Regel sorgt
nun dafuer, dass jedes Input/Select seinen Field-Container ausfuellt.
field-row wrappt auf schmalen Dialogen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:32:17 +02:00
Stefan Hacker e02c4f97c1 feat(calendar): Live-Refresh ueber CalDAV, Tagklick-Navigation, Listen-Ansicht
- caldav.py sendet SSE-Notifications bei Event-PUT/DELETE und Kalender-Loeschung,
  damit das Web-UI auch auf Aenderungen aus DAVx5 sofort reagiert.
- FullCalendar navLinks: Klick auf Tagesnummer im Monatsraster wechselt in
  die Tagesansicht.
- Neue Listen-Ansicht mit Volltext-Suche, Datumsbereich, Kalender-Filter,
  Sortierung nach Datum/Titel und Loeschen-Button pro Zeile.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 09:28:44 +02:00
Stefan Hacker 58ba130cd9 feat: Passwort-Manager Mehrfachauswahl + Bulk-Loeschen
Checkbox pro Eintrag, "Alle auswaehlen" oben und roter Loesch-Button mit
Anzahl. Sicherheitsabfrage vor dem Loeschen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:08:18 +02:00
Stefan Hacker 24a6015841 fix: Separate CalDAV/CardDAV Home-Sets + UI-URLs ohne /dav/
Kalender und Adressbuecher teilten sich den gleichen Home-Set
(/dav/<user>/). DAVx5 hat bei Depth-1-PROPFIND beide Collection-
Typen angezeigt und mangels bekanntem Resourcetype als
"DEFAULT_TASK_CALENDAR_NAME"-Kacheln gelistet.

Loesung:
* calendar-home-set zeigt auf /dav/<user>/calendars/
* addressbook-home-set zeigt auf /dav/<user>/addressbooks/
* Beide Pfade sind eigene Container-Collections - PROPFIND Depth 1
  liefert nur den jeweils passenden Typ
* /dav/<user>/ selbst gibt bei Depth 1 keine Kinder mehr zurueck,
  Clients folgen den Home-Sets
* Die konkreten URLs cal-<id> / ab-<id> liegen weiterhin unter
  /dav/<user>/ (keine Breaking Change fuer existierende Clients;
  nur die Discovery-URL aendert sich)

Frontend:
CalendarView + ContactsView zeigen als Auto-Discovery-URL nur
noch den Hostname - PROPFIND auf / funktioniert ja jetzt. Die
Direkt-URL bleibt vollstaendig mit /dav/<user>/cal-<id> bzw.
ab-<id> fuer Clients die das brauchen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:22:29 +02:00
Stefan Hacker 9c102823e4 feat: Kontakte mit Outlook-Feldern + CardDAV-Server + Sharing
Komplette Kontakte-Ueberarbeitung analog zum Kalender-Ausbau.

Backend-Model:
* AddressBook: color (pro Buch), ausserdem Per-User-Color via
  AddressBookShare.color wie bei CalendarShare.
* Contact: volle Outlook-artige Struktur - prefix/first/middle/
  last/suffix, display_name, nickname, organization, department,
  job_title, birthday, anniversary, notes, photo sowie JSON-
  Spalten fuer mehrfach vorhandene Felder (emails, phones,
  addresses mit allen Adressteilen, websites, impp, categories).

Backend-API:
* REST CRUD uebernimmt die neuen Felder und generiert vCard 3.0
  als Source of Truth fuer CardDAV. Voller vCard-Parser +
  -Builder mit Escape/Unescape, TYPE-Parametern, Line-Folding.
* Neuer Endpoint PUT /addressbooks/<id>/my-color - persoenliche
  Farbe pro Buch ohne den Besitzer zu beeinflussen.
* SSE-Events vom Typ 'addressbook' an Besitzer + alle Share-
  Empfaenger bei jeder Aenderung.

CardDAV-Server (backend/app/dav/carddav.py):
* Volle Discovery via principal - addressbook-home-set wird
  neben calendar-home-set annonciert.
* PROPFIND/REPORT/GET/PUT/DELETE/MKCOL fuer
  /dav/<user>/ab-<id>/ und /<...>/{uid}.vcf
* addressbook-query + addressbook-multiget REPORTs
* ETag-basierte Konfliktpruefung via If-Match/If-None-Match

Frontend (ContactsView.vue):
* Komplett neuer Editor mit vier Tabs: Allgemein (Name, Org),
  Kommunikation (Emails/Phones/Websites/IMPP dynamisch),
  Adressen (mehrere mit allen Teilen), Details (Geburtstag,
  Jahrestag, Kategorien, Notizen).
* Avatar mit Fotoauswahl oder Initialen-Farbkreis.
* Kalender-Sharing-Flow 1:1 uebernommen: Autocomplete fuer
  Benutzersuche, Share-Liste mit Stift zum Bearbeiten, Muelleimer
  zum Entfernen, Per-User-Farbe, CardDAV-URL-Info-Block pro
  Adressbuch, Live-Refresh via SSE.
* Suche durchsucht Displayname, E-Mail und Firma.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:16:01 +02:00
Stefan Hacker e85338761d feat: Persoenliche Farbe fuer freigegebene Kalender
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>
2026-04-12 13:14:45 +02:00
Stefan Hacker 2170f4a7b1 feat: Kalender-Ansicht aktualisiert sich live via SSE
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>
2026-04-12 13:10:54 +02:00
Stefan Hacker ce4faedd88 feat: CalDAV-URLs im Kalender-Menue anzeigen
Im Drei-Punkte-Menue jedes Kalenders wird jetzt ein Info-Block mit
den CalDAV-URLs angezeigt:

* Auto-Discovery URL fuer Thunderbird / DAVx5 / Apple Calendar
* Direkt-URL fuer diesen speziellen Kalender (z.B. Outlook
  CalDAV-Synchronizer)
* Kurz-Hinweis welcher Client welche URL nimmt

Jede URL hat ein Kopier-Icon. Ergaenzt den bestehenden iCal-Link
um die bidirektionale Sync-Moeglichkeit ueber CalDAV.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:08:11 +02:00
Stefan Hacker fda9e685a9 feat: Kalender-Freigaben per Stift-Button bearbeiten
Analog zu den Datei-Freigaben: Stift neben der Muelltonne in der
Share-Liste macht die Zeile zur Inline-Edit-Zeile mit Permission-
Dropdown + Check/X. Speichern nutzt denselben POST /share-
Endpoint, der auch das initiale Teilen erledigt - er erkennt den
existierenden User und aktualisiert nur die Berechtigung.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:04:48 +02:00
Stefan Hacker a143325bbe feat: Kalender - Autocomplete + Privat-Flag + Share-Liste + Bugfix
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>
2026-04-12 12:56:25 +02:00
Stefan Hacker cbb2786130 fix: Kalender - Termine immer als Balken statt Punkt+Zeit
eventDisplay: 'block' zwingt FullCalendar dazu, auch zeitlich
terminierte Termine in der Monatsansicht als farbige Balken
anzuzeigen statt als Punkt mit Uhrzeit-Label. Damit sieht ein
per "Neuer Termin"-Button angelegter Termin genauso aus wie einer,
der per Klick auf den Tag erstellt wurde.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:44:28 +02:00
Stefan Hacker c1b05e2525 feat: Serientermin-Bearbeitung: Nur diesen Termin oder Serie
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>
2026-04-12 12:41:35 +02:00
Stefan Hacker ddd8f57e69 feat: Kalender-Termine zeigen Icons + Start-Ende-Uhrzeit
* 📅-Icon bei ganztaegigen Terminen
* 🔁-Icon bei wiederkehrenden Terminen
* Anzeige "09:00-10:30" statt nur "09:00" in Woche/Tag-Ansicht
* Mouseover-Tooltip mit allen Termin-Infos inklusive Ort und
  Beschreibung

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:38:44 +02:00
Stefan Hacker c5284f57e0 feat: Kalender mit FullCalendar - Woche/Monat/Tag, Drag&Drop, Wiederholungen
Kalender-UI komplett neu aufgesetzt mit FullCalendar:
* Drei Ansichten: Monat, Woche, Tag - ueber Toolbar wechselbar
* Drag & Drop: Termine zwischen Tagen verschieben
* Resize: Termindauer direkt am Rand ziehen
* Sidebar mit aktiven Kalendern (Checkbox fuers Ein-/Ausblenden)
* Deutsch lokalisiert, Woche startet Mo, Wochennummern
* Heute-Marker + Jetzt-Linie in Woche/Tag

Terminbearbeitung:
* Titel, Ort, Beschreibung, Zeitraum (oder ganztaegig)
* Wiederholungs-Editor: taeglich, woechentlich (mit Wochentagen),
  monatlich (auch "jeden 2. Mittwoch"), jaehrlich - jeweils mit
  Intervall, Enddatum oder Wiederholungsanzahl
* RRULE-Feld (RFC 5545) wird generiert und vom rrule-Plugin fuer
  die Anzeige im Kalender gerendert

Backend:
* CalendarEvent: description + location Spalten ergaenzt
* Calendar: ical_password_hash fuer passwortgeschuetzte Abo-Links
* /calendars/<id>/ical-link unterstuetzt password + clear_password
* DELETE /calendars/<id>/ical-link zum Zurueckziehen
* ical_export erzwingt HTTP Basic Auth wenn Passwort gesetzt -
  DAVx5, Apple Cal, Thunderbird verstehen das out-of-the-box

Frontend-Deps: @fullcalendar/{core,daygrid,timegrid,interaction,
rrule,vue3}, rrule - ca. 150KB Bundle-Overhead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:32:59 +02:00
Stefan Hacker 04bc3f80ec feat: Bestehende Benutzerfreigaben per Stift-Button bearbeiten
Neben der Muelltonne jetzt ein Stift-Icon im Share-Dialog:
Klick macht die Zeile zur Inline-Edit-Zeile mit Permission-
Dropdown + Weiterteilen-Checkbox + Speichern/Abbrechen-Buttons.
Speichern ruft POST /permissions mit user_id auf - Backend
erkennt die bestehende Freigabe und aktualisiert sie, statt
loeschen + neu anlegen zu muessen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:14:03 +02:00
Stefan Hacker 9b135e42b7 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>
2026-04-12 12:00:15 +02:00
Stefan Hacker 9369c851a0 feat: Benutzerfreigabe - Weiterteilen-Recht + Lesezugriff wird erzwungen
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>
2026-04-12 11:54:36 +02:00
Stefan Hacker 5afb87c9cd fix: SSE-Reload in FilesView etwas robuster
Beim Verbindungsaufbau (open-Event) wird jetzt ein initialer Reload
ausgeloest, damit eventuelle Changes in der Zeit zwischen letzter
Anzeige und SSE-Verbindung nicht verloren gehen. Gilt fuer eigene
und freigegebene Ordner gleichermassen (selbe FilesView-Komponente).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 11:21:10 +02:00
Stefan Hacker 28fb1c47c2 feat: Web-GUI Live-Refresh via SSE
FilesView abonniert beim Mount die SSE-Events des Backends. Lock/
Unlock, Create, Update oder Delete durch andere Clients loest einen
debounced Reload der aktuellen Ordner-Ansicht aus. EventSource
reconnected automatisch; wird beim Unmount sauber geschlossen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:21:00 +02:00
Stefan Hacker c63a52629d fix: Lock/Unlock-Buttons in FilesView - doppelter /api-Prefix
apiClient hat baseURL '/api' - die URL darf nicht nochmal mit /api
anfangen, sonst wird daraus /api/api/... und der Request geht ins
Leere.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:01:43 +02:00
Stefan Hacker 6aad986d78 fix: PDFs im Preview-iframe statt neuem Tab
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>
2026-04-12 09:55:40 +02:00
Stefan Hacker 50385faa02 feat: Echtzeit-Sync via SSE + Journal-basierter 3-Wege-Vergleich
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>
2026-04-12 09:50:44 +02:00
Stefan Hacker 29cc00e284 fix: Client-Upload akzeptiert SECRET_KEY oder JWT_SECRET_KEY + Download in Settings
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>
2026-04-11 23:58:54 +02:00
Stefan Hacker 9a6aa7aadc feat: Client-Download-System + Auto-Upload nach Build
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>
2026-04-11 23:39:51 +02:00
Stefan Hacker 748537b9f5 feat: File Locking System (Ein-/Auschecken) + Konflikt-Email
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>
2026-04-11 23:20:55 +02:00
Stefan Hacker 5f79ebe9b0 fix: OnlyOffice/Preview zeigt immer aktuelle Version (kein Cache)
Drei Cache-Ebenen gefixt:
- Vue Router: :key=fullPath erzwingt Komponenten-Neuaufbau bei
  jeder Navigation (kein Wiederverwenden alter Instanzen)
- Frontend: Cache-Bust Parameter an Preview + OnlyOffice API-Calls
- Backend: No-Cache Headers (Cache-Control, Pragma) auf Preview-Endpunkt

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:50:33 +02:00
Stefan Hacker 15211509a6 simplify: OnlyOffice nutzt JWT_SECRET_KEY, kein extra Secret
- 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>
2026-04-11 22:10:06 +02:00
Stefan Hacker 0dbeef7cd9 refactor: OnlyOffice Konfiguration nur ueber .env, nicht Admin-GUI
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>
2026-04-11 22:08:07 +02:00
Stefan Hacker fca2bc2d47 fix: OnlyOffice Docker-Setup ohne Port-Mapping + nginx-Beispielconfig
- docker-compose.yml: Kein ports-Mapping fuer OnlyOffice mehr
  (Zugriff nur ueber nginx mit HTTPS, nicht direkt ueber Port 8080)
- nginx.example.conf: Komplette Beispielkonfiguration fuer
  cloud.example.com + office.example.com mit Let's Encrypt
  - Unbegrenztes Upload-Limit (client_max_body_size 0)
  - CalDAV/CardDAV spezielle Location
  - WebSocket-Support vorbereitet
  - HTTP->HTTPS Redirect
- Admin-UI: Setup-Anleitung aktualisiert - erklaert nginx + HTTPS +
  Let's Encrypt Schritte, Hinweis dass die oeffentliche HTTPS-URL
  eingetragen werden muss (nicht die interne Docker-URL)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:23:31 +02:00
Stefan Hacker 216445d41b feat: OnlyOffice Document Server Integration + Preview ohne neuen Tab
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>
2026-04-11 21:19:17 +02:00
Stefan Hacker a2ded7f97c feat: Office-Preview im neuen Tab + DOCX/XLSX/Text bearbeitbar
Preview-System komplett ueberarbeitet:
- Neuer Tab: Doppelklick oder Auge-Icon oeffnet Vorschau im neuen Tab
- Dedizierte PreviewView mit Toolbar (Zurueck, Bearbeiten, Speichern, Download)
- Token wird als Query-Parameter an Preview/Download-URLs angehaengt (kein 404 mehr)

Unterstuetzte Formate:
- PDF: Inline-Anzeige im iFrame
- Bilder: Zentrierte Anzeige mit Schatten
- DOCX: HTML-Darstellung mit Formatierung (Headings, Bold, Italic, Tabellen)
- XLSX: Tabellen-Ansicht mit Sheet-Tabs
- PPTX: Folien-Navigation (vor/zurueck)
- Text/Code: Monospace mit Syntax

Bearbeitung (neu!):
- DOCX: ContentEditable-Editor, Bold/Italic/Headings bleiben erhalten,
  Speichern schreibt zurueck als .docx (python-docx)
- XLSX: Direkt in der Tabelle bearbeiten (Zellen anklicken),
  Speichern schreibt zurueck als .xlsx (openpyxl)
- Text/Code: Textarea-Editor, Speichern als UTF-8

Backend: POST /files/<id>/save mit type-spezifischer Konvertierung
- html -> DOCX (Headings, Bold/Italic/Underline erhalten)
- spreadsheet -> XLSX (Zahlen werden automatisch konvertiert)
- text -> direkt als Datei

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 21:07:59 +02:00
Stefan Hacker 82f3091f2e feat: Papierkorb + Bestaetigungsdialoge bei allen Loeschaktionen
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>
2026-04-11 20:50:19 +02:00
Stefan Hacker 1ee80e650d fix: Download Token-Fehler - Token als Query-Parameter unterstuetzen
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>
2026-04-11 20:39:58 +02:00
Stefan Hacker ed1a619ad1 fix: ZIP-Download Icon - pi-box statt pi-file-zip (existiert nicht)
pi-file-zip gibt es in PrimeIcons nicht, daher wurde kein Icon
angezeigt. Ersetzt durch pi-box in Datei-Browser und Share-View.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 20:36:42 +02:00