package httpapi import ( "net/http" "strconv" "cloudipvalidator/internal/db" ) // --- validators --- func (s *Server) handleConfigListValidators(w http.ResponseWriter, r *http.Request) { validators, err := s.DB.ListValidators(r.Context()) if err != nil { writeDBError(w, err) return } out := make([]validatorDTO, len(validators)) for i, v := range validators { out[i] = validatorDTO{ ValidatorID: v.ValidatorID, Hostname: v.Hostname, OSPortID: v.OSPortID, State: v.State, LastHeartbeatAt: v.LastHeartbeatAt, } } writeJSON(w, http.StatusOK, out) } func (s *Server) handleConfigCreateValidator(w http.ResponseWriter, r *http.Request) { var req createValidatorRequest if err := readJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, "invalid body: "+err.Error()) return } if req.ValidatorID == "" { writeError(w, http.StatusBadRequest, "validator_id must not be empty") return } if err := s.DB.AdminCreateValidator(r.Context(), req.ValidatorID, req.OSPortID); err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusCreated, validatorDTO{ValidatorID: req.ValidatorID, OSPortID: req.OSPortID, State: "idle"}) } func (s *Server) handleConfigUpdateValidator(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") var req updateValidatorRequest if err := readJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, "invalid body: "+err.Error()) return } if err := s.DB.AdminUpdateValidatorPort(r.Context(), id, req.OSPortID); err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusOK, okResponse{OK: true}) } func (s *Server) handleConfigDeleteValidator(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") if err := s.DB.DeleteValidator(r.Context(), id); err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusOK, okResponse{OK: true}) } // --- sites --- func (s *Server) handleConfigListSites(w http.ResponseWriter, r *http.Request) { sites, err := s.DB.ListSites(r.Context()) if err != nil { writeDBError(w, err) return } out := make([]siteDTO, len(sites)) for i, site := range sites { out[i] = siteDTO{ Index: site.Index, SiteID: site.SiteID, Hostname: site.Hostname, State: site.State, LastHeartbeatAt: site.LastHeartbeatAt, } } writeJSON(w, http.StatusOK, out) } func (s *Server) handleConfigPutSite(w http.ResponseWriter, r *http.Request) { idx, err := strconv.Atoi(r.PathValue("index")) if err != nil { writeError(w, http.StatusBadRequest, "index must be an integer") return } var req putSiteRequest if err := readJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, "invalid body: "+err.Error()) return } if err := s.DB.UpsertSite(r.Context(), idx, req.SiteID); err != nil { writeDBError(w, err) return } // UpsertSite always resets hostname/state/last_heartbeat_at to their // "never connected" defaults on write (see its doc comment). writeJSON(w, http.StatusOK, siteDTO{Index: idx, SiteID: req.SiteID, State: db.SiteUnregistered}) } func (s *Server) handleConfigDeleteSite(w http.ResponseWriter, r *http.Request) { idx, err := strconv.Atoi(r.PathValue("index")) if err != nil { writeError(w, http.StatusBadRequest, "index must be an integer") return } if err := s.DB.DeleteSite(r.Context(), idx); err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusOK, okResponse{OK: true}) } // --- target groups --- func (s *Server) handleConfigListTargets(w http.ResponseWriter, r *http.Request) { groups, err := s.DB.ListTargetGroups(r.Context()) if err != nil { writeDBError(w, err) return } out := make([]targetGroupDTO, len(groups)) for i, g := range groups { out[i] = targetGroupDTO{Name: g.Name, Targets: g.Targets} } writeJSON(w, http.StatusOK, out) } func (s *Server) handleConfigPutTargetGroup(w http.ResponseWriter, r *http.Request) { name := r.PathValue("group") var req putTargetGroupRequest if err := readJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, "invalid body: "+err.Error()) return } if err := s.DB.UpsertTargetGroup(r.Context(), name, req.Targets); err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusOK, targetGroupDTO{Name: name, Targets: req.Targets}) } func (s *Server) handleConfigDeleteTargetGroup(w http.ResponseWriter, r *http.Request) { name := r.PathValue("group") if err := s.DB.DeleteTargetGroup(r.Context(), name); err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusOK, okResponse{OK: true}) } // --- check types --- func (s *Server) handleConfigListCheckTypes(w http.ResponseWriter, r *http.Request) { checkTypes, err := s.DB.ListCheckTypes(r.Context()) if err != nil { writeDBError(w, err) return } out := make([]checkTypeDTO, len(checkTypes)) for i, ct := range checkTypes { out[i] = checkTypeDTO{Name: ct.Name, Enabled: ct.Enabled, Targets: ct.TargetGroups} } writeJSON(w, http.StatusOK, out) } func (s *Server) handleConfigPutCheckType(w http.ResponseWriter, r *http.Request) { name := r.PathValue("name") var req putCheckTypeRequest if err := readJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, "invalid body: "+err.Error()) return } if err := s.DB.UpsertCheckType(r.Context(), name, req.Enabled, req.Targets); err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusOK, checkTypeDTO{Name: name, Enabled: req.Enabled, Targets: req.Targets}) } func (s *Server) handleConfigDeleteCheckType(w http.ResponseWriter, r *http.Request) { name := r.PathValue("name") if err := s.DB.DeleteCheckType(r.Context(), name); err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusOK, okResponse{OK: true}) } // --- orchestrator settings --- func (s *Server) handleConfigGetOrchestratorSettings(w http.ResponseWriter, r *http.Request) { settings, err := s.DB.GetSettings(r.Context()) if err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusOK, orchestratorSettingsDTO{ FIPSettleSeconds: settings.FIPSettleSeconds, HistoryRetentionCycles: settings.HistoryRetentionCycles, }) } // handleConfigPutOrchestratorSettings goes through the orchestrator (not a // direct DB call, unlike the read above) because setting fip_settle_seconds // needs cross-field validation against the static lease_ttl_seconds/ // self_check_timeout_seconds config, which only the orchestrator has. func (s *Server) handleConfigPutOrchestratorSettings(w http.ResponseWriter, r *http.Request) { var req orchestratorSettingsDTO if err := readJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, "invalid body: "+err.Error()) return } if err := s.Orch.SetFIPSettleSeconds(r.Context(), req.FIPSettleSeconds); err != nil { writeDBError(w, err) return } if err := s.DB.SetHistoryRetentionCycles(r.Context(), req.HistoryRetentionCycles); err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusOK, orchestratorSettingsDTO{ FIPSettleSeconds: req.FIPSettleSeconds, HistoryRetentionCycles: req.HistoryRetentionCycles, }) } // --- prober inbound checks --- func (s *Server) handleConfigGetInboundChecks(w http.ResponseWriter, r *http.Request) { settings, err := s.DB.GetInboundChecks(r.Context()) if err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusOK, inboundChecksDTO{Ports: settings.Ports, ICMP: settings.ICMP}) } // handleConfigPutInboundChecks writes directly to the DB (unlike the // orchestrator-settings PUT above) because port-range/duplicate validation // is purely local — no cross-field dependency on other orchestrator config. func (s *Server) handleConfigPutInboundChecks(w http.ResponseWriter, r *http.Request) { var req inboundChecksDTO if err := readJSON(r, &req); err != nil { writeError(w, http.StatusBadRequest, "invalid body: "+err.Error()) return } if err := s.DB.SetInboundChecks(r.Context(), req.Ports, req.ICMP); err != nil { writeDBError(w, err) return } writeJSON(w, http.StatusOK, inboundChecksDTO{Ports: req.Ports, ICMP: req.ICMP}) }