All checks were successful
check / check (push) Successful in 46s
Implement the two API endpoints documented in the README that were previously returning 404: - GET /api/v1/domains: Returns configured domains with their discovered nameservers, last check time, and status (ok/pending). - GET /api/v1/hostnames: Returns configured hostnames with per-nameserver DNS records, status, and last check time. Both endpoints read from the existing state store and config, requiring no new dependencies. Nameserver results in the hostnames endpoint are sorted for deterministic output. Closes #67
121 lines
2.7 KiB
Go
121 lines
2.7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"sort"
|
|
"time"
|
|
|
|
"sneak.berlin/go/dnswatcher/internal/state"
|
|
)
|
|
|
|
// nameserverRecordResponse represents one nameserver's records
|
|
// in the API response.
|
|
type nameserverRecordResponse struct {
|
|
Nameserver string `json:"nameserver"`
|
|
Records map[string][]string `json:"records"`
|
|
Status string `json:"status"`
|
|
Error string `json:"error,omitempty"`
|
|
LastChecked string `json:"lastChecked,omitempty"`
|
|
}
|
|
|
|
// hostnameResponse represents a single hostname in the API response.
|
|
type hostnameResponse struct {
|
|
Hostname string `json:"hostname"`
|
|
Nameservers []nameserverRecordResponse `json:"nameservers,omitempty"`
|
|
LastChecked string `json:"lastChecked,omitempty"`
|
|
Status string `json:"status"`
|
|
}
|
|
|
|
// hostnamesResponse is the top-level response for
|
|
// GET /api/v1/hostnames.
|
|
type hostnamesResponse struct {
|
|
Hostnames []hostnameResponse `json:"hostnames"`
|
|
}
|
|
|
|
// HandleHostnames returns the configured hostnames and their status.
|
|
func (h *Handlers) HandleHostnames() http.HandlerFunc {
|
|
return func(
|
|
writer http.ResponseWriter,
|
|
request *http.Request,
|
|
) {
|
|
configured := h.config.Hostnames
|
|
snapshot := h.state.GetSnapshot()
|
|
|
|
hostnames := make(
|
|
[]hostnameResponse, 0, len(configured),
|
|
)
|
|
|
|
for _, hostname := range configured {
|
|
hr := hostnameResponse{
|
|
Hostname: hostname,
|
|
Status: "pending",
|
|
}
|
|
|
|
hs, ok := snapshot.Hostnames[hostname]
|
|
if ok {
|
|
hr.Status = "ok"
|
|
|
|
if !hs.LastChecked.IsZero() {
|
|
hr.LastChecked = hs.LastChecked.
|
|
Format(time.RFC3339)
|
|
}
|
|
|
|
hr.Nameservers = buildNameserverRecords(
|
|
hs,
|
|
)
|
|
}
|
|
|
|
hostnames = append(hostnames, hr)
|
|
}
|
|
|
|
h.respondJSON(
|
|
writer, request,
|
|
&hostnamesResponse{Hostnames: hostnames},
|
|
http.StatusOK,
|
|
)
|
|
}
|
|
}
|
|
|
|
// buildNameserverRecords converts the per-nameserver state map
|
|
// into a sorted slice for deterministic JSON output.
|
|
func buildNameserverRecords(
|
|
hs *state.HostnameState,
|
|
) []nameserverRecordResponse {
|
|
if hs.RecordsByNameserver == nil {
|
|
return nil
|
|
}
|
|
|
|
nsNames := make(
|
|
[]string, 0, len(hs.RecordsByNameserver),
|
|
)
|
|
for ns := range hs.RecordsByNameserver {
|
|
nsNames = append(nsNames, ns)
|
|
}
|
|
|
|
sort.Strings(nsNames)
|
|
|
|
records := make(
|
|
[]nameserverRecordResponse, 0, len(nsNames),
|
|
)
|
|
|
|
for _, ns := range nsNames {
|
|
nsr := hs.RecordsByNameserver[ns]
|
|
|
|
entry := nameserverRecordResponse{
|
|
Nameserver: ns,
|
|
Records: nsr.Records,
|
|
Status: nsr.Status,
|
|
Error: nsr.Error,
|
|
}
|
|
|
|
if !nsr.LastChecked.IsZero() {
|
|
entry.LastChecked = nsr.LastChecked.
|
|
Format(time.RFC3339)
|
|
}
|
|
|
|
records = append(records, entry)
|
|
}
|
|
|
|
return records
|
|
}
|