package handlers import ( "context" "errors" "log/slog" "github.com/gin-gonic/gin" "git.netcell-it.de/projekte/edgeguard-native/internal/handlers/response" "git.netcell-it.de/projekte/edgeguard-native/internal/models" "git.netcell-it.de/projekte/edgeguard-native/internal/services/audit" ntpsvc "git.netcell-it.de/projekte/edgeguard-native/internal/services/ntp" ) type NTPHandler struct { Repo *ntpsvc.Repo Audit *audit.Repo NodeID string Reloader func(ctx context.Context) error } func NewNTPHandler(repo *ntpsvc.Repo, a *audit.Repo, nodeID string, reloader func(context.Context) error) *NTPHandler { return &NTPHandler{Repo: repo, Audit: a, NodeID: nodeID, Reloader: reloader} } func (h *NTPHandler) reload(ctx context.Context, op string) { if h.Reloader == nil { return } if err := h.Reloader(ctx); err != nil { slog.Warn("chrony: reload after mutation failed", "op", op, "error", err) } } func (h *NTPHandler) Register(rg *gin.RouterGroup) { g := rg.Group("/ntp") g.GET("/settings", h.GetSettings) g.PUT("/settings", h.UpdateSettings) p := g.Group("/pools") p.GET("", h.ListPools) p.POST("", h.CreatePool) p.GET("/:id", h.GetPool) p.PUT("/:id", h.UpdatePool) p.DELETE("/:id", h.DeletePool) } func (h *NTPHandler) GetSettings(c *gin.Context) { s, err := h.Repo.GetSettings(c.Request.Context()) if err != nil { response.Internal(c, err) return } response.OK(c, s) } func (h *NTPHandler) UpdateSettings(c *gin.Context) { var req models.NTPSettings if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err) return } out, err := h.Repo.UpdateSettings(c.Request.Context(), req) if err != nil { response.Internal(c, err) return } _ = h.Audit.Log(c.Request.Context(), actorOf(c), "ntp.settings.update", "settings", out, h.NodeID) response.OK(c, out) h.reload(c.Request.Context(), "settings.update") } func (h *NTPHandler) ListPools(c *gin.Context) { out, err := h.Repo.ListPools(c.Request.Context()) if err != nil { response.Internal(c, err) return } response.OK(c, gin.H{"pools": out}) } func (h *NTPHandler) GetPool(c *gin.Context) { id, ok := parseID(c) if !ok { return } p, err := h.Repo.GetPool(c.Request.Context(), id) if err != nil { if errors.Is(err, ntpsvc.ErrPoolNotFound) { response.NotFound(c, err) return } response.Internal(c, err) return } response.OK(c, p) } func (h *NTPHandler) CreatePool(c *gin.Context) { var req models.NTPPool if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err) return } if err := validateNTPPool(&req); err != nil { response.BadRequest(c, err) return } out, err := h.Repo.CreatePool(c.Request.Context(), req) if err != nil { response.Internal(c, err) return } _ = h.Audit.Log(c.Request.Context(), actorOf(c), "ntp.pool.create", out.Address, out, h.NodeID) response.Created(c, out) h.reload(c.Request.Context(), "pool.create") } func (h *NTPHandler) UpdatePool(c *gin.Context) { id, ok := parseID(c) if !ok { return } var req models.NTPPool if err := c.ShouldBindJSON(&req); err != nil { response.BadRequest(c, err) return } if err := validateNTPPool(&req); err != nil { response.BadRequest(c, err) return } out, err := h.Repo.UpdatePool(c.Request.Context(), id, req) if err != nil { if errors.Is(err, ntpsvc.ErrPoolNotFound) { response.NotFound(c, err) return } response.Internal(c, err) return } _ = h.Audit.Log(c.Request.Context(), actorOf(c), "ntp.pool.update", out.Address, out, h.NodeID) response.OK(c, out) h.reload(c.Request.Context(), "pool.update") } func (h *NTPHandler) DeletePool(c *gin.Context) { id, ok := parseID(c) if !ok { return } if err := h.Repo.DeletePool(c.Request.Context(), id); err != nil { if errors.Is(err, ntpsvc.ErrPoolNotFound) { response.NotFound(c, err) return } response.Internal(c, err) return } response.NoContent(c) h.reload(c.Request.Context(), "pool.delete") } func validateNTPPool(p *models.NTPPool) error { if p.Address == "" { return errors.New("address required") } switch p.Kind { case "pool", "server": default: return errors.New("kind must be 'pool' or 'server'") } return nil }