* Add GET /api/stats endpoint for total downloads badge
Public JSON endpoint that reads the existing package_stats singleton
row (same data source as the admin dashboard). Includes per-IP rate
limiting (10 req/min) and Cache-Control: public, max-age=300 so
CDN/shields.io caching reduces actual hits further.
Response shape:
{ total_installs, installs_30d, active_plugins, active_themes, total_packages }
https://claude.ai/code/session_019NzdEnnohsqdqyuw5VXUgy
* Use golang.org/x/time/rate for API rate limiting and add installs badge
Replace custom token-counting rate limiter with per-IP rate.Limiter
instances from golang.org/x/time/rate. Add total composer installs
badge to README.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Strip port from RemoteAddr before rate limiting
Use clientIP to normalize host:port to just the host, so varying
source ports from the same IP share a single rate limiter. Update
middleware test to use realistic host:port addresses.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Show exact install counts with per-type breakdown on admin dashboard
- Display exact numbers with commas instead of rounded "24K"
- Show plugin and theme install counts separately
- Show timestamp in CST instead of relative time
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
35 lines
1 KiB
Go
35 lines
1 KiB
Go
package http
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
"github.com/roots/wp-packages/internal/app"
|
|
)
|
|
|
|
type statsResponse struct {
|
|
TotalInstalls int64 `json:"total_installs"`
|
|
Installs30d int64 `json:"installs_30d"`
|
|
ActivePlugins int64 `json:"active_plugins"`
|
|
ActiveThemes int64 `json:"active_themes"`
|
|
TotalPackages int64 `json:"total_packages"`
|
|
}
|
|
|
|
func handleAPIStats(a *app.App) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var s statsResponse
|
|
err := a.DB.QueryRowContext(r.Context(),
|
|
`SELECT active_plugins, active_themes, active_plugins + active_themes,
|
|
plugin_installs + theme_installs, installs_30d
|
|
FROM package_stats WHERE id = 1`,
|
|
).Scan(&s.ActivePlugins, &s.ActiveThemes, &s.TotalPackages, &s.TotalInstalls, &s.Installs30d)
|
|
if err != nil {
|
|
http.Error(w, "stats unavailable", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("Cache-Control", "public, max-age=300")
|
|
_ = json.NewEncoder(w).Encode(s)
|
|
}
|
|
}
|