Einige kleine verbesserungen #1
@@ -128,6 +128,7 @@ def build_local_event_dict(
|
|||||||
"type": "local",
|
"type": "local",
|
||||||
"creator": creator,
|
"creator": creator,
|
||||||
"private": bool(ev.is_private),
|
"private": bool(ev.is_private),
|
||||||
|
"reminders": [int(x) for x in (ev.reminders or "").split(",") if x.strip().lstrip("-").isdigit()],
|
||||||
}
|
}
|
||||||
if owner is not None:
|
if owner is not None:
|
||||||
d["owner"] = owner
|
d["owner"] = owner
|
||||||
|
|||||||
@@ -102,6 +102,20 @@ def _migrate():
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn.execute(text("ALTER TABLE local_events ADD COLUMN reminders TEXT"))
|
||||||
|
conn.commit()
|
||||||
|
logging.info("Migration: added reminders to local_events")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn.execute(text("ALTER TABLE user_settings ADD COLUMN default_reminder_minutes INTEGER"))
|
||||||
|
conn.commit()
|
||||||
|
logging.info("Migration: added default_reminder_minutes to user_settings")
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
conn.execute(text("ALTER TABLE user_settings ADD COLUMN month_divider_color VARCHAR(7) DEFAULT '#7090c0'"))
|
conn.execute(text("ALTER TABLE user_settings ADD COLUMN month_divider_color VARCHAR(7) DEFAULT '#7090c0'"))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|||||||
@@ -101,6 +101,9 @@ class UserSettings(Base):
|
|||||||
# The single local calendar this user shares into all their groups
|
# The single local calendar this user shares into all their groups
|
||||||
# (combined view shows only this calendar per member). NULL = share nothing.
|
# (combined view shows only this calendar per member). NULL = share nothing.
|
||||||
group_visible_calendar_id = Column(Integer, nullable=True)
|
group_visible_calendar_id = Column(Integer, nullable=True)
|
||||||
|
# Default reminder in minutes-before-start applied to all events client-side
|
||||||
|
# (0 = at start time). NULL = no default reminder.
|
||||||
|
default_reminder_minutes = Column(Integer, nullable=True)
|
||||||
|
|
||||||
user = relationship("User", back_populates="settings")
|
user = relationship("User", back_populates="settings")
|
||||||
|
|
||||||
@@ -133,6 +136,8 @@ class LocalEvent(Base):
|
|||||||
color = Column(String(7), nullable=True)
|
color = Column(String(7), nullable=True)
|
||||||
rrule = Column(Text, nullable=True)
|
rrule = Column(Text, nullable=True)
|
||||||
exdate = Column(Text, nullable=True) # Comma-separated YYYYMMDD dates to exclude
|
exdate = Column(Text, nullable=True) # Comma-separated YYYYMMDD dates to exclude
|
||||||
|
# Comma-separated minutes-before-start for reminders, e.g. "10,60" (0 = at start).
|
||||||
|
reminders = Column(Text, nullable=True)
|
||||||
# Creator: set server-side from the auth token on create, never from the client.
|
# Creator: set server-side from the auth token on create, never from the client.
|
||||||
creator_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
creator_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
||||||
# For imported events without a local user (from the .ics ORGANIZER field).
|
# For imported events without a local user (from the .ics ORGANIZER field).
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Form, HTTPException, Query, UploadFile, File
|
from fastapi import APIRouter, Depends, Form, HTTPException, Query, UploadFile, File
|
||||||
from fastapi.responses import Response
|
from fastapi.responses import Response
|
||||||
@@ -43,6 +43,7 @@ class EventCreate(BaseModel):
|
|||||||
color: Optional[str] = None
|
color: Optional[str] = None
|
||||||
rrule: Optional[str] = None
|
rrule: Optional[str] = None
|
||||||
private: bool = False
|
private: bool = False
|
||||||
|
reminders: Optional[List[int]] = None # minutes before start (0 = at start)
|
||||||
|
|
||||||
|
|
||||||
class EventUpdate(BaseModel):
|
class EventUpdate(BaseModel):
|
||||||
@@ -56,6 +57,7 @@ class EventUpdate(BaseModel):
|
|||||||
rrule: Optional[str] = None
|
rrule: Optional[str] = None
|
||||||
exdate: Optional[str] = None
|
exdate: Optional[str] = None
|
||||||
private: Optional[bool] = None
|
private: Optional[bool] = None
|
||||||
|
reminders: Optional[List[int]] = None
|
||||||
|
|
||||||
|
|
||||||
class ShareCreate(BaseModel):
|
class ShareCreate(BaseModel):
|
||||||
@@ -247,6 +249,7 @@ def create_event(
|
|||||||
color=data.color,
|
color=data.color,
|
||||||
rrule=data.rrule,
|
rrule=data.rrule,
|
||||||
is_private=data.private,
|
is_private=data.private,
|
||||||
|
reminders=(",".join(str(m) for m in data.reminders) if data.reminders else None),
|
||||||
creator_id=current_user.id, # server-side, never from the client
|
creator_id=current_user.id, # server-side, never from the client
|
||||||
)
|
)
|
||||||
db.add(ev)
|
db.add(ev)
|
||||||
@@ -296,6 +299,8 @@ def update_event(
|
|||||||
if data.exdate not in dates:
|
if data.exdate not in dates:
|
||||||
dates.append(data.exdate)
|
dates.append(data.exdate)
|
||||||
ev.exdate = ",".join(dates)
|
ev.exdate = ",".join(dates)
|
||||||
|
if data.reminders is not None:
|
||||||
|
ev.reminders = ",".join(str(m) for m in data.reminders) if data.reminders else None
|
||||||
db.commit()
|
db.commit()
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class SettingsUpdate(BaseModel):
|
|||||||
bg_color: Optional[str] = None
|
bg_color: Optional[str] = None
|
||||||
private_event_visibility: Optional[str] = None
|
private_event_visibility: Optional[str] = None
|
||||||
group_visible_calendar_id: Optional[int] = None
|
group_visible_calendar_id: Optional[int] = None
|
||||||
|
default_reminder_minutes: Optional[int] = None # null = off
|
||||||
|
|
||||||
|
|
||||||
def _settings_dict(s: models.UserSettings) -> dict:
|
def _settings_dict(s: models.UserSettings) -> dict:
|
||||||
@@ -50,6 +51,7 @@ def _settings_dict(s: models.UserSettings) -> dict:
|
|||||||
"bg_color": s.bg_color,
|
"bg_color": s.bg_color,
|
||||||
"private_event_visibility": s.private_event_visibility or "busy",
|
"private_event_visibility": s.private_event_visibility or "busy",
|
||||||
"group_visible_calendar_id": s.group_visible_calendar_id,
|
"group_visible_calendar_id": s.group_visible_calendar_id,
|
||||||
|
"default_reminder_minutes": s.default_reminder_minutes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -92,7 +94,7 @@ def update_settings(
|
|||||||
# For these three override colours, an explicit null is meaningful
|
# For these three override colours, an explicit null is meaningful
|
||||||
# ("reset to default") and must be persisted as NULL. All other fields
|
# ("reset to default") and must be persisted as NULL. All other fields
|
||||||
# keep the previous behaviour where a null/missing value is ignored.
|
# keep the previous behaviour where a null/missing value is ignored.
|
||||||
NULLABLE_OVERRIDES = {"text_color", "line_color", "bg_color", "group_visible_calendar_id"}
|
NULLABLE_OVERRIDES = {"text_color", "line_color", "bg_color", "group_visible_calendar_id", "default_reminder_minutes"}
|
||||||
update_data = data.model_dump(exclude_unset=True)
|
update_data = data.model_dump(exclude_unset=True)
|
||||||
for field, value in update_data.items():
|
for field, value in update_data.items():
|
||||||
if field in NULLABLE_OVERRIDES:
|
if field in NULLABLE_OVERRIDES:
|
||||||
|
|||||||
Reference in New Issue
Block a user