The recipient colour was stored on calendar_shares, so it only worked for
DIRECT shares. A calendar made visible through a group (a co-member's
group_visible_calendar_id) has no CalendarShare row, so the colour endpoint
returned 403 and nothing was saved — the reported "colour picker opens but the
colour stays the same" for a group-shared calendar.
Replace the share-scoped colour with a general per-user override table
(calendar_color_prefs, keyed by user+calendar). PUT /calendars/{id}/color now
accepts any calendar the user can read (readable_local_calendar_ids covers
direct shares, group calendars AND group-visible), and the merge read + calendar
list apply the override for all of them. Owners still set the shared colour.
The new table is created by create_all; the old calendar_shares.color is unused.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A share recipient couldn't change anything on a shared calendar (update_calendar
is owner-only → 404), yet the clients still showed a colour picker for it.
Add a per-recipient colour: new nullable calendar_shares.color column (+ migration).
New PUT /calendars/{id}/color endpoint sets the calendar colour for the owner
(global) or, for a recipient, only their own share colour — never the name, so
recipients can recolour but not rename. The merge read and the calendar list now
prefer the recipient's share colour over the owner's (NULL = owner's colour).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two group/sharing display fixes, both server-side so every client benefits:
1. A personal calendar shared WITH a user showed only its raw name
("Persönlich"), indistinguishable from the user's own. The merge read now
relabels a shared *personal* calendar under the owner's display name (so
Guido's "Persönlich" reads as "Guido" for recipients) and adds read_only:true
when the share isn't read_write. Group calendars are excluded — they keep
their own name and stay writable for members.
2. The combined group view prefixed every foreign event with the owner's first
name ("Guido: …"). Each member already has a distinct display_color, so the
prefix was redundant. _decorate_title now returns the raw title; display_title
stays non-empty so clients' legacy prefix fallback never triggers. The change
takes effect on already-installed clients with no app update.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Events already carry calendar_id, but the corresponding sync-error
entries didn't, forcing clients to match errors to calendars by
fragile name-suffix comparison. Account-level failures (whole account
unreachable) still omit calendar_id since no single calendar is at fault.
CalDAV and Home Assistant sync failures were previously only logged
server-side, leaving clients unable to distinguish an empty calendar
from a broken sync. Unify error reporting across CalDAV, Home
Assistant, and Google into a single errors list on GET
/api/caldav/events, shaped as {source, name, message}. Messages are
fixed generic strings, never raw exception text, to avoid leaking
URLs or credential fragments. get_ha_events and get_google_events now
return (events, errors) tuples so per-calendar failures propagate to
the caller in addition to account-level failures. Frontend toast now
picks its label from err.source instead of assuming Google/err.email.
New boolean column on every calendar type (CalDAV, local, Google, Home
Assistant, iCal subscription), default ON so existing data is unchanged.
Lets a calendar be shown without generating reminders/notifications.
- models.py: reminders_enabled on the five calendar models
- main.py _migrate(): ALTER TABLE ADD COLUMN for each table
- caldav/google/homeassistant/local/ical routers: expose the flag in the
calendar serialization and accept it in the calendar/subscription update
endpoints
Clients map event -> (source, calendar_id) -> reminders_enabled to skip
scheduling notifications for disabled calendars.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Findings from the security review:
- HIGH: private local events leaked in full (title/location/description) to
anyone who could READ a shared or group calendar via GET /api/caldav/events —
the private_event_visibility rule was only enforced in /groups/{id}/combined.
Now enforced in the merge read too, via a shared helper (apply_event_privacy)
so the two paths can't drift.
- HIGH: 'busy' masking was a blacklist that still leaked creator identity,
source-calendar name, recurrence rule and per-event colour. Replaced with a
whitelist (mask_busy_event): only timing/identity/render fields survive.
- MEDIUM: .ics import had no size limit (raw = await file.read()) → memory DoS.
Now capped at 5 MB (413), read before creating any calendar.
- LOW/INFO: profile email now checked for uniqueness + basic format; display
name / username / email length-capped and control-chars stripped.
Deferred (tracked): RRULE expansion cap at the trust boundary, SQLite
PRAGMA foreign_keys + ON DELETE cascade, and JWT-by-user-id + token version.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CalDAV-Events hatten bisher kein source-Feld gesetzt. applyCalendarColor
filtert aber via ev.source !== 'caldav', sodass der Patch nie auf
CalDAV-Events angewendet wurde – die Farbe blieb sichtbar bis F5.
Jetzt wird source: 'caldav' beim Anreichern der Events gesetzt.
- End-Datum passt sich automatisch an wenn Start geändert wird (Duration bleibt erhalten)
- Erstellen-Button nutzt den aktuell angesehenen Tag statt immer heute
- Monatsansicht: Einzelklick = Tag auswählen, Doppelklick = Tagesansicht, Rechtsklick = Kontextmenü
- CalDAV URL-Matching robuster (Normalisierung, Path-Fallback, calendar_id Parameter)
- iCal-Abo-Termine sind nicht mehr bearbeitbar (Read-Only-Schutz)
- Wiederkehrende Termine mit RRULE-Support (täglich/wöchentlich/monatlich/jährlich/benutzerdefiniert)
Ergänzt die HA-Integration um Password-Grant OAuth2: Nutzer können sich
nun wahlweise mit einem Long-Lived Token oder mit Benutzername/Passwort
anmelden. Access Tokens werden automatisch per Refresh-Token erneuert.
- Neue Integration: Home Assistant als Kalenderquelle via REST-API
(GET /api/calendars + GET /api/calendars/{entity_id})
- Authentifizierung per Long-Lived Access Token
- Neues Modal zum Verbinden (Name, URL, Token) mit Fehlerbehandlung
- Kalender einzeln aktivierbar/deaktivierbar, Farbe änderbar
- Ausgeblendete HA-Kalender in Einstellungen wiederherstellbar
- Sync- und Trennen-Buttons in den Einstellungen
- Bugfix: CalDAV- und Google-Kalender mit sidebar_hidden=true
liefern nun keine Events mehr im Kalender
Wenn der Access-Token eines Google-Accounts abläuft und der Refresh
fehlschlägt, wurde die leere Terminliste bisher still zurückgegeben
(kein Log, keine UI-Meldung). Jetzt wird der Fehler geloggt, an den
Aufrufer weitergegeben und als Toast-Meldung im Frontend angezeigt
("Token abgelaufen – bitte Konto trennen und neu verbinden").
Das Events-Endpoint gibt nun {events, errors} statt ein reines Array
zurück; das Frontend extrahiert die Events entsprechend.
- sidebar_hidden-Spalte zu calendars und google_calendars hinzugefügt
- Ausblenden-Button persistiert jetzt server-seitig (cross-device)
- Einblenden in Einstellungen schreibt sidebar_hidden=false zurück
- Sidebar: overflow-x hidden verhindert dass lange Namen den Button rausschieben
- Google OAuth2 Flow: Admin konfiguriert Client-ID/Secret, User verbindet per Klick
- Google Calendar API v3: Events lesen, erstellen, bearbeiten, löschen
- GoogleAccount Model + google_router mit Token-Refresh
- Google-Events in Event-Pipeline integriert
- Frontend: Google Kalender in Sidebar, Dropdown, Event-CRUD-Routing
- CalDAV-Kalender: Ausblenden statt ganzes Konto löschen, Einblenden in Einstellungen
- Ausgeblendete Kalender Sektion in Einstellungen
Neue Features:
- Lokale Kalender erstellen mit vollem Event-CRUD (in SQLite gespeichert)
- iCal-URLs abonnieren mit Auto-Refresh und lokalem Caching
- iCal-Events sind editierbar/löschbar (Änderungen als lokale Overrides)
- Sidebar zeigt alle 3 Kalendertypen mit Farbe, Umbenennen, Löschen
- Dropdown "Kalender hinzufügen" mit 3 Optionen (Lokal, CalDAV, iCal)
Backend: models.py (4 neue Tabellen), local_router.py, ical_router.py
Frontend: Neue Modals, erweiterte Sidebar, Source-basiertes Event-Routing