veza/veza-backend-api/internal/middleware/cache_headers.go
senke a1000ce7fb style(backend): gofmt -w on 85 files (whitespace only)
backend-ci.yml's `test -z "$(gofmt -l .)"` strict gate (added in
13c21ac11) failed on a backlog of unformatted files. None of the
85 files in this commit had been edited since the gate was added
because no push touched veza-backend-api/** in between, so the
gate never fired until today's CI fixes triggered it.

The diff is exclusively whitespace alignment in struct literals
and trailing-space comments. `go build ./...` and the full test
suite (with VEZA_SKIP_INTEGRATION=1 -short) pass identically.
2026-04-14 12:22:14 +02:00

82 lines
2.4 KiB
Go

package middleware
import (
"strconv"
"strings"
"github.com/gin-gonic/gin"
)
// CacheHeadersConfig defines cache-control rules per path prefix
type CacheHeadersConfig struct {
Rules []CacheRule
}
// CacheRule maps a URL path prefix to cache headers
type CacheRule struct {
PathPrefix string
MaxAge int // seconds
Directive string // e.g. "public", "private", "no-cache"
Immutable bool
}
// DefaultCacheHeadersConfig returns production-ready cache header rules
// Reference: ORIGIN_PERFORMANCE_TARGETS.md §8.4
func DefaultCacheHeadersConfig() CacheHeadersConfig {
return CacheHeadersConfig{
Rules: []CacheRule{
// Static assets (JS, CSS, fonts) — immutable with content hash
{PathPrefix: "/static/", MaxAge: 31536000, Directive: "public", Immutable: true},
{PathPrefix: "/assets/", MaxAge: 31536000, Directive: "public", Immutable: true},
// Audio files — CDN cached for 7 days
{PathPrefix: "/audio/", MaxAge: 604800, Directive: "public"},
// HLS segments — short cache (live content changes)
{PathPrefix: "/hls/", MaxAge: 60, Directive: "public"},
// Images (covers, avatars) — CDN cached for 30 days
{PathPrefix: "/images/", MaxAge: 2592000, Directive: "public"},
{PathPrefix: "/uploads/", MaxAge: 86400, Directive: "public"},
// API responses — no caching by default (handled by ResponseCache middleware)
{PathPrefix: "/api/", MaxAge: 0, Directive: "no-cache"},
},
}
}
// CacheHeaders returns a middleware that sets appropriate Cache-Control headers
// based on the request path. This enables CDN and browser caching.
func CacheHeaders(cfg CacheHeadersConfig) gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
for _, rule := range cfg.Rules {
if strings.HasPrefix(path, rule.PathPrefix) {
setCacheHeaders(c, rule)
break
}
}
c.Next()
}
}
func setCacheHeaders(c *gin.Context, rule CacheRule) {
if rule.MaxAge == 0 && rule.Directive == "no-cache" {
c.Header("Cache-Control", "no-cache, no-store, must-revalidate")
c.Header("Pragma", "no-cache")
return
}
var parts []string
if rule.Directive != "" {
parts = append(parts, rule.Directive)
}
if rule.MaxAge > 0 {
parts = append(parts, "max-age="+strconv.Itoa(rule.MaxAge))
}
if rule.Immutable {
parts = append(parts, "immutable")
}
if len(parts) > 0 {
c.Header("Cache-Control", strings.Join(parts, ", "))
}
}