fix(events): normalize naive datetimes to UTC in HTTP route
All datetime parsing now uses _parse_dt() which adds UTC tzinfo when none is present, matching the fix already applied in tools.py. This prevents asyncpg errors when comparing naive datetimes against TIMESTAMPTZ columns — the root cause of events not appearing in the calendar view. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
"""Calendar events REST API."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from quart import Blueprint, g, jsonify, request
|
||||
|
||||
@@ -11,6 +11,14 @@ import fabledassistant.services.events as events_svc
|
||||
events_bp = Blueprint("events", __name__, url_prefix="/api/events")
|
||||
|
||||
|
||||
def _parse_dt(value: str) -> datetime:
|
||||
"""Parse ISO 8601 datetime string, ensuring UTC-awareness."""
|
||||
dt = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
||||
if dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
return dt
|
||||
|
||||
|
||||
def _get_current_user_id() -> int:
|
||||
return g.user.id
|
||||
|
||||
@@ -23,8 +31,8 @@ async def list_events():
|
||||
if not date_from_str or not date_to_str:
|
||||
return jsonify({"error": "from and to query params are required"}), 400
|
||||
try:
|
||||
date_from = datetime.fromisoformat(date_from_str)
|
||||
date_to = datetime.fromisoformat(date_to_str)
|
||||
date_from = _parse_dt(date_from_str)
|
||||
date_to = _parse_dt(date_to_str)
|
||||
except ValueError:
|
||||
return jsonify({"error": "Invalid datetime format"}), 400
|
||||
events = await events_svc.list_events(
|
||||
@@ -42,8 +50,8 @@ async def create_event():
|
||||
if not data.get("title") or not data.get("start_dt"):
|
||||
return jsonify({"error": "title and start_dt are required"}), 400
|
||||
try:
|
||||
start_dt = datetime.fromisoformat(data["start_dt"])
|
||||
end_dt = datetime.fromisoformat(data["end_dt"]) if data.get("end_dt") else None
|
||||
start_dt = _parse_dt(data["start_dt"])
|
||||
end_dt = _parse_dt(data["end_dt"]) if data.get("end_dt") else None
|
||||
except ValueError:
|
||||
return jsonify({"error": "Invalid datetime format"}), 400
|
||||
event = await events_svc.create_event(
|
||||
@@ -90,7 +98,7 @@ async def update_event(event_id: int):
|
||||
for dt_field in ("start_dt", "end_dt"):
|
||||
if dt_field in data and data[dt_field]:
|
||||
try:
|
||||
fields[dt_field] = datetime.fromisoformat(data[dt_field])
|
||||
fields[dt_field] = _parse_dt(data[dt_field])
|
||||
except ValueError:
|
||||
return jsonify({"error": f"Invalid datetime for {dt_field}"}), 400
|
||||
event = await events_svc.update_event(
|
||||
|
||||
Reference in New Issue
Block a user