Commit graph

19 commits

Author SHA1 Message Date
Shekhar Vaidya
1c54c36d03 feat: expiry countdown, refresh links, CLI tabs, recently viewed, file size support
- Backend: ?force=true on /proxy bypasses cache for fresh link fetch
- Frontend: parse `se` param for live expiry countdown on download links
- Frontend: show Refresh button when link expires in <6h, force-fetches fresh URL
- Frontend: CliCommand component replaces Aria2Tip — wget/curl/aria2 tabs, persists selection in localStorage
- Frontend: RecentlyViewed component — localStorage, shows active countdown or expired state
- Frontend: FileSize shown alongside arch if returned by Microsoft CDN

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-22 21:06:17 +05:30
Shekhar Vaidya
90c498233b fix: route background stale refresh through singleflight; cleanup logging
- Background refresh on stale serve now goes through sfGroup.Do so
  concurrent stale hits collapse into one Microsoft call instead of
  hammering the API from multiple goroutines simultaneously.

- cleanupCaches: only logs when entries are actually evicted (was
  logging every 30min regardless of whether anything changed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:12:59 +05:30
Shekhar Vaidya
a0a6341d5f feat: add /metrics endpoint, fix cache eviction and review issues
- /metrics: atomic counters for requests, cache hits, MS fetches, neg
  hits, stale serves, and cache sizes per endpoint. Auth via
  METRICS_SECRET env var (query param or Authorization: Bearer).

- Cache eviction: cleanupCaches() goroutine runs every 30min, removes
  expired SKU/neg entries and link entries past ExpiresAt + 4h stale
  window. Prevents unbounded map growth under sustained traffic.

- rand: replace global rand with package-level rand.Rand to avoid
  mutex contention at high QPS (jitter() uses local source).

- Singleflight keys: separate sfKey from negKey in both handlers for
  clarity — "sku:<id>" and "link:<id>:<sku>" vs neg cache keys.

- Background refresh on stale: when /proxy serves a stale entry after
  a failed refresh, fires a goroutine to retry and update the cache
  so the next request gets fresh data.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 23:08:43 +05:30
Shekhar Vaidya
2f14ca6f22 fix: bump Dockerfile to golang:1.25-alpine to match go.mod requirement 2026-05-18 22:51:25 +05:30
Shekhar Vaidya
c6d8615cf1 feat: two-layer in-memory caching for backend (closes #12)
Reduces Microsoft API traffic from thousands/day to ~50-100/day,
eliminating the 715-123130 rate-limit block under real traffic.

1. Singleflight (golang.org/x/sync/singleflight) — wraps all Microsoft
   fetches so 500 concurrent cache misses collapse into 1 API hit.

2. SKU info cache — 7-day TTL keyed by product_id. Language lists are
   stable; no need to hit Microsoft on every product page load.

3. Download link cache — dynamic TTL keyed by product_id:sku_id. Expiry
   is derived from the signed URL's `se` query param minus 30min buffer,
   so we never serve an expired signed URL.

4. Negative response caching — 60s TTL for 429 / 715-123130 failures.
   Prevents thundering herd from retries worsening an existing block.
   Only stored when no stale data is available to fall back on.

5. Dynamic TTL — parses `se` (signed expiry) from Microsoft CDN URL:
   TTL = (se - now) - 30min. Falls back to 22h if unparseable.

6. Stale-on-failure — if a refresh fails (rate-limited or transient),
   the expired cache entry is served temporarily. Applies to both
   /proxy (download links) and /evallinks (eval ISOs).

7. Jitter — ±5min random offset on all TTLs prevents synchronised
   mass-expiry spikes when many entries are warmed at the same time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:41:58 +05:30
Shekhar Vaidya
a78059187f feat: add Enterprise & Server eval ISOs via Microsoft evalcenter
Adds /eval listing page and /product/<slug> detail pages for Windows
Server 2025/2022/2019/2016 and Windows 11 Enterprise evaluation ISOs,
sourced from Microsoft evalcenter without any session flow.

Backend: new /evallinks endpoint fetches evalcenter page, extracts
fwlinks, follows all redirects in parallel, detects arch + locale.
24h cache per product with startup warming so first hit is instant.

Frontend: EvalPage listing at /eval, EvalDetailPage at /product/<slug>
matching consumer structure with downloads, aria2 template, system
requirements, and also-available section. Smart ProductRouter in
App.tsx dispatches slug vs numeric productId. Dock highlights Products
tab for /product/* and /eval routes. CTA banner on home page and
footer link added. sitemap.xml updated with 6 new URLs.

Closes #13

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-05-18 21:56:57 +05:30
Shekhar Vaidya
106e2e2b31 fix(backend): allow non-numeric SKU IDs for legacy Windows products
Replaced strconv.Atoi on sku_id with a permissive allowlist regex
(alphanumeric, hyphens, underscores). Older products like Windows 8.1
(#48, #52) return non-integer SKU IDs from Microsoft API, causing the
Get Download Links button to fail with 'sku_id must be a numeric value'.

Closes #9
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:45:27 +05:30
Shekhar Vaidya
8b036e94ba feat: route Microsoft API calls through Cloudflare Worker to avoid IP blocks
- Add cloudflare-worker/worker.js: edge proxy forwarding requests to allowed
  Microsoft hosts from Cloudflare distributed IPs. Set CF_WORKER_URL env var
  to activate; omit to keep direct-to-Microsoft behaviour (self-hosters unaffected).
- Wrap all outbound Microsoft URLs in proxyURL() in main.go so the Worker
  sits transparently in front of every session and download-link request.
- Fix Back button navigating to external referrer when user lands directly
  on a product URL (#5) — use window.history.state?.idx to detect whether
  there is prior in-app history before calling navigate(-1).

Closes #5, Closes #6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 12:01:03 +05:30
Shekhar Vaidya
37f947cb32 refactor(backend): validate numeric IDs and use url.Values for upstream URLs
- Reject non-numeric product_id and sku_id with HTTP 400 before any
  upstream request is made, preventing parameter injection into
  Microsoft API query strings.
- Replace all fmt.Sprintf URL construction with url.Values.Set +
  .Encode() across handleSkuInfo, handleProxy, and setupSession,
  ensuring values are always percent-encoded correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-12 19:00:09 +05:30
Shekhar Vaidya
2d688ae7e8 Implement inactive product state, restore backend tracker limits, and add dedicated catalog error UI panel 2026-04-11 13:01:56 +05:30
Shekhar Vaidya
aac8f5d990 Modernize UI, unify product catalog, and backend optimization
- Migrated products.json to structured metadata format.

- Refactored ProductDetailPage to eagerly reset state and validate product IDs against local catalog.

- Dynamic data fetching in HomePage and StatsBar for total release counts.

- Adjusted UI layout in App.tsx and UX states (404/Loading) for missing dependencies.

- Shortened backend tracking HTTP timeouts to prevent local Pi-Hole hangs.
2026-04-11 11:17:54 +05:30
Shekhar Vaidya
2b89aa3647 debug: add detailed MS API error logging 2026-04-10 22:03:02 +05:30
Shekhar Vaidya
f6cab53b77 fix: remove public folder copy from Dockerfile 2026-04-10 20:56:08 +05:30
Shekhar Vaidya
3b18b08c0a security: restrict CORS and remove test UI from root 2026-04-10 20:52:56 +05:30
Shekhar Vaidya
722d211197 chore: remove test UI and static file server for production 2026-04-10 20:50:05 +05:30
Shekhar Vaidya
74c06b0a8e fix: downgrade go version to 1.22 and ensure go.sum is tracked 2026-04-10 20:40:01 +05:30
Shekhar Vaidya
acd9f94e5f fix: remove go.sum from ignore list 2026-04-10 20:33:36 +05:30
Shekhar Vaidya
56a43b2ff0 feat: v3 release with Coolify support and UI polish 2026-04-10 20:23:59 +05:30
Shekhar Vaidya
7d847f8dd1 feat: initial release — Windows ISO Downloader v3 2026-04-10 20:15:46 +05:30