// ©AngelaMos | 2026 // handler.go package admin import ( "context" "database/sql" "net/http" "runtime" "github.com/go-chi/chi/v5" "github.com/redis/go-redis/v9" "github.com/CarterPerez-dev/cybersecurity-projects/canary-token-generator/backend/internal/core" ) type Handler struct { dbStats func() sql.DBStats redisStats func() *redis.PoolStats redisPing func(ctx context.Context) error dbPing func(ctx context.Context) error } type HandlerConfig struct { DBStats func() sql.DBStats RedisStats func() *redis.PoolStats RedisPing func(ctx context.Context) error DBPing func(ctx context.Context) error } func NewHandler(cfg HandlerConfig) *Handler { return &Handler{ dbStats: cfg.DBStats, redisStats: cfg.RedisStats, redisPing: cfg.RedisPing, dbPing: cfg.DBPing, } } func (h *Handler) RegisterRoutes(r chi.Router) { r.Route("/admin", func(r chi.Router) { r.Get("/stats", h.GetSystemStats) r.Get("/stats/db", h.GetDatabaseStats) r.Get("/stats/redis", h.GetRedisStats) r.Get("/stats/runtime", h.GetRuntimeStats) }) } func (h *Handler) GetSystemStats(w http.ResponseWriter, r *http.Request) { ctx := r.Context() dbHealthy := true if h.dbPing != nil { if err := h.dbPing(ctx); err != nil { dbHealthy = false } } redisHealthy := true if h.redisPing != nil { if err := h.redisPing(ctx); err != nil { redisHealthy = false } } var memStats runtime.MemStats runtime.ReadMemStats(&memStats) response := SystemStatsResponse{ Database: DatabaseStatus{ Healthy: dbHealthy, Stats: h.getDBStats(), }, Redis: RedisStatus{ Healthy: redisHealthy, Stats: h.getRedisStats(), }, Runtime: RuntimeStats{ GoVersion: runtime.Version(), NumGoroutine: runtime.NumGoroutine(), NumCPU: runtime.NumCPU(), MemAlloc: memStats.Alloc, MemSys: memStats.Sys, NumGC: memStats.NumGC, }, } core.OK(w, response) } func (h *Handler) GetDatabaseStats(w http.ResponseWriter, r *http.Request) { core.OK(w, h.getDBStats()) } func (h *Handler) GetRedisStats(w http.ResponseWriter, r *http.Request) { core.OK(w, h.getRedisStats()) } func (h *Handler) GetRuntimeStats(w http.ResponseWriter, r *http.Request) { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) response := RuntimeStats{ GoVersion: runtime.Version(), NumGoroutine: runtime.NumGoroutine(), NumCPU: runtime.NumCPU(), MemAlloc: memStats.Alloc, MemSys: memStats.Sys, NumGC: memStats.NumGC, } core.OK(w, response) } func (h *Handler) getDBStats() *DBPoolStats { if h.dbStats == nil { return nil } stats := h.dbStats() return &DBPoolStats{ MaxOpenConnections: stats.MaxOpenConnections, OpenConnections: stats.OpenConnections, InUse: stats.InUse, Idle: stats.Idle, WaitCount: stats.WaitCount, WaitDuration: stats.WaitDuration.String(), MaxIdleClosed: stats.MaxIdleClosed, MaxIdleTimeClosed: stats.MaxIdleTimeClosed, MaxLifetimeClosed: stats.MaxLifetimeClosed, } } func (h *Handler) getRedisStats() *RedisPoolStats { if h.redisStats == nil { return nil } stats := h.redisStats() return &RedisPoolStats{ Hits: stats.Hits, Misses: stats.Misses, Timeouts: stats.Timeouts, TotalConns: stats.TotalConns, IdleConns: stats.IdleConns, StaleConns: stats.StaleConns, } } type SystemStatsResponse struct { Database DatabaseStatus `json:"database"` Redis RedisStatus `json:"redis"` Runtime RuntimeStats `json:"runtime"` } type DatabaseStatus struct { Healthy bool `json:"healthy"` Stats *DBPoolStats `json:"stats,omitempty"` } type RedisStatus struct { Healthy bool `json:"healthy"` Stats *RedisPoolStats `json:"stats,omitempty"` } type DBPoolStats struct { MaxOpenConnections int `json:"max_open_connections"` OpenConnections int `json:"open_connections"` InUse int `json:"in_use"` Idle int `json:"idle"` WaitCount int64 `json:"wait_count"` WaitDuration string `json:"wait_duration"` MaxIdleClosed int64 `json:"max_idle_closed"` MaxIdleTimeClosed int64 `json:"max_idle_time_closed"` MaxLifetimeClosed int64 `json:"max_lifetime_closed"` } type RedisPoolStats struct { Hits uint32 `json:"hits"` Misses uint32 `json:"misses"` Timeouts uint32 `json:"timeouts"` TotalConns uint32 `json:"total_conns"` IdleConns uint32 `json:"idle_conns"` StaleConns uint32 `json:"stale_conns"` } type RuntimeStats struct { GoVersion string `json:"go_version"` NumGoroutine int `json:"num_goroutine"` NumCPU int `json:"num_cpu"` MemAlloc uint64 `json:"mem_alloc_bytes"` MemSys uint64 `json:"mem_sys_bytes"` NumGC uint32 `json:"num_gc"` }