fix(api): answer 404 only for missing rows

Lookups turned every error into a 404, and region filters into a 400, so a
transient database failure looked final to clients that don't retry 4xx.
Missing rows stay 404/400; anything else is a 500. Empty /regions and
/iatas lists are [] rather than null.
This commit is contained in:
MrAlders0n authored and MrAlders0n committed 2026-10-02 19:03:46 -04:00
1 parent 5b49356437
commit a1aff810e9
20 files changed
+220 -40

No files matched your search

+8 -1
View File
@@ -5,6 +5,7 @@ package handlers
import (
"encoding/hex"
"errors"
"net/http"
"strconv"
"strings"
@@ -12,6 +13,7 @@ import (
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5"
)
// ChannelsRouter mounts all /channels routes onto a subrouter.
@@ -117,6 +119,7 @@ func listChannels(reader api.Reader) http.HandlerFunc {
// @Success 200 {object} api.Channel
// @Failure 400 {object} handlers.APIError
// @Failure 404 {object} handlers.APIError
// @Failure 500 {object} handlers.APIError
// @Router /channels/{channelID} [get]
func getChannel(reader api.Reader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@@ -130,9 +133,13 @@ func getChannel(reader api.Reader) http.HandlerFunc {
id = i
}
channel, err := reader.GetChannel(r.Context(), int32(id))
if err != nil {
switch {
case errors.Is(err, pgx.ErrNoRows), err == nil && channel == nil:
respondError(w, http.StatusNotFound, "channel not found")
return
case err != nil:
respondError(w, http.StatusInternalServerError, "internal server error")
return
}
respond(w, http.StatusOK, channel)
}
+19 -4
View File
@@ -4,11 +4,13 @@
package handlers
import (
"errors"
"net/http"
"strings"
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5"
)
// IATAsRouter mounts all /iatas routes onto a subrouter.
@@ -45,15 +47,18 @@ func pathIATA(r *http.Request) (string, bool) {
// @Tags IATAs
// @Produce json
// @Success 200 {array} api.IATA
// @Failure 404 {object} handlers.APIError
// @Failure 500 {object} handlers.APIError
// @Router /iatas [get]
func listIATAs(reader api.Reader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
iatas, err := reader.ListIATAs(r.Context())
if err != nil {
respondError(w, http.StatusNotFound, "no IATAs found")
respondError(w, http.StatusInternalServerError, "internal server error")
return
}
if iatas == nil {
iatas = []api.IATA{}
}
respond(w, http.StatusOK, iatas)
}
}
@@ -66,6 +71,7 @@ func listIATAs(reader api.Reader) http.HandlerFunc {
// @Param iata path string true "3-letter IATA code"
// @Success 200 {object} api.IATA
// @Failure 404 {object} handlers.APIError
// @Failure 500 {object} handlers.APIError
// @Router /iatas/{iata} [get]
func getIATA(reader api.Reader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@@ -75,9 +81,13 @@ func getIATA(reader api.Reader) http.HandlerFunc {
return
}
result, err := reader.GetIATA(r.Context(), iata)
if err != nil {
switch {
case errors.Is(err, pgx.ErrNoRows), err == nil && result == nil:
respondError(w, http.StatusNotFound, "IATA not found")
return
case err != nil:
respondError(w, http.StatusInternalServerError, "internal server error")
return
}
respond(w, http.StatusOK, result)
}
@@ -92,6 +102,7 @@ func getIATA(reader api.Reader) http.HandlerFunc {
// @Success 200 {object} object "GeoJSON Feature (Polygon or MultiPolygon geometry, with bbox)"
// @Success 204 "IATA exists but has no border configured"
// @Failure 404 {object} handlers.APIError
// @Failure 500 {object} handlers.APIError
// @Router /iatas/{iata}/border [get]
func getIATABorder(reader api.Reader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@@ -101,9 +112,13 @@ func getIATABorder(reader api.Reader) http.HandlerFunc {
return
}
border, err := reader.GetIATABorder(r.Context(), iata)
if err != nil {
switch {
case errors.Is(err, pgx.ErrNoRows):
respondError(w, http.StatusNotFound, "IATA not found")
return
case err != nil:
respondError(w, http.StatusInternalServerError, "internal server error")
return
}
// A cache round-trip re-encodes a nil json.RawMessage as the literal 4-byte JSON
// "null" rather than leaving it empty, so both must be treated as "no border set".
+2 -2
View File
@@ -6,13 +6,13 @@ package handlers
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5"
)
func TestListIATAs_OK(t *testing.T) {
@@ -148,7 +148,7 @@ func TestGetIATABorder_NotFound(t *testing.T) {
r := chi.NewRouter()
r.Get("/iatas/{iata}/border", getIATABorder(stubReader{
getIATABorder: func(_ context.Context, _ string) (json.RawMessage, error) {
return nil, errors.New("not found")
return nil, pgx.ErrNoRows
},
}))
req := httptest.NewRequest(http.MethodGet, "/iatas/ZZZ/border", nil)
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
package handlers
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
// Lookups answer 404 only for a missing row; any other failure is a retryable 500.
func TestLookupErrors(t *testing.T) {
id := uuid.NewString()
for _, tc := range []struct {
name string
router func(error) http.Handler
path string
missing int // status for pgx.ErrNoRows
}{
{"region", func(err error) http.Handler {
return RegionsRouter(stubReader{getRegion: func(context.Context, int32) (*api.Region, error) { return nil, err }})
}, "/1", http.StatusNotFound},
{"regions", func(err error) http.Handler {
return RegionsRouter(stubReader{listRegions: func(context.Context) ([]api.RegionSummary, error) { return nil, err }})
}, "/", http.StatusInternalServerError},
{"iata", func(err error) http.Handler {
return IATAsRouter(stubReader{getIATA: func(context.Context, string) (*api.IATA, error) { return nil, err }})
}, "/YOW", http.StatusNotFound},
{"iata border", func(err error) http.Handler {
return IATAsRouter(stubReader{getIATABorder: func(context.Context, string) (json.RawMessage, error) { return nil, err }})
}, "/YOW/border", http.StatusNotFound},
{"iatas", func(err error) http.Handler {
return IATAsRouter(stubReader{listIATAs: func(context.Context) ([]api.IATA, error) { return nil, err }})
}, "/", http.StatusInternalServerError},
{"channel", func(err error) http.Handler {
return ChannelsRouter(stubReader{getChannel: func(context.Context, int32) (*api.Channel, error) { return nil, err }})
}, "/7", http.StatusNotFound},
{"observer", func(err error) http.Handler {
return ObserversRouter(stubReader{getObserver: func(context.Context, uuid.UUID) (*api.Observer, error) { return nil, err }})
}, "/" + id, http.StatusNotFound},
{"node", func(err error) http.Handler {
return NodesRouter(stubReader{getNode: func(context.Context, uuid.UUID) (*api.Node, error) { return nil, err }})
}, "/" + id, http.StatusNotFound},
{"packet", func(err error) http.Handler {
return PacketsRouter(stubReader{getPacket: func(context.Context, []byte) (*api.Packet, error) { return nil, err }})
}, "/abcd", http.StatusNotFound},
} {
for _, c := range []struct {
err error
want int
}{
{pgx.ErrNoRows, tc.missing},
{context.DeadlineExceeded, http.StatusInternalServerError},
} {
t.Run(tc.name+"/"+c.err.Error(), func(t *testing.T) {
w := httptest.NewRecorder()
tc.router(c.err).ServeHTTP(w, httptest.NewRequest(http.MethodGet, tc.path, nil))
if w.Code != c.want {
t.Fatalf("status = %d, want %d", w.Code, c.want)
}
var body map[string]APIError
if err := json.Unmarshal(w.Body.Bytes(), &body); err != nil || body["error"].Message == "" {
t.Fatalf("not a JSON error: %s", w.Body.String())
}
})
}
}
}
// An unknown region filter is the caller's mistake; a failed lookup is retryable.
func TestRegionFilterErrors(t *testing.T) {
for _, tc := range []struct {
err error
want int
}{
{pgx.ErrNoRows, http.StatusBadRequest},
{context.DeadlineExceeded, http.StatusInternalServerError},
} {
reader := stubReader{getRegionBySlug: func(context.Context, string) (*api.Region, error) { return nil, tc.err }}
for name, h := range map[string]http.Handler{
"packets": PacketsRouter(reader),
"nodes": NodesRouter(reader),
"traces": TracesRouter(reader),
} {
t.Run(name+"/"+tc.err.Error(), func(t *testing.T) {
w := httptest.NewRecorder()
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/?region=nowhere", nil))
if w.Code != tc.want {
t.Fatalf("status = %d, want %d", w.Code, tc.want)
}
})
}
}
}
func TestEmptyListsAreArrays(t *testing.T) {
for name, h := range map[string]http.Handler{
"regions": RegionsRouter(stubReader{}),
"iatas": IATAsRouter(stubReader{}),
} {
t.Run(name, func(t *testing.T) {
w := httptest.NewRecorder()
h.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/", nil))
if w.Code != http.StatusOK || w.Body.String() != "[]\n" {
t.Fatalf("got %d %q, want 200 []", w.Code, w.Body.String())
}
})
}
}
+1 -1
View File
@@ -147,7 +147,7 @@ func listMessagesBackfill(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), r.URL.Query().Get("regionId"), r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
+9 -2
View File
@@ -5,6 +5,7 @@ package handlers
import (
"encoding/hex"
"errors"
"net/http"
"strconv"
"strings"
@@ -12,6 +13,7 @@ import (
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
// NodesRouter mounts all /nodes routes onto a subrouter.
@@ -99,7 +101,7 @@ func listNodes(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -156,6 +158,7 @@ func listNodes(reader api.Reader) http.HandlerFunc {
// @Success 200 {object} api.Node
// @Failure 400 {object} handlers.APIError
// @Failure 404 {object} handlers.APIError
// @Failure 500 {object} handlers.APIError
// @Router /nodes/{nodeId} [get]
func getNode(reader api.Reader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@@ -165,9 +168,13 @@ func getNode(reader api.Reader) http.HandlerFunc {
return
}
node, err := reader.GetNode(r.Context(), nodeID)
if err != nil {
switch {
case errors.Is(err, pgx.ErrNoRows), err == nil && node == nil:
respondError(w, http.StatusNotFound, "node not found")
return
case err != nil:
respondError(w, http.StatusInternalServerError, "internal server error")
return
}
respond(w, http.StatusOK, node)
}
+1 -1
View File
@@ -64,7 +64,7 @@ func getObserverComparison(reader api.Reader) http.HandlerFunc {
if q.Get("regionId") != "" || q.Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(ctx, q.Get("regionId"), q.Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, "region not found")
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
+7 -2
View File
@@ -79,7 +79,7 @@ func listObservers(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -102,6 +102,7 @@ func listObservers(reader api.Reader) http.HandlerFunc {
// @Success 200 {object} api.Observer
// @Failure 400 {object} handlers.APIError
// @Failure 404 {object} handlers.APIError
// @Failure 500 {object} handlers.APIError
// @Router /observers/{observerId} [get]
func getObserver(reader api.Reader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@@ -112,9 +113,13 @@ func getObserver(reader api.Reader) http.HandlerFunc {
return
}
obs, err := reader.GetObserver(r.Context(), id)
if err != nil {
switch {
case errors.Is(err, pgx.ErrNoRows), err == nil && obs == nil:
respondError(w, http.StatusNotFound, "observer not found")
return
case err != nil:
respondError(w, http.StatusInternalServerError, "internal server error")
return
}
respond(w, http.StatusOK, obs)
}
+9 -3
View File
@@ -5,6 +5,7 @@ package handlers
import (
"encoding/hex"
"errors"
"net/http"
"strconv"
"strings"
@@ -12,6 +13,7 @@ import (
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5"
)
// PacketsRouter mounts all /packets routes onto a subrouter.
@@ -102,7 +104,7 @@ func listPackets(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -172,7 +174,7 @@ func listPacketsBackfill(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), r.URL.Query().Get("regionId"), r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -207,9 +209,13 @@ func getPacket(reader api.Reader) http.HandlerFunc {
return
}
packet, err := reader.GetPacket(r.Context(), hash)
if err != nil {
switch {
case errors.Is(err, pgx.ErrNoRows), err == nil && packet == nil:
respondError(w, http.StatusNotFound, "packet not found")
return
case err != nil:
respondError(w, http.StatusInternalServerError, "internal server error")
return
}
respond(w, http.StatusOK, packet)
}
+1 -1
View File
@@ -43,7 +43,7 @@ func getPathStats(reader api.Reader) http.HandlerFunc {
if q.Get("regionId") != "" || q.Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(ctx, q.Get("regionId"), q.Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, "region not found")
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
+2 -1
View File
@@ -14,6 +14,7 @@ import (
"time"
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/jackc/pgx/v5"
)
type pathReader struct {
@@ -53,7 +54,7 @@ func TestPathsFiltersErrorsAndCancellation(t *testing.T) {
return &api.Region{}, nil
}
if slug == "missing" {
return nil, errors.New("not found")
return nil, pgx.ErrNoRows
}
return &api.Region{IATAs: []string{"YVR"}}, nil
}}, read: func(ctx context.Context, since, until time.Time, iatas []string) (*api.PathStats, error) {
+23 -3
View File
@@ -5,6 +5,7 @@ package handlers
import (
"context"
"errors"
"fmt"
"net/http"
"strconv"
@@ -13,6 +14,7 @@ import (
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5"
)
// RegionsRouter mounts all /regions routes onto a subrouter.
@@ -35,15 +37,18 @@ func RegionsRouter(reader api.Reader) http.Handler {
// @Tags Regions
// @Produce json
// @Success 200 {array} api.RegionSummary
// @Failure 404 {object} handlers.APIError
// @Failure 500 {object} handlers.APIError
// @Router /regions [get]
func listRegions(reader api.Reader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
regions, err := reader.ListRegions(r.Context())
if err != nil {
respondError(w, http.StatusNotFound, "no regions found")
respondError(w, http.StatusInternalServerError, "internal server error")
return
}
if regions == nil {
regions = []api.RegionSummary{}
}
respond(w, http.StatusOK, regions)
}
}
@@ -57,6 +62,7 @@ func listRegions(reader api.Reader) http.HandlerFunc {
// @Success 200 {object} api.Region
// @Failure 400 {object} handlers.APIError
// @Failure 404 {object} handlers.APIError
// @Failure 500 {object} handlers.APIError
// @Router /regions/{regionId} [get]
func getRegion(reader api.Reader) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
@@ -67,9 +73,13 @@ func getRegion(reader api.Reader) http.HandlerFunc {
return
}
region, err := reader.GetRegion(r.Context(), int32(regionInt))
if err != nil {
switch {
case errors.Is(err, pgx.ErrNoRows), err == nil && region == nil:
respondError(w, http.StatusNotFound, "region not found")
return
case err != nil:
respondError(w, http.StatusInternalServerError, "internal server error")
return
}
respond(w, http.StatusOK, region)
}
@@ -191,3 +201,13 @@ func resolveRegionIATAs(ctx context.Context, regionID, regionSlug string, reader
}
return region.IATAs, nil
}
// respondRegionError reports an unknown or malformed region filter as 400 and a
// failed lookup as a retryable 500.
func respondRegionError(w http.ResponseWriter, err error) {
if errors.Is(err, pgx.ErrNoRows) || errors.Is(err, strconv.ErrSyntax) || errors.Is(err, strconv.ErrRange) {
respondError(w, http.StatusBadRequest, err.Error())
return
}
respondError(w, http.StatusInternalServerError, "internal server error")
}
+3 -2
View File
@@ -15,6 +15,7 @@ import (
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/MeshCore-Beacon/beacon-server/internal/scopestore"
"github.com/jackc/pgx/v5"
)
func TestGetStatsScopes_RegionErrors(t *testing.T) {
@@ -22,10 +23,10 @@ func TestGetStatsScopes_RegionErrors(t *testing.T) {
t.Run(query, func(t *testing.T) {
reader := stubReader{
getRegion: func(context.Context, int32) (*api.Region, error) {
return nil, errors.New("not found")
return nil, pgx.ErrNoRows
},
getRegionBySlug: func(context.Context, string) (*api.Region, error) {
return nil, errors.New("not found")
return nil, pgx.ErrNoRows
},
}
w := httptest.NewRecorder()
+1 -1
View File
@@ -55,7 +55,7 @@ func listScopes(reader api.Reader, scopes ScopeMembership) http.HandlerFunc {
if regionID != "" || region != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionID, region, reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
+1 -1
View File
@@ -43,7 +43,7 @@ func getSignalStats(reader api.Reader) http.HandlerFunc {
if q.Get("regionId") != "" || q.Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(ctx, q.Get("regionId"), q.Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, "region not found")
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
+2 -1
View File
@@ -14,6 +14,7 @@ import (
"time"
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/jackc/pgx/v5"
)
type signalReader struct {
@@ -56,7 +57,7 @@ func TestSignalFiltersAndErrors(t *testing.T) {
case "empty":
return &api.Region{}, nil
case "missing":
return nil, errors.New("not found")
return nil, pgx.ErrNoRows
default:
return &api.Region{IATAs: []string{"YVR"}}, nil
}
+11 -11
View File
@@ -86,7 +86,7 @@ func getStatsOverview(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -123,7 +123,7 @@ func getStatsObservations(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -164,7 +164,7 @@ func getStatsPayloadBreakdown(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -207,7 +207,7 @@ func getStatsTopNodes(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -254,7 +254,7 @@ func getStatsTopObservers(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -302,7 +302,7 @@ func getStatsTopAdvertisers(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -348,7 +348,7 @@ func getStatsClockDrift(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -390,7 +390,7 @@ func getStatsTopTalkers(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -437,7 +437,7 @@ func getStatsRadioPresets(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -475,7 +475,7 @@ func getStatsScopes(reader api.Reader, scopes ScopeMembership) http.HandlerFunc
if regionID := r.URL.Query().Get("regionId"); regionID != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionID, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
@@ -522,7 +522,7 @@ func getStatsNodeTypes(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), regionIDStr, r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
+1 -1
View File
@@ -54,7 +54,7 @@ func getStatsSeries(reader api.Reader, maxWindow time.Duration) http.HandlerFunc
if q.Get("regionId") != "" || q.Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(ctx, q.Get("regionId"), q.Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, "region not found")
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)
+2 -1
View File
@@ -14,6 +14,7 @@ import (
"time"
"github.com/MeshCore-Beacon/beacon-server/internal/api"
"github.com/jackc/pgx/v5"
)
func TestGetStatsSeries(t *testing.T) {
@@ -49,7 +50,7 @@ func TestGetStatsSeries(t *testing.T) {
case "empty":
return &api.Region{}, nil
case "missing":
return nil, errors.New("not found")
return nil, pgx.ErrNoRows
}
return &api.Region{IATAs: []string{"YVR"}}, nil
},
+1 -1
View File
@@ -83,7 +83,7 @@ func listTraceTags(reader api.Reader) http.HandlerFunc {
if regionIDStr := r.URL.Query().Get("regionId"); regionIDStr != "" || r.URL.Query().Get("region") != "" {
regionIATAs, err := resolveRegionIATAs(r.Context(), r.URL.Query().Get("regionId"), r.URL.Query().Get("region"), reader)
if err != nil {
respondError(w, http.StatusBadRequest, err.Error())
respondRegionError(w, err)
return
}
iatas = append(iatas, regionIATAs...)