Add the Home Assistant REST calendar client and the idempotent sync that writes important school events to Sam's Home Assistant calendar (FR-8). The school stays read-only; the only write is to the calendar (rule 7). - internal/calendar: HA REST client (states, create_event, get_events, delete_event) and the sync that selects important future events, adopts an existing event by its description marker, records the HA event id on the item, and records every attempt. - internal/store: migration 003 (items.ha_event_uid, items.ha_pushed_at and the append-only calendar_pushes table), candidates, mark-synced and the push log. - internal/parse: a school event is important; a routine class period is not. - cmd/school: `school calendar entities|status|push|events`. - web: an "Add to calendar" item action and a fail-loud calendar banner, so a failed write is never swallowed (FR-12). - scripts/calendar-push.sh, Makefile target, env example, docs and tests. The target is configurable (SCHOOL_HA_CALENDAR_ENTITY). Home Assistant has no non-Google writable calendar today, so the entity is asked on AUD-110. Co-Authored-By: Paperclip <noreply@paperclip.ing>
25 lines
1.1 KiB
SQL
25 lines
1.1 KiB
SQL
-- Migration 003 — Phase 6 calendar write (AGENTS.md §4 FR-8, §11 Phase 6).
|
|
--
|
|
-- Important school events are written to Sam's Home Assistant calendar. The
|
|
-- write must be idempotent: the Home Assistant event id is stored on the item,
|
|
-- so a re-run updates nothing and never duplicates an event. The push log is
|
|
-- append-only, so a failed calendar write is visible and loud instead of being
|
|
-- swallowed (FR-12).
|
|
|
|
ALTER TABLE items ADD COLUMN ha_event_uid TEXT NOT NULL DEFAULT '';
|
|
ALTER TABLE items ADD COLUMN ha_pushed_at TEXT;
|
|
|
|
-- One row per calendar write attempt. ok = 0 with an error is a loud failure.
|
|
CREATE TABLE IF NOT EXISTS calendar_pushes (
|
|
id INTEGER PRIMARY KEY,
|
|
item_id INTEGER NOT NULL REFERENCES items(id),
|
|
entity_id TEXT NOT NULL,
|
|
uid TEXT,
|
|
ok INTEGER NOT NULL DEFAULT 0,
|
|
error TEXT,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_calendar_pushes_item ON calendar_pushes(item_id);
|
|
CREATE INDEX IF NOT EXISTS idx_calendar_pushes_created ON calendar_pushes(created_at);
|