feat(sharing): recipients can recolour a shared calendar (per-user, no rename)

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>
This commit is contained in:
Scarriffle
2026-07-06 20:40:13 +02:00
parent cad48efcc6
commit 343f7a5e7b
4 changed files with 46 additions and 4 deletions

View File

@@ -70,11 +70,13 @@ class ShareCreate(BaseModel):
def _cal_dict(cal: models.LocalCalendar, *, owned: bool = True,
shared_by: Optional[str] = None, permission: Optional[str] = None,
color_override: Optional[str] = None,
request: Optional[Request] = None) -> dict:
d = {
"id": cal.id,
"name": cal.name,
"color": cal.color,
# A recipient's own colour for a shared calendar wins over the owner's.
"color": color_override or cal.color,
"enabled": cal.enabled,
"reminders_enabled": bool(cal.reminders_enabled),
"caldav_published": bool(cal.caldav_published),
@@ -150,6 +152,7 @@ def list_calendars(
cal, owned=False,
shared_by=(owner.display_name or owner.username) if owner else None,
permission=share.permission,
color_override=share.color,
)
if cal.id in group_cal_map:
d["group"] = True
@@ -246,6 +249,33 @@ def update_calendar(
return _cal_dict(cal, owned=True, request=request)
class CalendarColorUpdate(BaseModel):
color: str
@router.put("/calendars/{calendar_id}/color")
def set_calendar_color(
calendar_id: int,
data: CalendarColorUpdate,
db: Session = Depends(get_db),
current_user: models.User = Depends(get_current_user),
):
"""Set a calendar's colour. The owner changes the calendar's colour for
everyone; a share recipient sets only their OWN per-user colour (stored on
the share) without touching the owner's. Recipients may recolour but never
rename a shared calendar."""
cal = permissions.accessible_local_calendar(db, current_user, calendar_id)
if cal.user_id == current_user.id:
cal.color = data.color
else:
share = permissions._share_for(db, calendar_id, current_user.id)
if share is None:
raise HTTPException(403, "Only the owner or a share recipient can set the colour")
share.color = data.color
db.commit()
return {"ok": True, "color": data.color}
@router.post("/calendars/{calendar_id}/dav-token/rotate")
def rotate_dav_token(
calendar_id: int,