Skip to content

feat(feed): allow per-feed refresh interval override#4293

Open
ChrisJr404 wants to merge 1 commit into
miniflux:mainfrom
ChrisJr404:feat/per-feed-refresh-interval-412
Open

feat(feed): allow per-feed refresh interval override#4293
ChrisJr404 wants to merge 1 commit into
miniflux:mainfrom
ChrisJr404:feat/per-feed-refresh-interval-412

Conversation

@ChrisJr404

Copy link
Copy Markdown

Closes #412.

Description

Adds an optional refresh_interval_minutes column on feeds. When set, it overrides the global polling frequency for that feed only. NULL (the default) keeps the existing behaviour, so the migration does not disturb existing installations.

The override is honoured by Feed.ScheduleNextCheck. The HTTP refresh delay (RSS TTL, Retry-After, Cache-Control, Expires) is still applied as a floor, so a small override cannot be used to ignore a server's explicit back-off.

Motivation

Issue #412 (44+ thumbs-up, multi-year ask) reports that some users want to refresh certain feeds more often than the global POLLING_FREQUENCY (e.g. fast news, status pages) while keeping a longer interval for others (e.g. arXiv, blogs that 429 if you fetch them too aggressively). Today everything moves at one global rate, which leads to either missed updates or rate-limit bans depending on which way the user tunes the global value.

The earlier attempt in #949 was closed in 2021, partly because at that point per-user polling was tangled with multi-user semantics. Each feeds row already belongs to a single user_id, so a per-row override is well-defined and does not require touching the multi-user feed-fetching design at all.

Changes

  • Database: new refresh_interval_minutes integer column on feeds, NULL by default, with a CHECK constraint preventing values < 1. Added as a fresh migration step at the end of internal/database/migrations.go.
  • Scheduler: Feed.ScheduleNextCheck honours the override when set. When NULL or zero, the existing global scheduler logic runs unchanged.
  • REST API: model.Feed, FeedCreationRequest and FeedModificationRequest gain a refresh_interval_minutes field. On modification, sending 0 clears the override; omitting the field leaves it unchanged. Mirrored in client/model.go so existing Go consumers work too.
  • UI: Edit Feed page exposes a Refresh interval (minutes) number input with help text. 0 (or empty) means "use the global default".
  • Validation: value must be between MinFeedRefreshIntervalMinutes (5) and MaxFeedRefreshIntervalMinutes (10080, one week). The floor prevents a user from accidentally pointing a tighter interval at a publisher than the configured global minimum.
  • i18n: new error.feed_invalid_refresh_interval, form.feed.label.refresh_interval_minutes and form.feed.help.refresh_interval_minutes keys added to all 22 translation files (English text used as a placeholder for the non-English files; happy to drop those if maintainers prefer to add them in a follow-up).

Testing

  • go build ./..., go vet ./... and go test ./... all clean.
  • New unit tests in internal/model/feed_test.go:
    • TestFeedScheduleNextCheckRefreshIntervalOverride exercises the basic override.
    • TestFeedScheduleNextCheckRefreshIntervalOverrideRespectsRefreshDelay confirms that a Retry-After longer than the override still wins.
    • TestFeedScheduleNextCheckRefreshIntervalOverrideIgnoresGlobalCap confirms the override is not clamped by the global round-robin maximum.
    • TestFeedScheduleNextCheckRefreshIntervalNilFallsBackToGlobal and ...ZeroFallsBackToGlobal confirm the existing scheduler runs unchanged when the override is unset.
    • Three TestFeedModificationRequestPatch* cases cover set / clear (zero) / leave-alone (nil) semantics.
  • internal/validator/feed_test.go covers the bounds check.
  • internal/api/api_integration_test.go adds TestUpdateFeedRefreshInterval, which drives create -> read (must be nil) -> set 120 -> read back -> clear with 0 -> attempt to set 1 (must be rejected) through the real HTTP API.

Breaking changes

None. The new column is NULL by default; the API field is *int so existing clients that don't send it continue to work unchanged.

Related issues

Closes miniflux#412.

Adds an optional refresh_interval_minutes column on feeds. When set, it
overrides the global polling frequency for that feed only. NULL (the
default) keeps the existing behaviour, so existing installs are
unaffected by the migration.

The override is honoured by Feed.ScheduleNextCheck. The HTTP refresh
delay (RSS TTL, Retry-After, Cache-Control, Expires) is still applied
as a floor so a small override cannot be used to ignore a server's
explicit back-off.

Exposed in three places:

- The Edit Feed page gets a "Refresh interval (minutes)" input. Leaving
  it at zero clears the override and falls back to the global
  scheduler.
- The REST API Feed, FeedCreationRequest and FeedModificationRequest
  payloads gain a refresh_interval_minutes field. Sending zero on a
  modification clears the override; omitting the field leaves it
  unchanged.
- The Go client (client/model.go) exposes the same field so existing
  third-party integrations can read and write it.

Validation enforces 5 <= value <= 10080 minutes (one week, matching
the round-robin cap). Below the floor would let users hammer
publishers harder than the configured global minimum.

Tested:

- New unit tests in internal/model and internal/validator cover the
  override path, the refresh-delay floor, the global-cap behaviour,
  the nil/zero fallback, and the Patch semantics for set/clear/leave.
- Added an API integration test (TestUpdateFeedRefreshInterval) that
  exercises the full create -> read -> set -> clear -> reject path
  through the HTTP API.
- go build ./... and go test ./... pass; go vet clean.
@jvoisin

jvoisin commented May 8, 2026

Copy link
Copy Markdown
Collaborator

A dumb issue with allowing users to set their own refresh rates is that this allows someone to set a super-short one, getting the IP address of the miniflux instance blacklisted/429 for the other users :/

@ChrisJr404

Copy link
Copy Markdown
Author

Fair concern. The PR has two protections that should cover the abuse case:

  1. The override is clamped between 5 and 10080 minutes (validated in internal/validator/feed.go). 5 minutes is the same floor that protects against accidental misconfig elsewhere in the codebase. There is also a test that an attempted 1-minute override is rejected at the API layer.
  2. Even when the override says "5 minutes", Feed.ScheduleNextCheck still applies the HTTP refresh delay as a floor, so if a server sent Retry-After: 3600 or a long Cache-Control: max-age, the override does not override that. That part is covered by the RespectsRefreshDelay test.

So a hostile user setting the shortest legal value (5 min) on a feed whose origin returns Retry-After: 6h would still wait 6 hours between fetches per the server hint.

Happy to also add a per-instance setting if you would prefer the floor to be operator-configurable rather than hardcoded to 5. Let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

[Feature request] Fresh frequency setting for single feed

2 participants