* Update all import paths * Rename directory cmd/wpcomposer/ → cmd/wppackages/ * Rename import alias wpcomposergo → wppackagesgo in main.go and migrate_test.go * Makefile — binary name wpcomposer → wppackages * Update Air path * Global replace repo.wp-composer.com → repo.wp-packages.org * Global replace cdn.wp-composer.com → cdn.wp-packages.org * Global replace wp-composer.com → wp-packages.org (remaining) * Composer repo key in templates/docs: repositories.wp-composer → repositories.wp-packages * Rename columns on the existing schema * Update all Go code referencing these column names * Routes & SEO * Templates & front-end * Admin UI * Documentation * CI/CD * Config defaults * Rename role directory * Rename all systemd template files inside the role * Update contents of all .j2 templates — service names, binary paths, descriptions * Update tasks/main.yml and handlers/main.yml in the role * Update deploy/ansible/roles/app/tasks/main.yml and deploy.yml * Update deploy/ansible/group_vars/production/main.yml * Additional renames/fixes * Additional renames/fixes * Additional renames/fixes * not needed
75 lines
1.9 KiB
Go
75 lines
1.9 KiB
Go
package http
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net"
|
|
"net/http"
|
|
|
|
"github.com/roots/wp-packages/internal/app"
|
|
"github.com/roots/wp-packages/internal/telemetry"
|
|
)
|
|
|
|
type notifyBatchRequest struct {
|
|
Downloads []downloadEntry `json:"downloads"`
|
|
}
|
|
|
|
type downloadEntry struct {
|
|
Name string `json:"name"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
const maxDownloadsRequestBodyBytes = 1 << 20 // 1 MiB
|
|
|
|
func handleDownloads(a *app.App) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var req notifyBatchRequest
|
|
r.Body = http.MaxBytesReader(w, r.Body, maxDownloadsRequestBodyBytes)
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
|
return
|
|
}
|
|
|
|
// Cap batch size
|
|
downloads := req.Downloads
|
|
if len(downloads) > 100 {
|
|
downloads = downloads[:100]
|
|
}
|
|
|
|
ip := clientIP(r)
|
|
ipHash := telemetry.HashIP(ip)
|
|
uaHash := telemetry.HashUserAgent(r.Header.Get("User-Agent"))
|
|
dedupeWindow := a.Config.Telemetry.DedupeWindowSeconds
|
|
|
|
for _, dl := range downloads {
|
|
if dl.Name == "" || dl.Version == "" {
|
|
continue
|
|
}
|
|
|
|
pkgID, err := telemetry.LookupPackageID(r.Context(), a.DB, dl.Name)
|
|
if err != nil || pkgID == 0 {
|
|
continue
|
|
}
|
|
|
|
_, _ = telemetry.RecordInstall(r.Context(), a.DB, telemetry.InstallParams{
|
|
PackageID: pkgID,
|
|
Version: dl.Version,
|
|
IPHash: ipHash,
|
|
UserAgentHash: uaHash,
|
|
}, dedupeWindow)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
|
}
|
|
}
|
|
|
|
// clientIP extracts the IP address from RemoteAddr, stripping the port.
|
|
func clientIP(r *http.Request) string {
|
|
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
if err != nil {
|
|
return r.RemoteAddr // fallback if no port
|
|
}
|
|
return host
|
|
}
|