✨ feat(cache): add in-memory cache service (ADR-0022 Phase 1 part 2) #23
13
config.yaml
13
config.yaml
@@ -87,4 +87,15 @@ database:
|
|||||||
|
|
||||||
# Maximum lifetime of connections (default: "1h")
|
# Maximum lifetime of connections (default: "1h")
|
||||||
# Format: number + unit (s, m, h)
|
# Format: number + unit (s, m, h)
|
||||||
conn_max_lifetime: 1h
|
conn_max_lifetime: 1h
|
||||||
|
|
||||||
|
# Cache configuration (in-memory)
|
||||||
|
cache:
|
||||||
|
# Enable in-memory cache (default: true)
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
# Default TTL in seconds for cache items (default: 300 = 5 minutes)
|
||||||
|
default_ttl_seconds: 300
|
||||||
|
|
||||||
|
# Cleanup interval in seconds for expired items (default: 600 = 10 minutes)
|
||||||
|
cleanup_interval_seconds: 600
|
||||||
1
go.mod
1
go.mod
@@ -10,6 +10,7 @@ require (
|
|||||||
github.com/go-playground/validator/v10 v10.30.2
|
github.com/go-playground/validator/v10 v10.30.2
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||||
github.com/lib/pq v1.12.3
|
github.com/lib/pq v1.12.3
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||||
github.com/rs/zerolog v1.35.0
|
github.com/rs/zerolog v1.35.0
|
||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/spf13/viper v1.21.0
|
github.com/spf13/viper v1.21.0
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -118,6 +118,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||||
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
|||||||
56
pkg/cache/cache.go
vendored
Normal file
56
pkg/cache/cache.go
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
gocache "github.com/patrickmn/go-cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service defines the interface for cache operations
|
||||||
|
type Service interface {
|
||||||
|
Set(key string, value interface{}, ttl time.Duration)
|
||||||
|
Get(key string) (interface{}, bool)
|
||||||
|
Delete(key string)
|
||||||
|
Flush()
|
||||||
|
ItemCount() int
|
||||||
|
}
|
||||||
|
|
||||||
|
// InMemoryService implements Service using go-cache library
|
||||||
|
type InMemoryService struct {
|
||||||
|
cache *gocache.Cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewInMemoryService creates a new in-memory cache service
|
||||||
|
// defaultTTL: default time-to-live for cache items
|
||||||
|
// cleanupInterval: interval at which expired items are cleaned up
|
||||||
|
func NewInMemoryService(defaultTTL, cleanupInterval time.Duration) Service {
|
||||||
|
c := gocache.New(defaultTTL, cleanupInterval)
|
||||||
|
return &InMemoryService{cache: c}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set stores a value in the cache with the specified TTL
|
||||||
|
func (s *InMemoryService) Set(key string, value interface{}, ttl time.Duration) {
|
||||||
|
s.cache.Set(key, value, ttl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get retrieves a value from the cache
|
||||||
|
// Returns the value and true if found, nil and false if not found or expired
|
||||||
|
func (s *InMemoryService) Get(key string) (interface{}, bool) {
|
||||||
|
val, found := s.cache.Get(key)
|
||||||
|
return val, found
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete removes an item from the cache
|
||||||
|
func (s *InMemoryService) Delete(key string) {
|
||||||
|
s.cache.Delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush clears all items from the cache
|
||||||
|
func (s *InMemoryService) Flush() {
|
||||||
|
s.cache.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ItemCount returns the number of items currently in the cache
|
||||||
|
func (s *InMemoryService) ItemCount() int {
|
||||||
|
return s.cache.ItemCount()
|
||||||
|
}
|
||||||
135
pkg/cache/cache_test.go
vendored
Normal file
135
pkg/cache/cache_test.go
vendored
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInMemoryService_SetGet(t *testing.T) {
|
||||||
|
svc := NewInMemoryService(1*time.Hour, 1*time.Hour)
|
||||||
|
|
||||||
|
// Test Set and Get
|
||||||
|
svc.Set("key1", "value1", 1*time.Hour)
|
||||||
|
val, ok := svc.Get("key1")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Expected to find key1 in cache")
|
||||||
|
}
|
||||||
|
if val != "value1" {
|
||||||
|
t.Fatalf("Expected 'value1', got '%v'", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Get non-existent key
|
||||||
|
_, ok = svc.Get("nonexistent")
|
||||||
|
if ok {
|
||||||
|
t.Fatal("Expected not to find nonexistent key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInMemoryService_Delete(t *testing.T) {
|
||||||
|
svc := NewInMemoryService(1*time.Hour, 1*time.Hour)
|
||||||
|
|
||||||
|
svc.Set("key1", "value1", 1*time.Hour)
|
||||||
|
_, ok := svc.Get("key1")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Expected to find key1 before delete")
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.Delete("key1")
|
||||||
|
_, ok = svc.Get("key1")
|
||||||
|
if ok {
|
||||||
|
t.Fatal("Expected not to find key1 after delete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInMemoryService_Flush(t *testing.T) {
|
||||||
|
svc := NewInMemoryService(1*time.Hour, 1*time.Hour)
|
||||||
|
|
||||||
|
svc.Set("key1", "value1", 1*time.Hour)
|
||||||
|
svc.Set("key2", "value2", 1*time.Hour)
|
||||||
|
|
||||||
|
if svc.ItemCount() != 2 {
|
||||||
|
t.Fatalf("Expected 2 items, got %d", svc.ItemCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.Flush()
|
||||||
|
|
||||||
|
if svc.ItemCount() != 0 {
|
||||||
|
t.Fatalf("Expected 0 items after flush, got %d", svc.ItemCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := svc.Get("key1")
|
||||||
|
if ok {
|
||||||
|
t.Fatal("Expected key1 to be flushed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInMemoryService_ItemCount(t *testing.T) {
|
||||||
|
svc := NewInMemoryService(1*time.Hour, 1*time.Hour)
|
||||||
|
|
||||||
|
if svc.ItemCount() != 0 {
|
||||||
|
t.Fatalf("Expected 0 items initially, got %d", svc.ItemCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.Set("key1", "value1", 1*time.Hour)
|
||||||
|
if svc.ItemCount() != 1 {
|
||||||
|
t.Fatalf("Expected 1 item, got %d", svc.ItemCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.Set("key2", "value2", 1*time.Hour)
|
||||||
|
if svc.ItemCount() != 2 {
|
||||||
|
t.Fatalf("Expected 2 items, got %d", svc.ItemCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
svc.Delete("key1")
|
||||||
|
if svc.ItemCount() != 1 {
|
||||||
|
t.Fatalf("Expected 1 item after delete, got %d", svc.ItemCount())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInMemoryService_TTLExpiration(t *testing.T) {
|
||||||
|
// Use a very short TTL for testing
|
||||||
|
svc := NewInMemoryService(100*time.Millisecond, 50*time.Millisecond)
|
||||||
|
|
||||||
|
svc.Set("key1", "value1", 50*time.Millisecond)
|
||||||
|
|
||||||
|
// Should be present immediately
|
||||||
|
val, ok := svc.Get("key1")
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Expected to find key1 immediately after set")
|
||||||
|
}
|
||||||
|
if val != "value1" {
|
||||||
|
t.Fatalf("Expected 'value1', got '%v'", val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for expiration
|
||||||
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
|
// Should be expired now
|
||||||
|
_, ok = svc.Get("key1")
|
||||||
|
if ok {
|
||||||
|
t.Fatal("Expected key1 to be expired after TTL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInMemoryService_DifferentTypes(t *testing.T) {
|
||||||
|
svc := NewInMemoryService(1*time.Hour, 1*time.Hour)
|
||||||
|
|
||||||
|
// Test with different types
|
||||||
|
svc.Set("string", "hello", 1*time.Hour)
|
||||||
|
svc.Set("int", 42, 1*time.Hour)
|
||||||
|
svc.Set("slice", []string{"a", "b"}, 1*time.Hour)
|
||||||
|
|
||||||
|
if svc.ItemCount() != 3 {
|
||||||
|
t.Fatalf("Expected 3 items, got %d", svc.ItemCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok := svc.Get("string")
|
||||||
|
if !ok || val != "hello" {
|
||||||
|
t.Fatal("String value mismatch")
|
||||||
|
}
|
||||||
|
|
||||||
|
val, ok = svc.Get("int")
|
||||||
|
if !ok || val != 42 {
|
||||||
|
t.Fatal("Int value mismatch")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ type Config struct {
|
|||||||
Auth AuthConfig `mapstructure:"auth"`
|
Auth AuthConfig `mapstructure:"auth"`
|
||||||
Database DatabaseConfig `mapstructure:"database"`
|
Database DatabaseConfig `mapstructure:"database"`
|
||||||
RateLimit RateLimitConfig `mapstructure:"rate_limit"`
|
RateLimit RateLimitConfig `mapstructure:"rate_limit"`
|
||||||
|
Cache CacheConfig `mapstructure:"cache"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerConfig holds server-related configuration
|
// ServerConfig holds server-related configuration
|
||||||
@@ -105,6 +106,13 @@ type RateLimitConfig struct {
|
|||||||
BurstSize int `mapstructure:"burst_size"`
|
BurstSize int `mapstructure:"burst_size"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CacheConfig holds cache configuration
|
||||||
|
type CacheConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
DefaultTTLSeconds int `mapstructure:"default_ttl_seconds"`
|
||||||
|
CleanupIntervalSeconds int `mapstructure:"cleanup_interval_seconds"`
|
||||||
|
}
|
||||||
|
|
||||||
// VersionInfo holds application version information
|
// VersionInfo holds application version information
|
||||||
type VersionInfo struct {
|
type VersionInfo struct {
|
||||||
Version string `mapstructure:"-"` // Set via ldflags
|
Version string `mapstructure:"-"` // Set via ldflags
|
||||||
@@ -202,6 +210,11 @@ func LoadConfig() (*Config, error) {
|
|||||||
v.SetDefault("rate_limit.requests_per_minute", 60)
|
v.SetDefault("rate_limit.requests_per_minute", 60)
|
||||||
v.SetDefault("rate_limit.burst_size", 10)
|
v.SetDefault("rate_limit.burst_size", 10)
|
||||||
|
|
||||||
|
// Cache defaults
|
||||||
|
v.SetDefault("cache.enabled", true)
|
||||||
|
v.SetDefault("cache.default_ttl_seconds", 300)
|
||||||
|
v.SetDefault("cache.cleanup_interval_seconds", 600)
|
||||||
|
|
||||||
// Auth defaults
|
// Auth defaults
|
||||||
v.SetDefault("auth.jwt_secret", "default-secret-key-please-change-in-production")
|
v.SetDefault("auth.jwt_secret", "default-secret-key-please-change-in-production")
|
||||||
v.SetDefault("auth.admin_master_password", "admin123")
|
v.SetDefault("auth.admin_master_password", "admin123")
|
||||||
@@ -266,6 +279,11 @@ func LoadConfig() (*Config, error) {
|
|||||||
v.BindEnv("rate_limit.requests_per_minute", "DLC_RATE_LIMIT_REQUESTS_PER_MINUTE")
|
v.BindEnv("rate_limit.requests_per_minute", "DLC_RATE_LIMIT_REQUESTS_PER_MINUTE")
|
||||||
v.BindEnv("rate_limit.burst_size", "DLC_RATE_LIMIT_BURST_SIZE")
|
v.BindEnv("rate_limit.burst_size", "DLC_RATE_LIMIT_BURST_SIZE")
|
||||||
|
|
||||||
|
// Cache environment variables
|
||||||
|
v.BindEnv("cache.enabled", "DLC_CACHE_ENABLED")
|
||||||
|
v.BindEnv("cache.default_ttl_seconds", "DLC_CACHE_DEFAULT_TTL_SECONDS")
|
||||||
|
v.BindEnv("cache.cleanup_interval_seconds", "DLC_CACHE_CLEANUP_INTERVAL_SECONDS")
|
||||||
|
|
||||||
// Database environment variables
|
// Database environment variables
|
||||||
v.BindEnv("database.host", "DLC_DATABASE_HOST")
|
v.BindEnv("database.host", "DLC_DATABASE_HOST")
|
||||||
v.BindEnv("database.port", "DLC_DATABASE_PORT")
|
v.BindEnv("database.port", "DLC_DATABASE_PORT")
|
||||||
@@ -428,6 +446,27 @@ func (c *Config) GetRateLimitBurstSize() int {
|
|||||||
return c.RateLimit.BurstSize
|
return c.RateLimit.BurstSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCacheEnabled returns whether cache is enabled
|
||||||
|
func (c *Config) GetCacheEnabled() bool {
|
||||||
|
return c.Cache.Enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCacheDefaultTTLSeconds returns the default TTL in seconds for cache items
|
||||||
|
func (c *Config) GetCacheDefaultTTLSeconds() int {
|
||||||
|
if c.Cache.DefaultTTLSeconds <= 0 {
|
||||||
|
return 300
|
||||||
|
}
|
||||||
|
return c.Cache.DefaultTTLSeconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCacheCleanupIntervalSeconds returns the cleanup interval in seconds for cache
|
||||||
|
func (c *Config) GetCacheCleanupIntervalSeconds() int {
|
||||||
|
if c.Cache.CleanupIntervalSeconds <= 0 {
|
||||||
|
return 600
|
||||||
|
}
|
||||||
|
return c.Cache.CleanupIntervalSeconds
|
||||||
|
}
|
||||||
|
|
||||||
// GetDatabaseHost returns the database host
|
// GetDatabaseHost returns the database host
|
||||||
func (c *Config) GetDatabaseHost() string {
|
func (c *Config) GetDatabaseHost() string {
|
||||||
if c.Database.Host == "" {
|
if c.Database.Host == "" {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
httpSwagger "github.com/swaggo/http-swagger"
|
httpSwagger "github.com/swaggo/http-swagger"
|
||||||
|
|
||||||
|
"dance-lessons-coach/pkg/cache"
|
||||||
"dance-lessons-coach/pkg/config"
|
"dance-lessons-coach/pkg/config"
|
||||||
"dance-lessons-coach/pkg/greet"
|
"dance-lessons-coach/pkg/greet"
|
||||||
"dance-lessons-coach/pkg/middleware"
|
"dance-lessons-coach/pkg/middleware"
|
||||||
@@ -65,6 +66,7 @@ type Server struct {
|
|||||||
validator *validation.Validator
|
validator *validation.Validator
|
||||||
userRepo user.UserRepository
|
userRepo user.UserRepository
|
||||||
userService user.UserService
|
userService user.UserService
|
||||||
|
cacheService cache.Service
|
||||||
startedAt time.Time
|
startedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,15 +85,28 @@ func NewServer(cfg *config.Config, readyCtx context.Context) *Server {
|
|||||||
log.Warn().Err(err).Msg("Failed to initialize user services, user functionality will be disabled")
|
log.Warn().Err(err).Msg("Failed to initialize user services, user functionality will be disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize cache service
|
||||||
|
var cacheService cache.Service
|
||||||
|
if cfg.GetCacheEnabled() {
|
||||||
|
cacheService = cache.NewInMemoryService(
|
||||||
|
time.Duration(cfg.GetCacheDefaultTTLSeconds())*time.Second,
|
||||||
|
time.Duration(cfg.GetCacheCleanupIntervalSeconds())*time.Second,
|
||||||
|
)
|
||||||
|
log.Trace().Msg("Cache service initialized")
|
||||||
|
} else {
|
||||||
|
log.Trace().Msg("Cache service disabled")
|
||||||
|
}
|
||||||
|
|
||||||
s := &Server{
|
s := &Server{
|
||||||
router: chi.NewRouter(),
|
router: chi.NewRouter(),
|
||||||
readyCtx: readyCtx,
|
readyCtx: readyCtx,
|
||||||
withOTEL: cfg.GetTelemetryEnabled(),
|
withOTEL: cfg.GetTelemetryEnabled(),
|
||||||
config: cfg,
|
config: cfg,
|
||||||
validator: validator,
|
validator: validator,
|
||||||
userRepo: userRepo,
|
userRepo: userRepo,
|
||||||
userService: userService,
|
userService: userService,
|
||||||
startedAt: time.Now(),
|
cacheService: cacheService,
|
||||||
|
startedAt: time.Now(),
|
||||||
}
|
}
|
||||||
s.setupRoutes()
|
s.setupRoutes()
|
||||||
return s
|
return s
|
||||||
@@ -351,26 +366,49 @@ func (s *Server) handleVersion(w http.ResponseWriter, r *http.Request) {
|
|||||||
format = "plain" // default format
|
format = "plain" // default format
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check cache if enabled
|
||||||
|
cacheKey := "version:" + format
|
||||||
|
if s.cacheService != nil {
|
||||||
|
if cached, ok := s.cacheService.Get(cacheKey); ok {
|
||||||
|
log.Trace().Str("cache_key", cacheKey).Msg("Cache hit for version")
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
if format == "json" {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
}
|
||||||
|
w.Write([]byte(cached.(string)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build response
|
||||||
|
var response string
|
||||||
switch format {
|
switch format {
|
||||||
case "plain":
|
case "plain":
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
w.Write([]byte(version.Short()))
|
response = version.Short()
|
||||||
case "full":
|
case "full":
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
w.Write([]byte(version.Full()))
|
response = version.Full()
|
||||||
case "json":
|
case "json":
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
jsonResponse := fmt.Sprintf(`{
|
response = fmt.Sprintf(`{
|
||||||
"version": "%s",
|
"version": "%s",
|
||||||
"commit": "%s",
|
"commit": "%s",
|
||||||
"built": "%s",
|
"built": "%s",
|
||||||
"go": "%s"
|
"go": "%s"
|
||||||
}`, version.Version, version.Commit, version.Date, version.GoVersion)
|
}`, version.Version, version.Commit, version.Date, version.GoVersion)
|
||||||
w.Write([]byte(jsonResponse))
|
|
||||||
default:
|
default:
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
w.Write([]byte(version.Short()))
|
response = version.Short()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache the response for 60 seconds if cache is enabled
|
||||||
|
if s.cacheService != nil {
|
||||||
|
s.cacheService.Set(cacheKey, response, 60*time.Second)
|
||||||
|
log.Trace().Str("cache_key", cacheKey).Msg("Cached version response")
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Write([]byte(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
// HealthzResponse represents the Kubernetes-style health check response
|
// HealthzResponse represents the Kubernetes-style health check response
|
||||||
|
|||||||
Reference in New Issue
Block a user