feat(phase3): reminders, ICS feeds, templates and nightly backups
Reminders are absolute or relative ("10 minutes before"). A relative one
is resolved against the task in SQL, so moving a task moves its reminders
too. They are marked sent before delivery, not after: a reminder that
goes out unrecorded repeats every minute forever, which is worse than
one recorded that fails to send.
The calendar feed is a per-user token in the path, because Apple Calendar
and Google fetch with no credentials and no way to prompt for any. Tasks
go out as VTODO — the same component CalDAV will serve — and timed ones
also as VEVENT, since Google ignores VTODO entirely.
Templates store relative day offsets rather than dates, which is what
makes them reusable: an onboarding checklist is "day 1, day 3, day 14",
not three dates in March. Building a project from one is a single
transaction, so a failure cannot leave half a checklist.
Backups use SQLite's VACUUM INTO, not a file copy — a copy of a live
WAL database can miss recent writes or catch a page mid-update, and the
result looks like a database right up until it is needed. Rotation keeps
fourteen by count, not by age: a container that was off for a month must
not come back and delete every backup it has.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4d142b2fa7
commit
7e4c730049
@@ -0,0 +1,51 @@
|
||||
-- Calendar feeds, project templates and reminder delivery.
|
||||
|
||||
-- A per-user token for the read-only calendar feed.
|
||||
--
|
||||
-- Calendar clients cannot log in — Apple Calendar and Google both fetch a URL with
|
||||
-- no credentials and no way to prompt for any — so the URL itself is the
|
||||
-- capability. That is why the token is separate from the session and from the API
|
||||
-- token: it can be rotated on its own when a feed URL ends up somewhere it should
|
||||
-- not, without signing the person out of everything else.
|
||||
ALTER TABLE users ADD COLUMN ics_token TEXT;
|
||||
|
||||
CREATE UNIQUE INDEX idx_users_ics_token ON users (ics_token) WHERE ics_token IS NOT NULL;
|
||||
|
||||
-- A project saved as a shape to reuse: its sections and tasks, without dates,
|
||||
-- assignees or anything else that belonged to the run it was captured from.
|
||||
--
|
||||
-- The body is JSON rather than a set of tables. A template is written once and read
|
||||
-- whole; normalising it would buy queries nobody makes, and cost a migration every
|
||||
-- time a task gains a field.
|
||||
CREATE TABLE project_templates (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
color TEXT NOT NULL DEFAULT 'graphite',
|
||||
body_json TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX idx_templates_user ON project_templates (user_id);
|
||||
|
||||
-- Reminders already exist as a table; what was missing is the ability to find the
|
||||
-- ones that are due. A relative reminder ("10 minutes before") has no absolute time
|
||||
-- of its own, so the scheduler resolves it against the task's due_datetime — and
|
||||
-- this index is what keeps that sweep from being a full scan every minute.
|
||||
CREATE INDEX idx_reminders_task ON reminders (task_id) WHERE sent_at IS NULL;
|
||||
|
||||
-- Where a nightly backup got to. One row per run, so the panel can show when the
|
||||
-- last one succeeded and an operator can tell "no backups configured" apart from
|
||||
-- "backups have been failing for a week".
|
||||
CREATE TABLE backup_runs (
|
||||
id TEXT PRIMARY KEY,
|
||||
started_at INTEGER NOT NULL,
|
||||
finished_at INTEGER,
|
||||
path TEXT,
|
||||
size_bytes INTEGER,
|
||||
error TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX idx_backup_runs_started ON backup_runs (started_at DESC);
|
||||
Reference in New Issue
Block a user