{"record":{"id":"ff3b54f13113676a","repo":"odysseus-dev/odysseus","slug":"event-not-found","errorCode":null,"errorMessage":"Event not found","messagePattern":"Event not found","errorType":"http","errorClass":"HTTPException","httpStatus":404,"severity":"warning","filePath":"routes/calendar_routes.py","lineNumber":100,"sourceCode":"\ndef _get_or_404_calendar(db, cal_id: str, owner: str) -> CalendarCal:\n    cal = db.query(CalendarCal).filter(CalendarCal.id == cal_id).first()\n    if not cal:\n        raise HTTPException(404, \"Calendar not found\")\n    # Tighten the legacy null-owner gate (v2 review HIGH-12): if the\n    # caller is authenticated AND the calendar's owner is null OR\n    # belongs to a different user, treat it as not-found. The previous\n    # rule (`if cal.owner and cal.owner != owner`) silently allowed any\n    # authenticated user to read/edit any calendar with owner=None.\n    if owner and (cal.owner is None or cal.owner != owner):\n        raise HTTPException(404, \"Calendar not found\")\n    return cal\n\n\ndef _get_or_404_event(db, uid: str, owner: str) -> CalendarEvent:\n    ev = db.query(CalendarEvent).join(CalendarCal).filter(CalendarEvent.uid == uid).first()\n    if not ev:\n        raise HTTPException(404, \"Event not found\")\n    cal = ev.calendar\n    if owner and cal and (cal.owner is None or cal.owner != owner):\n        raise HTTPException(404, \"Event not found\")\n    return ev\n\n\ndef _ics_escape(text: str) -> str:\n    \"\"\"Escape a value for an iCalendar TEXT field (RFC 5545 §3.3.11).\n\n    Backslash, semicolon and comma are structural in TEXT values and must be\n    escaped, and newlines become a literal ``\\\\n``. Backslash is escaped first\n    so the escapes we add aren't re-escaped.\n    \"\"\"\n    return (\n        (text or \"\")\n        .replace(\"\\\\\", \"\\\\\\\\\")\n        .replace(\";\", \"\\\\;\")\n        .replace(\",\", \"\\\\,\")","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/calendar_routes.py#L82-L118","documentation":"HTTP 404 raised by _get_or_404_event when no CalendarEvent row matches the given uid (joined to its calendar). Raised before any ownership check, so it covers both nonexistent and malformed uids.","triggerScenarios":"Any /api/calendar event route with a uid not in the events table: deleted event, occurrence uid from a different instance, truncated uid in a URL.","commonSituations":"Editing an event that was deleted from another client; stale UI state; compound recurrence uids passed where the base uid is stored (or vice versa).","solutions":["Refresh events from the server and retry with a current uid.","For recurring events, resolve the base uid with _resolve_base_uid semantics (:: strips the date suffix) before calling event endpoints.","Handle 404 by removing the stale event from local UI state."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"function baseUid(uid) {\n  const i = uid.indexOf('::');\n  const base = i === -1 ? uid : uid.slice(0, i);\n  if (!base) throw new Error('bad uid');\n  return base;\n}","typeGuard":null,"tryCatchPattern":"catch (e) { if (e.status === 404) { removeLocalEvent(uid); } else throw e; }","preventionTips":["For recurring occurrences, send the compound uid exactly as the server returned it.","Refresh events before editing ones that may have changed elsewhere.","Drop 404-ing events from local state instead of retrying."],"tags":["not-found","http-404","calendar","events","fastapi"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}