Add mass closure history page + API#107
Merged
Merged
Conversation
- /api/closures returns page/per_page/total/total_pages alongside events
- /api/closures/{vendor} returns 404 + JSON error for unknown vendor
- Reserve "feed" slug so a vendor named "feed" can't shadow /closures/feed
- Add /closures/feed to nonNegotiableExact
- Decode HTML entities in author names before slugifying
- Add test for window-reset → new event branch
- Drop unnecessary AUTOINCREMENT on closure_events.id
- Use .VendorSlug instead of (index .Events 0).VendorSlug in vendor template
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- GetClosurePluginStatuses returns *ClosurePluginStatus so missing keys are nil; templates can distinguish unknown plugins (zero structs are truthy in Go templates, which silently mislabeled them as Closed) - scanClosureEvent wraps detected_at parse errors with row id, matching the JSON unmarshal error handling - API 404 for unknown vendor now includes documentation_url for parity - RSS feed item descriptions list affected plugin slugs (up to 10 + "and N more"), so the feed is scannable in a reader Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add `id DESC` tie-breaker on `detected_at DESC` so events with identical timestamps render in a stable order - RSS feed now serves `application/rss+xml` (matches the layout's `<link rel="alternate">` declaration) - Wrap TrackMassClosures in a transaction so partial runs roll back cleanly; also tightens behavior under concurrent invocations Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…lback - RSS channel now declares an `<atom:link rel="self">` matching the declared `xmlns:atom`; item GUIDs marked `isPermaLink="false"` since they're fragment-style identifiers - TrackMassClosures dedupes plugin slugs before checking the threshold so duplicate closure rows for one plugin can't create count=1 events - Migration 030 down rollback now scopes by (vendor_slug, detected_at) so any real events recorded post-deploy aren't deleted on rollback - Add slugify tests for the reserved-slug "feed" guard - Check `rows.Err()` after iteration in the closure_events readers - Drop unused Total from the closures handler data; comment the intentional non-fatal handling of GetClosurePluginStatuses errors Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- handleClosuresFeed now caches the rendered XML (1h TTL, mirrors handleFeed) and sets Cache-Control: public, max-age=3600 - TrackMassClosures, when creating a new event, subtracts slugs already recorded in this vendor's most recent event. The rolling 24h candidate set can include source rows from a prior outbreak whose detected_at has aged out of cooldown but whose change rows are still in window — without this, slow-rolling vendors would get adjacent events sharing slugs. Regression test added. - Drop unused closure.Time field (residual from when window_start was per-event); loadRecentClosures no longer SELECTs created_at - Markdown vendor 404 returns a proper Markdown body instead of stdlib plain text, matching the JSON/HTML 404 surfaces Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- handleAPIClosures and handleAPIVendorClosures set Cache-Control: public, max-age=3600 to match the cron cadence (closures only change hourly), mirroring handleAPIClosedPackages - Update the docs caching footnote (HTML + Markdown) to mention the closures endpoints - loadRecentClosures sorts by scc.id so groupByVendor's first-seen display-name casing is deterministic if a vendor's plugins ever land with mixed author capitalizations Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- handleAPIVendorClosures only sets Cache-Control on success. Previously a 404 picked up max-age=3600 too, which could hide a vendor's first-ever mass closure behind a stale CDN-cached 404 for up to an hour after the cron recorded it. - renderClosuresRSS uses events[0].DetectedAt for lastBuildDate (with time.Now fallback when empty), so polling RSS readers stop seeing the channel "update" every hour during quiet periods. Mirrors the seo.go atom feed pattern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #106
Summary
/closureshistory page that lists every recent vendor mass-closure event on WordPress.org, with per-vendor detail pages at/closures/{vendor-slug}./api/closures,/api/closures/{vendor-slug}), RSS feed (/closures/feed), and.mdvariants for both pages. Replaces the old private-gist closure report.