Files
benvin d9d192757b
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
Add per-device L2/misc long-tail: tunnels/stopped_rules/proxy_arp/proxy_ndp/arp_rules/maclist
Storage + CRUD (migration 0007, model, id-keyed store, REST handlers) + compiler
rendering, each owned by a device. proxy_arp/proxy_ndp share the ProxyEntry
shape via table-parameterized store helpers.
2026-07-26 15:50:01 +10:00

235 lines
6.4 KiB
Go

package server
import (
"context"
"net/http"
"github.com/go-chi/chi/v5"
"git.unkin.net/unkin/tomswallapi/internal/model"
)
// mountPerDeviceL2 wires the per-device L2/misc sections: tunnels, stopped_rules,
// proxy_arp, proxy_ndp, arp_rules, maclist.
func (s *Server) mountPerDeviceL2(r chi.Router) {
idCRUD(r, "/tunnels", s.listTunnels, s.createTunnel, s.getTunnel, s.deleteTunnel)
idCRUD(r, "/stopped-rules", s.listStoppedRules, s.createStoppedRule, s.getStoppedRule, s.deleteStoppedRule)
idCRUD(r, "/proxy-arp", s.listProxyARP, s.createProxyARP, s.getProxyARP, s.deleteProxyARP)
idCRUD(r, "/proxy-ndp", s.listProxyNDP, s.createProxyNDP, s.getProxyNDP, s.deleteProxyNDP)
idCRUD(r, "/arp-rules", s.listArpRules, s.createArpRule, s.getArpRule, s.deleteArpRule)
idCRUD(r, "/maclist", s.listMaclist, s.createMaclist, s.getMaclist, s.deleteMaclist)
}
// idCRUD wires the four standard id-keyed handlers for a collection.
func idCRUD(r chi.Router, path string, list, create, get, del http.HandlerFunc) {
r.Route(path, func(r chi.Router) {
r.Get("/", list)
r.Post("/", create)
r.Get("/{id}", get)
r.Delete("/{id}", del)
})
}
func (s *Server) listTunnels(w http.ResponseWriter, r *http.Request) {
list, err := s.store.ListTunnels(r.Context())
respondList(w, list, err)
}
func (s *Server) createTunnel(w http.ResponseWriter, r *http.Request) {
var v model.Tunnel
if !decode(w, r, &v) {
return
}
if v.Device == "" || v.Type == "" || v.Zone == "" {
writeError(w, http.StatusBadRequest, "device, type, and zone are required")
return
}
id, err := s.store.CreateTunnel(r.Context(), v)
if err == nil {
v.ID = id
}
respondCreated(w, v, err)
}
func (s *Server) getTunnel(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
v, err := s.store.GetTunnel(r.Context(), id)
respondOne(w, v, err)
}
func (s *Server) deleteTunnel(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
respondDelete(w, s.store.DeleteTunnel(r.Context(), id))
}
func (s *Server) listStoppedRules(w http.ResponseWriter, r *http.Request) {
list, err := s.store.ListStoppedRules(r.Context())
respondList(w, list, err)
}
func (s *Server) createStoppedRule(w http.ResponseWriter, r *http.Request) {
var v model.StoppedRule
if !decode(w, r, &v) {
return
}
if v.Device == "" || v.Action == "" {
writeError(w, http.StatusBadRequest, "device and action are required")
return
}
id, err := s.store.CreateStoppedRule(r.Context(), v)
if err == nil {
v.ID = id
}
respondCreated(w, v, err)
}
func (s *Server) getStoppedRule(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
v, err := s.store.GetStoppedRule(r.Context(), id)
respondOne(w, v, err)
}
func (s *Server) deleteStoppedRule(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
respondDelete(w, s.store.DeleteStoppedRule(r.Context(), id))
}
func (s *Server) listProxyARP(w http.ResponseWriter, r *http.Request) {
list, err := s.store.ListProxyARP(r.Context())
respondList(w, list, err)
}
func (s *Server) createProxyARP(w http.ResponseWriter, r *http.Request) {
s.createProxy(w, r, s.store.CreateProxyARP)
}
func (s *Server) getProxyARP(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
v, err := s.store.GetProxyARP(r.Context(), id)
respondOne(w, v, err)
}
func (s *Server) deleteProxyARP(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
respondDelete(w, s.store.DeleteProxyARP(r.Context(), id))
}
func (s *Server) listProxyNDP(w http.ResponseWriter, r *http.Request) {
list, err := s.store.ListProxyNDP(r.Context())
respondList(w, list, err)
}
func (s *Server) createProxyNDP(w http.ResponseWriter, r *http.Request) {
s.createProxy(w, r, s.store.CreateProxyNDP)
}
func (s *Server) getProxyNDP(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
v, err := s.store.GetProxyNDP(r.Context(), id)
respondOne(w, v, err)
}
func (s *Server) deleteProxyNDP(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
respondDelete(w, s.store.DeleteProxyNDP(r.Context(), id))
}
// createProxy is shared by proxy_arp/ndp (identical shape).
func (s *Server) createProxy(w http.ResponseWriter, r *http.Request, create func(context.Context, model.ProxyEntry) (int64, error)) {
var v model.ProxyEntry
if !decode(w, r, &v) {
return
}
if v.Device == "" || v.Address == "" || v.External == "" {
writeError(w, http.StatusBadRequest, "device, address, and external are required")
return
}
id, err := create(r.Context(), v)
if err == nil {
v.ID = id
}
respondCreated(w, v, err)
}
func (s *Server) listArpRules(w http.ResponseWriter, r *http.Request) {
list, err := s.store.ListArpRules(r.Context())
respondList(w, list, err)
}
func (s *Server) createArpRule(w http.ResponseWriter, r *http.Request) {
var v model.ArpRule
if !decode(w, r, &v) {
return
}
if v.Device == "" || v.Action == "" {
writeError(w, http.StatusBadRequest, "device and action are required")
return
}
id, err := s.store.CreateArpRule(r.Context(), v)
if err == nil {
v.ID = id
}
respondCreated(w, v, err)
}
func (s *Server) getArpRule(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
v, err := s.store.GetArpRule(r.Context(), id)
respondOne(w, v, err)
}
func (s *Server) deleteArpRule(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
respondDelete(w, s.store.DeleteArpRule(r.Context(), id))
}
func (s *Server) listMaclist(w http.ResponseWriter, r *http.Request) {
list, err := s.store.ListMaclist(r.Context())
respondList(w, list, err)
}
func (s *Server) createMaclist(w http.ResponseWriter, r *http.Request) {
var v model.MaclistEntry
if !decode(w, r, &v) {
return
}
if v.Device == "" || v.Action == "" || v.Interface == "" {
writeError(w, http.StatusBadRequest, "device, action, and interface are required")
return
}
id, err := s.store.CreateMaclist(r.Context(), v)
if err == nil {
v.ID = id
}
respondCreated(w, v, err)
}
func (s *Server) getMaclist(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
v, err := s.store.GetMaclist(r.Context(), id)
respondOne(w, v, err)
}
func (s *Server) deleteMaclist(w http.ResponseWriter, r *http.Request) {
id, ok := idParam(w, r)
if !ok {
return
}
respondDelete(w, s.store.DeleteMaclist(r.Context(), id))
}