From 14dea0105374f337eb81846107845b81bfee9d75 Mon Sep 17 00:00:00 2001 From: Scarriffle Date: Tue, 9 Jun 2026 18:16:45 +0200 Subject: [PATCH] Add per-calendar reminders_enabled flag (server) 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 --- backend/main.py | 9 +++++++++ backend/models.py | 10 ++++++++++ backend/routers/caldav_router.py | 4 ++++ backend/routers/google_router.py | 4 ++++ backend/routers/homeassistant_router.py | 4 ++++ backend/routers/ical_router.py | 4 ++++ backend/routers/local_router.py | 4 ++++ 7 files changed, 39 insertions(+) diff --git a/backend/main.py b/backend/main.py index b931917..6a1a4b5 100644 --- a/backend/main.py +++ b/backend/main.py @@ -201,6 +201,15 @@ def _migrate(): logging.info("Migration: added color to group_members") except Exception: pass + # Per-calendar reminder toggle (default ON = unchanged behaviour for existing data). + for tbl in ("calendars", "local_calendars", "google_calendars", + "homeassistant_calendars", "ical_subscriptions"): + try: + conn.execute(text(f"ALTER TABLE {tbl} ADD COLUMN reminders_enabled BOOLEAN DEFAULT 1")) + conn.commit() + logging.info("Migration: added reminders_enabled to %s", tbl) + except Exception: + pass _migrate() diff --git a/backend/models.py b/backend/models.py index e09d976..bcc8443 100644 --- a/backend/models.py +++ b/backend/models.py @@ -71,6 +71,8 @@ class Calendar(Base): color = Column(String(7), nullable=True) enabled = Column(Boolean, default=True) sidebar_hidden = Column(Boolean, default=False) + # Whether events of this calendar generate reminders/notifications on clients. + reminders_enabled = Column(Boolean, default=True, nullable=False) account = relationship("CalDAVAccount", back_populates="calendars") @@ -116,6 +118,8 @@ class LocalCalendar(Base): name = Column(String(100), nullable=False) color = Column(String(7), default="#34a853") enabled = Column(Boolean, default=True) + # Whether events of this calendar generate reminders/notifications on clients. + reminders_enabled = Column(Boolean, default=True, nullable=False) user = relationship("User", back_populates="local_calendars") events = relationship("LocalEvent", back_populates="calendar", cascade="all, delete-orphan") @@ -158,6 +162,8 @@ class ICalSubscription(Base): url = Column(String(1000), nullable=False) color = Column(String(7), default="#46bdc6") enabled = Column(Boolean, default=True) + # Whether events of this subscription generate reminders/notifications on clients. + reminders_enabled = Column(Boolean, default=True, nullable=False) refresh_minutes = Column(Integer, default=60) last_fetched = Column(DateTime, nullable=True) cached_ics = Column(Text, nullable=True) @@ -210,6 +216,8 @@ class GoogleCalendar(Base): color = Column(String(7), nullable=True) enabled = Column(Boolean, default=True) sidebar_hidden = Column(Boolean, default=False) + # Whether events of this calendar generate reminders/notifications on clients. + reminders_enabled = Column(Boolean, default=True, nullable=False) account = relationship("GoogleAccount", back_populates="calendars") @@ -243,6 +251,8 @@ class HomeAssistantCalendar(Base): color = Column(String(7), nullable=True) enabled = Column(Boolean, default=True) sidebar_hidden = Column(Boolean, default=False) + # Whether events of this calendar generate reminders/notifications on clients. + reminders_enabled = Column(Boolean, default=True, nullable=False) account = relationship("HomeAssistantAccount", back_populates="calendars") diff --git a/backend/routers/caldav_router.py b/backend/routers/caldav_router.py index d93fc38..4427706 100644 --- a/backend/routers/caldav_router.py +++ b/backend/routers/caldav_router.py @@ -42,6 +42,7 @@ class CalendarUpdate(BaseModel): color: Optional[str] = None name: Optional[str] = None sidebar_hidden: Optional[bool] = None + reminders_enabled: Optional[bool] = None class EventCreate(BaseModel): @@ -84,6 +85,7 @@ def _account_dict(a: models.CalDAVAccount) -> dict: "enabled": c.enabled, "cal_id": c.cal_id, "sidebar_hidden": bool(c.sidebar_hidden), + "reminders_enabled": bool(c.reminders_enabled), } for c in a.calendars ], @@ -270,6 +272,8 @@ def update_calendar( calendar.name = data.name if data.sidebar_hidden is not None: calendar.sidebar_hidden = data.sidebar_hidden + if data.reminders_enabled is not None: + calendar.reminders_enabled = data.reminders_enabled db.commit() return {"ok": True} diff --git a/backend/routers/google_router.py b/backend/routers/google_router.py index 510090b..aa6a7fb 100644 --- a/backend/routers/google_router.py +++ b/backend/routers/google_router.py @@ -142,6 +142,7 @@ def _account_dict(a: models.GoogleAccount) -> dict: "color": c.color or "#4285f4", "enabled": c.enabled, "sidebar_hidden": bool(c.sidebar_hidden), + "reminders_enabled": bool(c.reminders_enabled), } for c in a.calendars if not _is_system_calendar(c.cal_id) @@ -344,6 +345,7 @@ class GoogleCalendarUpdate(BaseModel): color: Optional[str] = None name: Optional[str] = None sidebar_hidden: Optional[bool] = None + reminders_enabled: Optional[bool] = None @router.put("/calendars/{calendar_id}") @@ -372,6 +374,8 @@ def update_calendar( gcal.name = data.name if data.sidebar_hidden is not None: gcal.sidebar_hidden = data.sidebar_hidden + if data.reminders_enabled is not None: + gcal.reminders_enabled = data.reminders_enabled db.commit() return {"ok": True} diff --git a/backend/routers/homeassistant_router.py b/backend/routers/homeassistant_router.py index 36447d8..7b5bc97 100644 --- a/backend/routers/homeassistant_router.py +++ b/backend/routers/homeassistant_router.py @@ -349,6 +349,7 @@ def _account_dict(a: models.HomeAssistantAccount) -> dict: "color": c.color or HA_DEFAULT_COLOR, "enabled": c.enabled, "sidebar_hidden": bool(c.sidebar_hidden), + "reminders_enabled": bool(c.reminders_enabled), } for c in a.calendars ], @@ -375,6 +376,7 @@ class HACalendarUpdate(BaseModel): color: Optional[str] = None name: Optional[str] = None sidebar_hidden: Optional[bool] = None + reminders_enabled: Optional[bool] = None # ── Endpoints ───────────────────────────────────────────── @@ -623,6 +625,8 @@ def update_calendar( cal.name = data.name if data.sidebar_hidden is not None: cal.sidebar_hidden = data.sidebar_hidden + if data.reminders_enabled is not None: + cal.reminders_enabled = data.reminders_enabled db.commit() return {"ok": True} diff --git a/backend/routers/ical_router.py b/backend/routers/ical_router.py index ca9704f..b10ad04 100644 --- a/backend/routers/ical_router.py +++ b/backend/routers/ical_router.py @@ -29,6 +29,7 @@ class SubscriptionUpdate(BaseModel): color: Optional[str] = None enabled: Optional[bool] = None refresh_minutes: Optional[int] = None + reminders_enabled: Optional[bool] = None def _sub_dict(sub: models.ICalSubscription) -> dict: @@ -38,6 +39,7 @@ def _sub_dict(sub: models.ICalSubscription) -> dict: "url": sub.url, "color": sub.color, "enabled": sub.enabled, + "reminders_enabled": bool(sub.reminders_enabled), "refresh_minutes": sub.refresh_minutes, "last_fetched": sub.last_fetched.isoformat() if sub.last_fetched else None, } @@ -215,6 +217,8 @@ def update_subscription( sub.enabled = data.enabled if data.refresh_minutes is not None: sub.refresh_minutes = data.refresh_minutes + if data.reminders_enabled is not None: + sub.reminders_enabled = data.reminders_enabled db.commit() return {"ok": True} diff --git a/backend/routers/local_router.py b/backend/routers/local_router.py index 5f4c980..e514566 100644 --- a/backend/routers/local_router.py +++ b/backend/routers/local_router.py @@ -30,6 +30,7 @@ class CalendarUpdate(BaseModel): name: Optional[str] = None color: Optional[str] = None enabled: Optional[bool] = None + reminders_enabled: Optional[bool] = None class EventCreate(BaseModel): @@ -72,6 +73,7 @@ def _cal_dict(cal: models.LocalCalendar, *, owned: bool = True, "name": cal.name, "color": cal.color, "enabled": cal.enabled, + "reminders_enabled": bool(cal.reminders_enabled), "type": "local", "owned": owned, } @@ -199,6 +201,8 @@ def update_calendar( cal.color = data.color if data.enabled is not None: cal.enabled = data.enabled + if data.reminders_enabled is not None: + cal.reminders_enabled = data.reminders_enabled db.commit() return {"ok": True}