initial commit

This commit is contained in:
2026-08-16 21:18:45 -05:00
commit 1e05a01bcf
122 changed files with 29178 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
package api
import (
"net/http"
"github.com/owen/vibedns/internal/app"
)
// routes registers every REST endpoint.
//
// Paths follow the usual collection/item shape, and PUT and PATCH are both
// accepted for updates: the service layer carries forward any field a caller
// omits, so a partial update behaves the way PATCH callers expect.
func (s *Server) routes(mux *http.ServeMux) {
const v1 = "/api/v1"
// Zones.
mux.HandleFunc("GET "+v1+"/zones", s.h(s.listZones))
mux.HandleFunc("POST "+v1+"/zones", s.h(s.createZone))
mux.HandleFunc("GET "+v1+"/zones/{id}", s.h(s.getZone))
mux.HandleFunc("PUT "+v1+"/zones/{id}", s.h(s.updateZone))
mux.HandleFunc("PATCH "+v1+"/zones/{id}", s.h(s.updateZone))
mux.HandleFunc("DELETE "+v1+"/zones/{id}", s.h(s.deleteZone))
mux.HandleFunc("POST "+v1+"/zones/{id}/clone", s.h(s.cloneZone))
mux.HandleFunc("GET "+v1+"/zones/{id}/export", s.h(s.exportZone))
mux.HandleFunc("POST "+v1+"/zones/{id}/import", s.h(s.importZone))
// Records, both nested under a zone and flat.
mux.HandleFunc("GET "+v1+"/zones/{id}/records", s.h(s.listZoneRecords))
mux.HandleFunc("POST "+v1+"/zones/{id}/records", s.h(s.createRecord))
mux.HandleFunc("GET "+v1+"/records", s.h(s.listRecords))
mux.HandleFunc("GET "+v1+"/records/{id}", s.h(s.getRecord))
mux.HandleFunc("PUT "+v1+"/records/{id}", s.h(s.updateRecord))
mux.HandleFunc("PATCH "+v1+"/records/{id}", s.h(s.updateRecord))
mux.HandleFunc("DELETE "+v1+"/records/{id}", s.h(s.deleteRecord))
mux.HandleFunc("GET "+v1+"/record-types", s.h(s.listRecordTypes))
// Client networks.
mux.HandleFunc("GET "+v1+"/networks", s.h(s.listNetworks))
mux.HandleFunc("POST "+v1+"/networks", s.h(s.createNetwork))
mux.HandleFunc("GET "+v1+"/networks/{id}", s.h(s.getNetwork))
mux.HandleFunc("PUT "+v1+"/networks/{id}", s.h(s.updateNetwork))
mux.HandleFunc("PATCH "+v1+"/networks/{id}", s.h(s.updateNetwork))
mux.HandleFunc("DELETE "+v1+"/networks/{id}", s.h(s.deleteNetwork))
// Policies.
mux.HandleFunc("GET "+v1+"/policies", s.h(s.listPolicies))
mux.HandleFunc("POST "+v1+"/policies", s.h(s.createPolicy))
mux.HandleFunc("GET "+v1+"/policies/{id}", s.h(s.getPolicy))
mux.HandleFunc("PUT "+v1+"/policies/{id}", s.h(s.updatePolicy))
mux.HandleFunc("PATCH "+v1+"/policies/{id}", s.h(s.updatePolicy))
mux.HandleFunc("DELETE "+v1+"/policies/{id}", s.h(s.deletePolicy))
// Blacklists and allowlists share one implementation, differing only in
// the kind they filter and create.
for _, kind := range []string{"blacklists", "allowlists"} {
k := kind
mux.HandleFunc("GET "+v1+"/"+k, s.h(s.listListsFor(k)))
mux.HandleFunc("POST "+v1+"/"+k, s.h(s.createListFor(k)))
mux.HandleFunc("GET "+v1+"/"+k+"/{id}", s.h(s.getList))
mux.HandleFunc("PUT "+v1+"/"+k+"/{id}", s.h(s.updateList))
mux.HandleFunc("PATCH "+v1+"/"+k+"/{id}", s.h(s.updateList))
mux.HandleFunc("DELETE "+v1+"/"+k+"/{id}", s.h(s.deleteList))
mux.HandleFunc("GET "+v1+"/"+k+"/{id}/domains", s.h(s.listDomains))
mux.HandleFunc("POST "+v1+"/"+k+"/{id}/domains", s.h(s.addDomain))
mux.HandleFunc("DELETE "+v1+"/"+k+"/{id}/domains", s.h(s.clearDomains))
mux.HandleFunc("POST "+v1+"/"+k+"/{id}/import", s.h(s.importDomains))
mux.HandleFunc("GET "+v1+"/"+k+"/{id}/export", s.h(s.exportDomains))
}
mux.HandleFunc("DELETE "+v1+"/domains/{id}", s.h(s.deleteDomain))
// Cache.
mux.HandleFunc("GET "+v1+"/cache", s.h(s.getCache))
mux.HandleFunc("DELETE "+v1+"/cache", s.h(s.flushCache))
mux.HandleFunc("GET "+v1+"/cache/entries", s.h(s.listCacheEntries))
mux.HandleFunc("DELETE "+v1+"/cache/entries", s.h(s.deleteCacheEntry))
// Settings.
mux.HandleFunc("GET "+v1+"/settings", s.h(s.getSettings))
mux.HandleFunc("PUT "+v1+"/settings", s.h(s.updateSettings))
mux.HandleFunc("PATCH "+v1+"/settings", s.h(s.updateSettings))
// Statistics and logs.
mux.HandleFunc("GET "+v1+"/stats", s.h(s.getStats))
mux.HandleFunc("GET "+v1+"/querylog", s.h(s.listQueryLog))
mux.HandleFunc("DELETE "+v1+"/querylog", s.h(s.clearQueryLog))
mux.HandleFunc("GET "+v1+"/auditlog", s.h(s.listAuditLog))
// Operations.
mux.HandleFunc("GET "+v1+"/backups", s.h(s.listBackups))
mux.HandleFunc("POST "+v1+"/backups", s.h(s.createBackup))
mux.HandleFunc("GET "+v1+"/config/export", s.h(s.exportConfig))
mux.HandleFunc("POST "+v1+"/config/import", s.h(s.importConfig))
// Tools.
mux.HandleFunc("GET "+v1+"/tools/reverse-zone", s.h(s.reverseZone))
mux.HandleFunc("GET "+v1+"/tools/lookup", s.h(s.lookup))
mux.HandleFunc("GET "+v1+"/tools/domain-check", s.h(s.domainCheck))
// Service metadata. Both the bare path and the trailing-slash form serve
// the index, because ServeMux redirects the former to the latter.
mux.HandleFunc("GET "+v1, s.h(s.index))
mux.HandleFunc("GET "+v1+"/{$}", s.h(s.index))
mux.HandleFunc("GET "+v1+"/", s.h(s.notFound))
}
// index describes the API surface, so a caller can discover it with one GET.
func (s *Server) index(w http.ResponseWriter, r *http.Request) error {
writeJSON(w, http.StatusOK, map[string]any{
"version": "v1",
"resources": []string{
"/api/v1/zones", "/api/v1/records", "/api/v1/networks",
"/api/v1/policies", "/api/v1/blacklists", "/api/v1/allowlists",
"/api/v1/cache", "/api/v1/settings", "/api/v1/stats",
"/api/v1/querylog", "/api/v1/auditlog", "/api/v1/backups",
"/api/v1/config/export", "/api/v1/config/import",
"/api/v1/tools/lookup", "/api/v1/tools/reverse-zone",
},
})
return nil
}
func (s *Server) notFound(w http.ResponseWriter, r *http.Request) error {
return app.NotFound("No API endpoint matches %s %s.", r.Method, r.URL.Path)
}