mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-16 20:20:07 +00:00
Warn at startup when limits are on without trusted_proxies, and once when a request carries forwarding headers Beacon ignores. Reject short or whitespace admin keys and negative durations. Key default-window stats by hour, return 500 for scope lookup failures, and validate IATA path params.
101 lines
3.4 KiB
Go
101 lines
3.4 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/MeshCore-Beacon/beacon-server/internal/api"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
// ScopeMembership lists scopes by configured region and imported catalogue membership.
|
|
type ScopeMembership interface {
|
|
NamesForIATAs(iatas []string) []string // sorted
|
|
}
|
|
|
|
func scopeNamesFor(scopes ScopeMembership, iatas []string) []string {
|
|
if scopes == nil {
|
|
return []string{}
|
|
}
|
|
return scopes.NamesForIATAs(iatas)
|
|
}
|
|
|
|
// ScopesRouter mounts all /scopes routes onto a subrouter.
|
|
//
|
|
// GET /scopes → listScopes
|
|
// GET /scopes/{name} → getScope
|
|
func ScopesRouter(reader api.Reader, scopes ScopeMembership) http.Handler {
|
|
r := chi.NewRouter()
|
|
r.Get("/", listScopes(reader, scopes))
|
|
r.Get("/{name}", getScope(reader))
|
|
return r
|
|
}
|
|
|
|
// listScopes godoc
|
|
//
|
|
// @Summary List transport scopes
|
|
// @Description Without filters, lists every stored scope name, including imported names retained after an importer is disabled or a source is removed. With IATA or region filters, lists only manual scopes configured for a matching region and imported scopes whose current MeshMapper catalogue includes a matching IATA; observed traffic does not add scopes.
|
|
// @Tags Scopes
|
|
// @Produce json
|
|
// @Param iatas query string false "Filter by IATA code(s), comma-separated"
|
|
// @Param region query string false "Filter by region slug"
|
|
// @Param regionId query int false "Filter by region ID"
|
|
// @Success 200 {array} string
|
|
// @Failure 400 {object} handlers.APIError
|
|
// @Failure 500 {object} handlers.APIError
|
|
// @Router /scopes [get]
|
|
func listScopes(reader api.Reader, scopes ScopeMembership) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
iatas := parseIATAs(r)
|
|
regionID, region := r.URL.Query().Get("regionId"), r.URL.Query().Get("region")
|
|
if regionID != "" || region != "" {
|
|
regionIATAs, err := resolveRegionIATAs(r.Context(), regionID, region, reader)
|
|
if err != nil {
|
|
respondError(w, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
iatas = append(iatas, regionIATAs...)
|
|
} else if len(iatas) == 0 {
|
|
names, err := reader.GetScopeNames(r.Context())
|
|
if err != nil {
|
|
respondError(w, http.StatusInternalServerError, "internal server error")
|
|
return
|
|
}
|
|
respond(w, http.StatusOK, names)
|
|
return
|
|
}
|
|
respond(w, http.StatusOK, scopeNamesFor(scopes, iatas))
|
|
}
|
|
}
|
|
|
|
// getScope godoc
|
|
//
|
|
// @Summary Get scope detail by name
|
|
// @Description packetCount sums packets per hour across every retained rollup hour; iatas are the IATAs that heard the scope's packets. Observer and node counts are current memberships.
|
|
// @Tags Scopes
|
|
// @Produce json
|
|
// @Param name path string true "Scope name e.g. %23bc (URL-encoded #bc)"
|
|
// @Success 200 {object} api.ScopeDetail
|
|
// @Failure 404 {object} handlers.APIError
|
|
// @Failure 500 {object} handlers.APIError
|
|
// @Router /scopes/{name} [get]
|
|
func getScope(reader api.Reader) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
name := chi.URLParam(r, "name")
|
|
scope, err := reader.GetScopeByName(r.Context(), name)
|
|
switch {
|
|
case errors.Is(err, pgx.ErrNoRows), err == nil && scope == nil:
|
|
respondError(w, http.StatusNotFound, "scope not found")
|
|
return
|
|
case err != nil:
|
|
respondError(w, http.StatusInternalServerError, "failed to load scope")
|
|
return
|
|
}
|
|
respond(w, http.StatusOK, scope)
|
|
}
|
|
}
|