feat: deferred dispatch, release-check provider, settings polish

- Defer quiet-hours dispatches into new deferred_dispatch table; drain
  job + periodic catch-up scan re-fire at window end with coalescing on
  (link, event_type, collection_id).
- Add ON DELETE SET NULL migration on event_log_id and partial unique
  index on (link_id, collection_id, event_type) WHERE status='pending'.
- Add release-check provider abstraction (Gitea/GitHub) with SSRF-safe
  URL validation, settings UI cassette, and scheduled polling.
- Replace importlib-only version lookup with version.py helper that
  prefers the higher of installed metadata vs source pyproject so stale
  editable dev installs stop misreporting.
- Aurora frontend polish: MetaStrip component, ReleaseCassette,
  EventDetailModal expansion, and i18n additions.
This commit is contained in:
2026-05-12 02:58:07 +03:00
parent bb5afcc222
commit ba199f24bd
47 changed files with 5627 additions and 290 deletions
@@ -42,8 +42,9 @@ from ..database.models import (
TrackingConfig,
)
from .dispatch_helpers import (
GateReason,
apply_tracking_display_filters,
event_allowed_by_config,
evaluate_event_gate,
get_app_timezone,
load_link_data,
)
@@ -262,7 +263,11 @@ async def dispatch_scheduled_for_tracker(
if tc is not None:
if not getattr(tc, f"{kind}_enabled", True):
continue
if not event_allowed_by_config(event, tc, app_tz):
# Scheduled / periodic / memory dispatches are wall-clock
# by nature — a "good morning" delivered at 3 pm is wrong,
# so quiet hours = drop (not defer) for these kinds. The
# other gate (per-event-type flag) still applies.
if not evaluate_event_gate(event, tc, app_tz).allowed:
continue
if tmpl is None:
continue