feat(events): recurring expansion, CalDAV pull sync, past search, reminders

- RRULE expansion: list_events now expands recurring events into
  individual occurrences within the query window using python-dateutil
- CalDAV pull sync: new caldav_sync.py + POST /api/events/sync route;
  imports remote events into the internal store by caldav_uid
- Past event search: search_events accepts include_past=true to search
  historical events; exposed in the LLM tool definition
- Internal reminders: migration 0037 adds reminder_minutes +
  reminder_sent_at columns; event_scheduler.py checks every 5 min and
  fires push notifications; CalDAV sync job runs hourly
- reminder_minutes now stored and returned in create/update routes + tools

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 12:15:37 -04:00
parent 358534efbf
commit edfed6b5bb
8 changed files with 454 additions and 23 deletions
+12 -2
View File
@@ -40,7 +40,7 @@ async def list_events():
date_from=date_from,
date_to=date_to,
)
return jsonify([e.to_dict() for e in events])
return jsonify(events)
@events_bp.post("")
@@ -65,6 +65,7 @@ async def create_event():
color=data.get("color", ""),
recurrence=data.get("recurrence"),
project_id=data.get("project_id"),
reminder_minutes=data.get("reminder_minutes"),
)
return jsonify(event.to_dict()), 201
@@ -92,7 +93,7 @@ async def update_event(event_id: int):
for bool_field in ("all_day",):
if bool_field in data:
fields[bool_field] = data[bool_field]
for int_field in ("project_id",):
for int_field in ("project_id", "reminder_minutes"):
if int_field in data:
fields[int_field] = data[int_field]
for dt_field in ("start_dt", "end_dt"):
@@ -129,3 +130,12 @@ async def delete_event(event_id: int):
event_id=event_id,
)
return "", 204
@events_bp.post("/sync")
@login_required
async def sync_caldav():
"""Trigger a CalDAV pull sync for the current user."""
from fabledassistant.services.caldav_sync import sync_user_events
result = await sync_user_events(user_id=_get_current_user_id())
return jsonify(result)