Add birthday calendar backend support

Birthday calendars are ordinary local calendars flagged is_birthday, so they
flow to all clients via the merge read and inherit sharing/colors/reminders.

- models: LocalCalendar.is_birthday + birthday_notify_days_before;
  LocalEvent.external_uid (Contacts dedup) + birth_year
- build_local_event_dict: server-computed display_title "Name (age)" per
  occurrence, is_birthday flag for the client cake icon, and a reminder injected
  from birthday_notify_days_before so mobile schedulers fire it
- groups combined view keeps the birthday display_title instead of overwriting it
- local_router: calendar flags + event fields on create/update, plus
  GET /calendars/{id}/birthdays for importer reconcile by external_uid
- additive SQLite migrations

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-07-12 21:38:37 +02:00
parent d426f8985c
commit ca09538971
5 changed files with 123 additions and 4 deletions

View File

@@ -420,10 +420,13 @@ def combined_events(
b["display_color"] = group_cal_color if is_group else member_color.get(owner_id)
# Decorated title (group icon / owner name) computed server-side
# so all clients render identically; raw `title` kept for editing.
b["display_title"] = _decorate_title(
b.get("title", ""), is_group=is_group, creator=b.get("creator"),
owner=owner, me_id=current_user.id,
)
# A birthday event already carries an age display_title from
# build_local_event_dict — keep it rather than clobber the age.
if not b.get("is_birthday"):
b["display_title"] = _decorate_title(
b.get("title", ""), is_group=is_group, creator=b.get("creator"),
owner=owner, me_id=current_user.id,
)
all_events.append(b)
# Each member shares exactly one calendar into their groups, chosen in their