Cybersecurity-Projects/PROJECTS/beginner/canary-token-generator/backend/internal/admin/handler.go

196 lines
4.7 KiB
Go

// ©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"`
}