Follow calendars somebody else keeps, read-only #1234

Merged
tiagoagueda merged 5 commits from f-1098 into main 2026-11-12 06:30:31 +00:00
Owner

Closes #1098: Calendar → Subscriptions follows a calendar somebody else keeps, by its address, read-only, refreshed by the scheduler.

  • One card per calendar: name and colour, the address shown as its site only, the interval (hourly, four-hourly, daily), when it was last read and how many events it held, what was left out and why, and while it fails the reason and the next try. Read it now, Edit, Delete.
  • Its events are drawn on the calendar in its colour, marked as from it (also for screen readers); one opens a read-only page from which a timed event can be added once as an appointment or an interview.
  • The decided shape: parsed events in a table of their own, replaced on each refresh; core, beside the importer it reuses (ical_reader); private addresses refused unless POSTULO_CONNECTIONS_ALLOW_PRIVATE, checked on every request and redirect; 2 MB and 30 s limits; fetched through plugins.http.client.
  • The scheduler reads each subscription at most once per interval, claims it first, stops starting readings after 60 s, and backs a failing address off with #1162's rule.
  • The address is encrypted and never logged or exported. Migration applications 0022_calendar_subscriptions; archive format 81 (subscriptions and their events, never the address).

Choices where the issue was silent, for review: repeating events and times without a zone are left out (and counted on the card); subscribed events are left out of the calendar's .ics download and of what plugins read; no API endpoints; the pages follow the iCalendar module's switch.

Tested: the core change fails its main test without it; 19 unit tests and 6 browser tests (axe light, dark and right to left, 320 px, target size). New strings are extracted for Weblate.

Note for landing: #1233 (#1099) also takes archive format 81; whichever merges second moves to 82.

🤖 Generated with Claude Code

Closes #1098: Calendar → Subscriptions follows a calendar somebody else keeps, by its address, read-only, refreshed by the scheduler. - One card per calendar: name and colour, the address shown as its site only, the interval (hourly, four-hourly, daily), when it was last read and how many events it held, what was left out and why, and while it fails the reason and the next try. *Read it now*, *Edit*, *Delete*. - Its events are drawn on the calendar in its colour, marked as from it (also for screen readers); one opens a read-only page from which a timed event can be added once as an appointment or an interview. - The decided shape: parsed events in a table of their own, replaced on each refresh; core, beside the importer it reuses (`ical_reader`); private addresses refused unless `POSTULO_CONNECTIONS_ALLOW_PRIVATE`, checked on every request and redirect; 2 MB and 30 s limits; fetched through `plugins.http.client`. - The scheduler reads each subscription at most once per interval, claims it first, stops starting readings after 60 s, and backs a failing address off with #1162's rule. - The address is encrypted and never logged or exported. Migration `applications 0022_calendar_subscriptions`; archive format 81 (subscriptions and their events, never the address). Choices where the issue was silent, for review: repeating events and times without a zone are left out (and counted on the card); subscribed events are left out of the calendar's `.ics` download and of what plugins read; no API endpoints; the pages follow the iCalendar module's switch. Tested: the core change fails its main test without it; 19 unit tests and 6 browser tests (axe light, dark and right to left, 320 px, target size). New strings are extracted for Weblate. Note for landing: #1233 (#1099) also takes archive format 81; whichever merges second moves to 82. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Postulo published feeds and imported an .ics file once, and could not follow a calendar
that keeps changing: an employment office's appointments, a training provider's sessions,
a calendar a career coach shares. Calendar -> Subscriptions now takes an address, a name, a
colour and an interval, and draws that calendar's events beside the person's own.

- Two tables (applications 0022_calendar_subscriptions): `CalendarSubscription`, owned,
  and `SubscribedEvent`, hanging off it, replaced whole on every reading that works and
  never edited. A reading that fails keeps what the last one held.
- Every fetch goes through the connections' guarded client: private and local addresses
  are refused unless POSTULO_CONNECTIONS_ALLOW_PRIVATE allows them (in the form, before the
  address is kept, and again on every request and redirect), the body is held to the
  importer's 2 MB and the download to 30 s. webcal:// is read over https. The text is read
  by the importer's own `ical_reader`, with a larger entry limit; repeating events, times
  with no usable zone and tasks are left out and counted on the card.
- The scheduler pass reads each subscription at most once per its interval (hour, four
  hours, day), claimed first so two schedulers do not both read it, within a pass-wide
  budget of 60 s. One that fails backs off as a failing sync does (#1162), up to a day, and
  its card says why and when it will be tried again. *Read it now* is held to 12 an hour.
- The address is a secret: kept encrypted as a connection's secrets are, shown back only
  as its site, never put back in the form, never logged (a failure logs the number and the
  kind of error), and not in the archive.
- On the calendar the events are a kind of their own, *Subscribed calendars*, in the
  subscription's colour, with a link mark and, for a screen reader, the subscription's
  name; the legend offers the kind only to somebody who has a subscription. Each links to a
  read-only page that can add it as an appointment or an interview of the person's own,
  once. They are left out of the page's .ics download and of what a calendar plugin reads
  (`items_between`); the print carries them.
- Archive format 81 carries each subscription's name, colour, interval and events, not its
  address; a restored one keeps its events and asks for the address again. They go with
  the account by cascade. Pages and the refresh are under the iCalendar module, as the
  import is.

Closes #1098

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Record #1098 in the changelog and extract its strings
Some checks failed
CI / Unit tests and coverage (Python 3.14) (pull_request) Failing after 15m3s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 2m17s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m21s
CI / Security audit (pull_request) Successful in 19s
CI / Browser tests (Chromium) (pull_request) Successful in 35m31s
a935c3efb7
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tiagoagueda force-pushed f-1098 from a935c3efb7
Some checks failed
CI / Unit tests and coverage (Python 3.14) (pull_request) Failing after 15m3s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 2m17s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m21s
CI / Security audit (pull_request) Successful in 19s
CI / Browser tests (Chromium) (pull_request) Successful in 35m31s
to d7cc68e382
Some checks failed
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
2026-11-12 02:14:05 +00:00
Compare
tiagoagueda force-pushed f-1098 from d7cc68e382
Some checks failed
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
to 41f9f50083
Some checks failed
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 21m25s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 2m25s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m11s
CI / Security audit (pull_request) Successful in 22s
CI / Browser tests (Chromium) (pull_request) Successful in 44m37s
CI / Unit tests and coverage (Python 3.14) (push) Successful in 16m18s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 2m34s
CI / Database tests on PostgreSQL (push) Successful in 4m52s
CI / Security audit (push) Successful in 26s
README / README language tables (push) Successful in 24s
CI / Browser tests (Chromium) (push) Successful in 41m33s
Dev image / Dev image: build, scan, push (push) Failing after 59m54s
2026-11-12 04:54:46 +00:00
Compare
tiagoagueda deleted branch f-1098 2026-11-12 06:30:34 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo!1234
No description provided.