Download nightly exports from the web app

Go · Go · intermediate · modification

Lets admins download the nightly CSV exports straight from the Exports page instead of asking support for them. The file name is validated against the same character set the export job uses (and capped at 100 characters) before it touches the filesystem, and every lookup is rooted in the caller's own org directory. `http.ServeContent` gives us range requests for free, and the response is marked `private, no-store` because the files hold payroll data. Tested locally with the September export, a missing file (404) and a `.part` file (400).

Go 1.22 service in Linux containers, standard library only (`net/http` method-pattern `ServeMux`, `log/slog`). `auth.FromContext(ctx)` returns the signed-in `auth.Member` (`OrgID int64`). The nightly export job writes `<ExportDir>/<orgID>/<name>.csv`, where org ids are sequential integers that appear in the app's URLs. Names are like `payroll-2026-09.csv`, and each file is written as `<name>.csv.part` and renamed into place when complete. The export volume holds only those org directories and regular files, with no symlinks, and is mounted read-only into this service. Exports contain employees' salaries and bank details. `GET /api/exports` (existing) lists the caller's org's file names; `writeJSON(w http.ResponseWriter, status int, v any)` (existing, same package) sets the JSON content type, writes the status and encodes `v`.

Requirements

Files touched

--- internal/exports/routes.go
 	h := &Handler{ExportDir: exportDir}
 	mux.HandleFunc("GET /api/exports", h.List)
+	mux.HandleFunc("GET /api/exports/download", h.Download)
 }
 
--- internal/exports/download.go
+package exports
+
+import (
+	"errors"
+	"io/fs"
+	"log/slog"
+	"mime"
+	"net/http"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strconv"
+
+	"example.com/payroll/internal/auth"
+)
+
+const maxNameLength = 100
+
+var exportName = regexp.MustCompile(`[A-Za-z0-9_-][A-Za-z0-9._-]*\.csv$`)
+
+func (h *Handler) Download(w http.ResponseWriter, r *http.Request) {
+	member, ok := auth.FromContext(r.Context())
+	if !ok {

Review this PR

Go practice