infra/stacks/f1-stream/files/.planning/PROJECT.md
Viktor Barzin c7c7047f1c [ci skip] Flatten module wrappers into stack roots
Remove the module "xxx" { source = "./module" } indirection layer
from all 66 service stacks. Resources are now defined directly in
each stack's main.tf instead of through a wrapper module.

- Merge module/main.tf contents into stack main.tf
- Apply variable replacements (var.tier -> local.tiers.X, renamed vars)
- Fix shared module paths (one fewer ../ at each level)
- Move extra files/dirs (factory/, chart_values, subdirs) to stack root
- Update state files to strip module.<name>. prefix
- Update CLAUDE.md to reflect flat structure

Verified: terragrunt plan shows 0 add, 0 destroy across all stacks.
2026-02-22 15:13:55 +00:00

4.1 KiB

F1 Stream

What This Is

A self-hosted web app that aggregates live Formula 1 streaming links from Reddit and user submissions, presenting them in a clean UI with embedded iframes. It scrapes r/motorsportsstreams2, allows users to submit their own stream URLs, and provides admin controls for content moderation. Built in Go with vanilla JS frontend, deployed on Kubernetes.

Core Value

Users can find working F1 streams quickly — the app automatically discovers, validates, and surfaces healthy streams while removing dead ones.

Requirements

Validated

  • ✓ Reddit scraper polls r/motorsportsstreams2 for F1-related posts — existing
  • ✓ URL extraction from post bodies and comment trees — existing
  • ✓ F1 keyword filtering with negative keyword exclusion — existing
  • ✓ Domain filtering (reddit, imgur, youtube, twitter excluded) — existing
  • ✓ Deduplication via normalized URLs — existing
  • ✓ User stream submission (anonymous + authenticated) — existing
  • ✓ WebAuthn passwordless authentication — existing
  • ✓ Admin approval workflow for user-submitted streams — existing
  • ✓ HTTP proxy with rate limiting, private IP blocking, CSP stripping — existing
  • ✓ Static frontend with iframe-based stream viewing — existing
  • ✓ Default seed streams on first run — existing
  • ✓ Stale link cleanup (24h) — existing
  • ✓ Client-side health sort (reorder by reachability) — existing

Active

  • Scraper validates extracted URLs look like actual streams (video/player content), not random links
  • Server-side health checker runs every 5 minutes against all known streams
  • Health check: HTTP reachability check first, then proxy-fetch to detect video/player markers
  • Configurable health check timeout
  • Streams marked unhealthy after 5 consecutive check failures get hidden from public page
  • Unhealthy streams retried on each check cycle — restored if they recover
  • Scraped streams that pass health checks auto-published to main streams page
  • Dead streams dynamically removed from the page without manual intervention
  • Health status persisted (failure count, last check time, healthy/unhealthy state)

Out of Scope

  • Database migration (SQLite/PostgreSQL) — file-based storage is fine for this scope
  • Multiple subreddit sources — stick with r/motorsportsstreams2 for now
  • Real-time WebSocket push of stream status — polling is sufficient
  • Mobile app — web-only
  • OAuth/social login — WebAuthn is sufficient

Context

  • The app runs on a personal Kubernetes cluster, deployed via Terraform
  • Single-user / small-group usage — performance at scale is not a concern
  • The existing client-side sortStreamsByHealth does a basic no-cors fetch but can't inspect content; server-side checks via the proxy can do deeper validation
  • Reddit's public JSON API requires no auth but rate-limits aggressively; the scraper already handles 429s with backoff
  • Stream sites frequently go down, change URLs, or get taken down — health checking is essential for a good UX

Constraints

  • Tech stack: Go backend, vanilla JS frontend — no new frameworks or dependencies unless strictly necessary
  • Storage: File-based JSON — no database
  • Deployment: Docker container on Kubernetes, single replica
  • Reddit API: Public JSON endpoints, must respect rate limits (1 req/sec delay already in place)

Key Decisions

Decision Rationale Outcome
Server-side health checks over client-side only Client can't inspect response content (CORS); server proxy can detect video markers — Pending
5 consecutive failures before hiding Avoids flapping — streams that are temporarily down aren't immediately removed — Pending
Auto-publish scraped streams that pass health Reduces manual admin work; the health check is the quality gate — Pending
Health check every 5 minutes Balances freshness vs. load — streams don't change status that frequently — Pending

Last updated: 2026-02-17 after initialization