Add per-device routing long-tail: hosts, providers, routes, routing_rules
Storage + CRUD (migration 0006, model, id-keyed store, REST handlers) plus compiler rendering: each section is owned by a device and projected into that device's rendered config (hosts/providers/routes/routing_rules).
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"git.unkin.net/unkin/tomswallapi/internal/model"
|
||||
)
|
||||
|
||||
// mountPerDevice wires the per-device long-tail sections: hosts, providers,
|
||||
// routes, and routing-rules.
|
||||
func (s *Server) mountPerDevice(r chi.Router) {
|
||||
r.Route("/hosts", func(r chi.Router) {
|
||||
r.Get("/", s.listHosts)
|
||||
r.Post("/", s.createHost)
|
||||
r.Get("/{id}", s.getHost)
|
||||
r.Delete("/{id}", s.deleteHost)
|
||||
})
|
||||
r.Route("/providers", func(r chi.Router) {
|
||||
r.Get("/", s.listProviders)
|
||||
r.Post("/", s.createProvider)
|
||||
r.Get("/{id}", s.getProvider)
|
||||
r.Delete("/{id}", s.deleteProvider)
|
||||
})
|
||||
r.Route("/routes", func(r chi.Router) {
|
||||
r.Get("/", s.listRoutes)
|
||||
r.Post("/", s.createRoute)
|
||||
r.Get("/{id}", s.getRoute)
|
||||
r.Delete("/{id}", s.deleteRoute)
|
||||
})
|
||||
r.Route("/routing-rules", func(r chi.Router) {
|
||||
r.Get("/", s.listRoutingRules)
|
||||
r.Post("/", s.createRoutingRule)
|
||||
r.Get("/{id}", s.getRoutingRule)
|
||||
r.Delete("/{id}", s.deleteRoutingRule)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) listHosts(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListHosts(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
|
||||
func (s *Server) createHost(w http.ResponseWriter, r *http.Request) {
|
||||
var v model.Host
|
||||
if !decode(w, r, &v) {
|
||||
return
|
||||
}
|
||||
if v.Device == "" || v.Zone == "" || v.Interface == "" {
|
||||
writeError(w, http.StatusBadRequest, "device, zone, and interface are required")
|
||||
return
|
||||
}
|
||||
id, err := s.store.CreateHost(r.Context(), v)
|
||||
if err == nil {
|
||||
v.ID = id
|
||||
}
|
||||
respondCreated(w, v, err)
|
||||
}
|
||||
|
||||
func (s *Server) getHost(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := s.store.GetHost(r.Context(), id)
|
||||
respondOne(w, v, err)
|
||||
}
|
||||
|
||||
func (s *Server) deleteHost(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
respondDelete(w, s.store.DeleteHost(r.Context(), id))
|
||||
}
|
||||
|
||||
func (s *Server) listProviders(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListProviders(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
|
||||
func (s *Server) createProvider(w http.ResponseWriter, r *http.Request) {
|
||||
var v model.Provider
|
||||
if !decode(w, r, &v) {
|
||||
return
|
||||
}
|
||||
if v.Device == "" || v.Name == "" || v.Interface == "" {
|
||||
writeError(w, http.StatusBadRequest, "device, name, and interface are required")
|
||||
return
|
||||
}
|
||||
id, err := s.store.CreateProvider(r.Context(), v)
|
||||
if err == nil {
|
||||
v.ID = id
|
||||
}
|
||||
respondCreated(w, v, err)
|
||||
}
|
||||
|
||||
func (s *Server) getProvider(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := s.store.GetProvider(r.Context(), id)
|
||||
respondOne(w, v, err)
|
||||
}
|
||||
|
||||
func (s *Server) deleteProvider(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
respondDelete(w, s.store.DeleteProvider(r.Context(), id))
|
||||
}
|
||||
|
||||
func (s *Server) listRoutes(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListRoutes(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
|
||||
func (s *Server) createRoute(w http.ResponseWriter, r *http.Request) {
|
||||
var v model.Route
|
||||
if !decode(w, r, &v) {
|
||||
return
|
||||
}
|
||||
if v.Device == "" || v.Dest == "" {
|
||||
writeError(w, http.StatusBadRequest, "device and dest are required")
|
||||
return
|
||||
}
|
||||
id, err := s.store.CreateRoute(r.Context(), v)
|
||||
if err == nil {
|
||||
v.ID = id
|
||||
}
|
||||
respondCreated(w, v, err)
|
||||
}
|
||||
|
||||
func (s *Server) getRoute(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := s.store.GetRoute(r.Context(), id)
|
||||
respondOne(w, v, err)
|
||||
}
|
||||
|
||||
func (s *Server) deleteRoute(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
respondDelete(w, s.store.DeleteRoute(r.Context(), id))
|
||||
}
|
||||
|
||||
func (s *Server) listRoutingRules(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := s.store.ListRoutingRules(r.Context())
|
||||
respondList(w, list, err)
|
||||
}
|
||||
|
||||
func (s *Server) createRoutingRule(w http.ResponseWriter, r *http.Request) {
|
||||
var v model.RoutingRule
|
||||
if !decode(w, r, &v) {
|
||||
return
|
||||
}
|
||||
if v.Device == "" || v.Provider == "" {
|
||||
writeError(w, http.StatusBadRequest, "device and provider are required")
|
||||
return
|
||||
}
|
||||
id, err := s.store.CreateRoutingRule(r.Context(), v)
|
||||
if err == nil {
|
||||
v.ID = id
|
||||
}
|
||||
respondCreated(w, v, err)
|
||||
}
|
||||
|
||||
func (s *Server) getRoutingRule(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
v, err := s.store.GetRoutingRule(r.Context(), id)
|
||||
respondOne(w, v, err)
|
||||
}
|
||||
|
||||
func (s *Server) deleteRoutingRule(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := idParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
respondDelete(w, s.store.DeleteRoutingRule(r.Context(), id))
|
||||
}
|
||||
@@ -61,6 +61,7 @@ func (s *Server) mountResources(r chi.Router) {
|
||||
})
|
||||
s.mountNAT(r)
|
||||
s.mountLongtail(r)
|
||||
s.mountPerDevice(r)
|
||||
}
|
||||
|
||||
// respondOne writes a single resource, mapping ErrNotFound to 404.
|
||||
@@ -76,6 +77,15 @@ func respondOne(w http.ResponseWriter, v any, err error) {
|
||||
writeJSON(w, http.StatusOK, v)
|
||||
}
|
||||
|
||||
// respondCreated writes a 201 with the created resource, or 500 on error.
|
||||
func respondCreated(w http.ResponseWriter, v any, err error) {
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusCreated, v)
|
||||
}
|
||||
|
||||
// respondDelete maps a delete result to 204/404/500.
|
||||
func respondDelete(w http.ResponseWriter, err error) {
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user