feat: add aggregated router-lists catalog endpoint and update module listing filters
CI / changes (push) Successful in 5s
CI / openapi (push) Successful in 22s
CI / go (push) Successful in 38s
CI / bird2 (push) Has been cancelled
CI / docker-go-prime (push) Has been cancelled
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Has been cancelled
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been cancelled
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been cancelled
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been cancelled
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been cancelled
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been cancelled
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been cancelled
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been cancelled
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Has started running
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Has been cancelled
CI / docker-bird (push) Has been cancelled
CI / changes (push) Successful in 5s
CI / openapi (push) Successful in 22s
CI / go (push) Successful in 38s
CI / bird2 (push) Has been cancelled
CI / docker-go-prime (push) Has been cancelled
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Has been cancelled
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been cancelled
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been cancelled
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been cancelled
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been cancelled
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been cancelled
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been cancelled
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been cancelled
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Has started running
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Has been cancelled
CI / docker-bird (push) Has been cancelled
Introduced a new endpoint `GET /v1/router-lists/catalog` that returns a consolidated view of modules, domain entries, ASNs, IP ranges, and communities. Enhanced the existing module listing functionality to support filtering by type and enabled status. Updated documentation to reflect these changes and added tests for the new endpoint and filtering capabilities.
This commit is contained in:
@@ -47,6 +47,7 @@ func (s *Server) registerRoutes() {
|
||||
|
||||
func (s *Server) registerV1(m *http.ServeMux) {
|
||||
m.HandleFunc("GET /modules", s.handleListModules)
|
||||
m.HandleFunc("GET /router-lists/catalog", s.handleRouterListsCatalog)
|
||||
m.HandleFunc("GET /modules/{module_id}", s.handleGetModule)
|
||||
m.HandleFunc("GET /peers", s.handleListPeers)
|
||||
m.HandleFunc("GET /speakers", s.handleListSpeakers)
|
||||
@@ -161,9 +162,27 @@ func (s *Server) handleListModules(w http.ResponseWriter, r *http.Request) {
|
||||
if !s.requireAtLeast(w, a, "viewer") {
|
||||
return
|
||||
}
|
||||
typeFilter := strings.TrimSpace(r.URL.Query().Get("type"))
|
||||
enabledRaw := strings.TrimSpace(r.URL.Query().Get("enabled"))
|
||||
var enabledFilter *bool
|
||||
if enabledRaw != "" {
|
||||
v, err := strconv.ParseBool(enabledRaw)
|
||||
if err != nil {
|
||||
writeProblem(w, http.StatusBadRequest, "Bad Request", "enabled must be boolean")
|
||||
return
|
||||
}
|
||||
enabledFilter = &v
|
||||
}
|
||||
|
||||
mods := s.store.ListModules(a.TenantID)
|
||||
items := make([]map[string]any, 0, len(mods))
|
||||
for _, mod := range mods {
|
||||
if typeFilter != "" && mod.Type != typeFilter {
|
||||
continue
|
||||
}
|
||||
if enabledFilter != nil && mod.Enabled != *enabledFilter {
|
||||
continue
|
||||
}
|
||||
items = append(items, moduleJSON(mod))
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
@@ -171,6 +190,103 @@ func (s *Server) handleListModules(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleRouterListsCatalog(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := authFromContext(r.Context())
|
||||
if !ok {
|
||||
writeProblem(w, http.StatusUnauthorized, "Unauthorized", "missing auth")
|
||||
return
|
||||
}
|
||||
if !s.requireAtLeast(w, a, "viewer") {
|
||||
return
|
||||
}
|
||||
|
||||
mods := s.store.ListModules(a.TenantID)
|
||||
moduleItems := make([]map[string]any, 0, len(mods))
|
||||
domains := make([]map[string]any, 0)
|
||||
asns := make([]map[string]any, 0)
|
||||
ipRanges := make([]map[string]any, 0)
|
||||
|
||||
for _, mod := range mods {
|
||||
switch mod.Type {
|
||||
case "DOMAINS", "AS_PREFIXES", "IP_RANGES":
|
||||
moduleItems = append(moduleItems, moduleJSON(mod))
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
switch mod.Type {
|
||||
case "DOMAINS":
|
||||
list, err := s.store.ListDomainEntries(a.TenantID, mod.ID)
|
||||
if err != nil {
|
||||
writeStoreErr(w, err)
|
||||
return
|
||||
}
|
||||
for _, x := range list {
|
||||
domains = append(domains, map[string]any{
|
||||
"module_id": mod.ID,
|
||||
"entry": domainEntryJSON(x),
|
||||
})
|
||||
}
|
||||
case "AS_PREFIXES":
|
||||
list, err := s.store.ListASEntries(a.TenantID, mod.ID)
|
||||
if err != nil {
|
||||
writeStoreErr(w, err)
|
||||
return
|
||||
}
|
||||
for _, x := range list {
|
||||
asns = append(asns, map[string]any{
|
||||
"module_id": mod.ID,
|
||||
"entry": asEntryJSON(x),
|
||||
})
|
||||
}
|
||||
case "IP_RANGES":
|
||||
list, err := s.store.ListIPRangeEntries(a.TenantID, mod.ID)
|
||||
if err != nil {
|
||||
writeStoreErr(w, err)
|
||||
return
|
||||
}
|
||||
for _, x := range list {
|
||||
ipRanges = append(ipRanges, map[string]any{
|
||||
"module_id": mod.ID,
|
||||
"entry": ipRangeJSON(x),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
comms, err := s.store.ListCommunities(a.TenantID)
|
||||
if err != nil {
|
||||
writeStoreErr(w, err)
|
||||
return
|
||||
}
|
||||
communityItems := make([]map[string]any, 0, len(comms))
|
||||
for _, c := range comms {
|
||||
communityItems = append(communityItems, map[string]any{
|
||||
"id": c.ID,
|
||||
"community": c.Community,
|
||||
"title": c.Title,
|
||||
})
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]any{
|
||||
"modules": map[string]any{
|
||||
"items": moduleItems,
|
||||
},
|
||||
"domains": map[string]any{
|
||||
"items": domains,
|
||||
},
|
||||
"asns": map[string]any{
|
||||
"items": asns,
|
||||
},
|
||||
"ip_ranges": map[string]any{
|
||||
"items": ipRanges,
|
||||
},
|
||||
"communities": map[string]any{
|
||||
"items": communityItems,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) handleGetModule(w http.ResponseWriter, r *http.Request) {
|
||||
a, ok := authFromContext(r.Context())
|
||||
if !ok {
|
||||
|
||||
@@ -149,6 +149,76 @@ func TestAPIRefreshApplyJobsBundle(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("modules filter by type", func(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodGet, base+"/v1/modules?type=IP_RANGES", nil)
|
||||
req.Header.Set("Authorization", "Bearer opkey")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
t.Fatalf("status %d: %s", resp.StatusCode, b)
|
||||
}
|
||||
var body struct {
|
||||
Items []struct {
|
||||
Type string `json:"type"`
|
||||
} `json:"items"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(body.Items) == 0 {
|
||||
t.Fatalf("expected at least one IP_RANGES module")
|
||||
}
|
||||
for _, item := range body.Items {
|
||||
if item.Type != "IP_RANGES" {
|
||||
t.Fatalf("unexpected module type %q", item.Type)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("router lists catalog endpoint", func(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodGet, base+"/v1/router-lists/catalog", nil)
|
||||
req.Header.Set("Authorization", "Bearer opkey")
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
t.Fatalf("status %d: %s", resp.StatusCode, b)
|
||||
}
|
||||
var body struct {
|
||||
Modules struct {
|
||||
Items []map[string]any `json:"items"`
|
||||
} `json:"modules"`
|
||||
Domains struct {
|
||||
Items []map[string]any `json:"items"`
|
||||
} `json:"domains"`
|
||||
ASNs struct {
|
||||
Items []map[string]any `json:"items"`
|
||||
} `json:"asns"`
|
||||
IPRanges struct {
|
||||
Items []map[string]any `json:"items"`
|
||||
} `json:"ip_ranges"`
|
||||
Communities struct {
|
||||
Items []map[string]any `json:"items"`
|
||||
} `json:"communities"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(body.Modules.Items) == 0 {
|
||||
t.Fatalf("expected modules in catalog")
|
||||
}
|
||||
if body.Communities.Items == nil {
|
||||
t.Fatalf("expected communities.items field in catalog")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("rollback queues job", func(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodPost, base+"/v1/revisions/"+rev+"/rollback", nil)
|
||||
req.Header.Set("Authorization", "Bearer opkey")
|
||||
|
||||
Reference in New Issue
Block a user